/* ===========================================================================
   hotel-table.css — hotel results as a table in the middle workspace.

   Ported VERBATIM from the DESIGN delta
   (docs/product/mockups/hotel-middle-table/css/hotel-table.css, owner-GO'd
   2026-09-13); the only additions are the two clearly-marked sections at the
   bottom — the sub-1024px in-chat host, which the frames do not cover because
   the workspace does not exist below 1024px. NOTHING is ported from the
   mockup's harness.css.

   This is the ONLY new CSS this feature ships. Everything else it renders
   comes from the real app sheets (tokens.css, chat.css, flight-table.css,
   table-first.css) — the hotel table is a SIBLING of the flight table, not a
   new visual family:

     <div class="hotel-table-container">
       <table class="flight-table hotel-table"> ... </table>
     </div>

   Carrying `.flight-table` inherits thead/th/td/row-hover/sort-icon grammar
   verbatim from flight-table.css. The container is deliberately NOT
   `.flight-table-container.award-container` — those modifiers carry the
   <=700px "table becomes cards" transform, and the workspace's mobile story
   for this table is horizontal scroll INSIDE the pane (brief, hard
   constraints). `.hotel-table-container` re-declares only the bounded
   scrollbox the sticky header needs.
   =========================================================================== */

/* ---- container ---------------------------------------------------------- */
.hotel-table-container {
    margin: 0 0 14px;
    max-width: 100%;
    max-height: 72vh;
    overflow-y: auto;
    overflow-x: auto;
}

/* ---- the honesty line under the chips row -------------------------------
   Result-level disclosures (area_filter + search_defaults) joined with " · "
   into ONE muted sentence. Plain text on purpose: not a chip (chips are
   removable filters the user owns), not a banner (banners interrupt). It sits
   directly under .tw-header so it reads as a footnote on the chip row above
   it, and it is never dismissable. */
.tw-disclosure {
    padding: 8px 16px 0;
    font-size: 0.74rem;
    line-height: 1.45;
    color: var(--aw-muted);
}
/* [hidden] needs an explicit rule wherever author CSS declares a display —
   recurred twice in this codebase (memory: hidden-attr-loses-to-author-display). */
.tw-disclosure[hidden] { display: none; }
/* Same guard for the results/empty swap: .tw-body carries author layout
   (flex/overflow), so state-swapping it by attribute needs the rule spelled
   out rather than trusting the UA sheet. */
.tw-body[hidden] { display: none; }

/* ---- 1. thumbnail ------------------------------------------------------- */
.hotel-table th.ht-thumb-th {
    width: 1%;
    padding-right: 4px;
}
.hotel-table td.ht-thumb-cell {
    width: 1%;
    padding-right: 4px;
}
.ht-thumb {
    display: block;
    width: 64px;
    height: 48px;
    border-radius: 6px;
    object-fit: cover;
    background: var(--aw-elevated);
}
/* Missing image is a KNOWN absence, not a broken <img>: a flat neutral block
   in the same box, with a quiet glyph so it reads as "no photo" rather than
   "loading". Never a stock photo of some other hotel. */
.ht-thumb-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 48px;
    border-radius: 6px;
    background: var(--aw-elevated);
    border: 1px solid var(--aw-hairline);
    color: var(--aw-structure);
    font-size: 0.8rem;
    line-height: 1;
}

/* ---- 2. hotel identity -------------------------------------------------- */
.hotel-table td.ht-name-cell {
    min-width: 180px;
    max-width: 280px;
}
.ht-name {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--aw-ink);
    white-space: normal;
}
/* hotel_class display string ("4-star hotel"). The element is NOT rendered at
   all when the payload string is empty — no placeholder, no "—": an absent
   class line is an omission, and "—" is reserved for a value the row was
   expected to carry. */
.ht-class {
    display: block;
    margin-top: 2px;
    font-size: 0.74rem;
    color: var(--aw-muted);
}
/* `type: vacation rental` rides as a structural tag, reusing flight-table's
   .fd-hub-tag grammar (muted outline chip) — it states what the row IS, it
   does not rank the row against its neighbours, so it must never be an accent
   badge. Only the non-default type is ever tagged; a hotel is untagged. */
.hotel-table .fd-hub-tag {
    margin-left: 6px;
}

/* ---- 3. rating ---------------------------------------------------------- */
.hotel-table td.ht-rating-cell {
    white-space: nowrap;
}
.ht-rating {
    font-size: 0.9rem;
    font-weight: 650;
    color: var(--aw-ink);
    font-variant-numeric: tabular-nums;
}
.ht-reviews {
    margin-left: 5px;
    font-size: 0.74rem;
    color: var(--aw-muted);
    font-variant-numeric: tabular-nums;
}

/* ---- 3b. location score (design 2026-09-19-hotel-location-column) -------
   Sibling quality score to RATING, sortable like it, but deliberately in a
   QUIETER register than .ht-rating's bold --aw-ink — the number must not
   read as a second overall rating. --aw-structure is the same "states what
   the row has, at less weight than the headline number" step .ht-bfast-paid
   already uses. No review-count-style subtext exists or is ever appended
   (buildLocationCell has nothing else to render) — that half of the
   distinct-register requirement is structural, not just visual. */
.hotel-table th.ht-loc-th,
.hotel-table td.ht-loc-cell {
    white-space: nowrap;
}
.ht-loc-score {
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--aw-structure);
    font-variant-numeric: tabular-nums;
}
/* .ht-source-label ("· Google" header attribution) removed by owner ruling
   2026-09-21: it manufactured a phantom empty column (wide nowrap header,
   short left-aligned body cell) — RATING, also a Google number, carries no
   attribution either, so this restores consistency. See the SORT_COLUMNS
   comment in hotel-table.js for the full ruling. */
/* The mobile fold line inside .ht-rating-cell (design doc §7). Hidden by
   default — the dedicated LOCATION column is what desktop/tablet shows — so
   the score is never rendered twice. Shares .ht-anchor/.ht-total's own
   always-present-element, CSS-toggled-visibility convention; see the
   <=700px swap below. */
.ht-loc-fold {
    display: none;
}
.ht-loc-fold-label {
    color: var(--aw-muted);
    font-size: 0.74rem;
}
.ht-loc-fold .ht-loc-score {
    font-size: 0.74rem;
}

/* ---- 4. price / night --------------------------------------------------- */
.hotel-table th.ht-price-th,
.hotel-table td.ht-price-cell {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.ht-price {
    font-size: 0.9rem;
    font-weight: 650;
    color: var(--aw-ink);
}
/* A nightly figure DERIVED from a stay total. The qualifier is part of the
   value, never a tooltip: the "~" and the "avg/night" suffix both render, and
   the number is the same weight as a quoted rate so nobody reads it as a
   lesser row — only as a differently-sourced number. */
.ht-price-est {
    font-size: 0.9rem;
    font-weight: 650;
    color: var(--aw-ink-dim);
}
.ht-price-est-qual {
    margin-left: 4px;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--aw-muted);
    letter-spacing: 0.02em;
}
/* ---- the DUAL DISPLAY (owner ruling 2026-09-13, design doc §10) ---------
   The headline number is the ALL-IN rate and keeps primary weight; the "all-in"
   qualifier rides beside it, muted, because it labels the number rather than
   competing with it. "all-in" and "taxes & fees included" are the only tax
   phrasings allowed anywhere in hotel price copy — never a named rate or a
   percentage. */
.ht-price-allin {
    margin-left: 5px;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--aw-muted);
    letter-spacing: 0.02em;
}
/* ---- the NO-FAMILY-RATE state (design 2026-09-15-hotel-occupancy R4) ------
   The honesty core. A property that returned no family-inclusive rate under
   this occupancy shows a KNOWN ABSENCE — a cached fact about this property, NOT
   the transient "unavailable right now" and NEVER the adults-only price it was
   quoted at. It gets its OWN muted 600-weight mark, deliberately distinct from
   the reserved "—" unknown glyph (.ht-unknown, "a value the column promised and
   the payload did not carry" — a different claim), and never green / never
   crowned. "Adults only · no children" (R6) is the same register, more specific
   copy, gated by BUILD on a measured signal. */
.ht-no-family-rate {
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--aw-muted);
    white-space: nowrap;
}
/* The comparability anchor: the vendor's PRE-TAX teaser, the figure Google and
   the OTAs display. Its own line under the headline (on every host — on a
   phone it simply wraps there), muted, and never the price the row is sorted
   or filtered by. "from" marks it as a starting figure, not the price paid.
   Deliberately carries NO channel name: no channel sells the room at this
   number, so a name here would imply a bookable rate that does not exist. */
.ht-anchor {
    display: block;
    margin-top: 2px;
    font-size: 0.74rem;
    color: var(--aw-muted);
    font-variant-numeric: tabular-nums;
}
/* price_total, when present. Muted second line; omitted entirely when absent
   (a total is supplementary, so its absence is an omission, not a "—"). */
.ht-total {
    display: block;
    margin-top: 2px;
    font-size: 0.74rem;
    color: var(--aw-muted);
}
/* The reserved unknown glyph. Used ONLY where a value the column promises is
   genuinely missing. Never 0, never a guess. */
.ht-unknown {
    color: var(--aw-muted);
    font-size: 0.9rem;
}

/* ---- 5. amenities ------------------------------------------------------- */
.hotel-table td.ht-amen-cell {
    /* Measured, not guessed: at 1440px the workspace gives the table a 703px
       visible box (the same number flight-table.css's .fd-composed cap was
       measured against). A 230px floor here pushed the table's min-content to
       822px and silently cut the amenity chips off the right edge. 150px holds
       two chips per line and lets the column absorb whatever is left. */
    min-width: 150px;
}
.ht-amenities {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
/* Muted outline chips — same "states what the row has" register as
   .fd-hub-tag, deliberately quieter than .tw-chip (which is an APPLIED FILTER
   the user owns and can remove). */
.ht-amenity {
    display: inline-block;
    padding: 1px 7px 2px;
    border: 1px solid var(--aw-hairline);
    border-radius: var(--aw-radius-pill);
    font-size: 0.7rem;
    color: var(--aw-muted);
    white-space: nowrap;
}
.ht-amenity-more {
    border-style: dashed;
    color: var(--aw-structure);
}

/* ---- breakfast column (design 2026-09-19-hotel-breakfast-column) --------
   3-state, muted register. NO green "win" tint — an amenity-register mark,
   deliberately quiet (same rationale as .ht-amenity above). The width floor
   is measured against the same 703px workspace box the .ht-amen-cell note
   cites: a short categorical cell that must not force the table wider. */
.hotel-table th.ht-bfast-th,
.hotel-table td.ht-bfast-cell {
    min-width: 76px;
    white-space: nowrap;
}
.ht-bfast {
    font-size: 0.72rem;
}
.ht-bfast-free {
    display: inline-block;
    padding: 1px 7px 2px;
    border: 1px solid var(--aw-hairline);
    border-radius: var(--aw-radius-pill);
    color: var(--aw-muted);
    white-space: nowrap;
}
.ht-bfast-paid {
    color: var(--aw-structure);
    font-variant-numeric: tabular-nums;
}
.ht-bfast-none {
    color: var(--aw-structure);
}

/* ---- expand panel ------------------------------------------------------
   Reuses .flight-detail / .fd-section / .fd-label / .fd-line / .fd-mono /
   .fd-note / .fd-actions / .fd-book from flight-table.css unchanged. Only the
   hotel-specific measure caps live here. */
/* The open row and its panel row reuse flight-table.css's own
   `.expandable-row.is-expanded > td` tint and `.flight-detail-row > td`
   surface unchanged — no hotel-specific rule is needed or wanted. */
.flight-detail.ht-detail {
    box-sizing: border-box;
    max-width: 820px;
    align-items: flex-start;
    gap: 14px 30px;
}
.ht-detail .fd-section.ht-amen-full {
    max-width: 330px;
}
.ht-detail .ht-amenities {
    max-width: 330px;
}
/* "Prices across sites" — an OTA name and its price per line, mono so the
   figures align. Booking links are not fetched with the search, so the panel
   says so instead of rendering dead link affordances. */
.ht-ota {
    display: flex;
    justify-content: space-between;
    gap: 18px;
    min-width: 190px;
    font-family: var(--aw-font-mono);
    font-size: 0.78rem;
    color: var(--aw-ink-dim);
    font-variant-numeric: tabular-nums;
}
.ht-ota-name { color: var(--aw-ink-dim); }
.ht-ota-price { color: var(--aw-ink); font-weight: 600; }
/* A hydrated entry (the booking-links detail call resolved with a real link)
   is an <a>, and chat.css's `.bubble a { color: accent; text-decoration:
   underline }` at (0,1,1) OUTRANKS `.ht-ota` at (0,1,0) — so on the in-chat
   host the same markup that reads correctly in the workspace would come back
   accent-underlined with still-ink-dim children. `.bubble a.ht-ota` (0,2,1)
   settles it in both directions rather than relying on sheet order, which is
   the equal-specificity trap this codebase has paid for three times.
   The link affordance moves to the name on hover, beside its own ↗. */
a.ht-ota,
.bubble a.ht-ota {
    color: var(--aw-ink-dim);
    text-decoration: none;
}
a.ht-ota:hover .ht-ota-name,
.bubble a.ht-ota:hover .ht-ota-name {
    color: var(--aw-accent);
}
/* The two hotel actions leave the app, so both are outline pills rather than
   one filled primary: neither is the app's own recommended next step. */
.fd-book.ht-book-out {
    background: var(--aw-surface);
    color: var(--aw-ink);
    border: 1px solid var(--aw-hairline-strong);
}

/* ---- empty state -------------------------------------------------------
   The empty-state family primitive (agent-lab design/2026-09-01-empty-state-
   family.md) — reuses .view-toggle-empty's 240px centred block from
   table-first.css; only the two copy lines are typed here. No button, no
   counts. */
.ht-empty-head {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--aw-ink);
}
.ht-empty-sub {
    font-size: 0.85rem;
    color: var(--aw-muted);
}

/* ---- chat rail pointer -------------------------------------------------
   Under the new split the chat bubble carries the narrative sentence plus ONE
   pointer row into the workspace. `white-space: normal` is mandatory: chat.css
   sets `.bubble { white-space: pre-wrap }`, which would otherwise render this
   structured child's source newlines as blank lines (the .bubble pre-wrap
   inflation bug, PR #235). */
.ht-pointer {
    /* Block-level flex + fit-content, NOT inline-flex: the pointer is its own
       row under the sentence. Inline-flex let it flow into the last line of
       prose ("...a night. [12 hotels in the table ->]"), which read as a word
       rather than as the hand-off to the workspace. */
    display: flex;
    width: fit-content;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
    padding: 7px 11px;
    border: 1px solid var(--aw-hairline-strong);
    border-radius: var(--aw-radius-sm);
    background: var(--aw-ground-2);
    color: var(--aw-ink-dim);
    font-size: 0.82rem;
    font-weight: 600;
    white-space: normal;
    cursor: pointer;
    text-align: left;
}
.ht-pointer:hover {
    border-color: var(--aw-accent);
    color: var(--aw-accent);
}
.ht-pointer-arrow {
    color: var(--aw-muted);
    font-size: 0.9rem;
}
.ht-pointer:hover .ht-pointer-arrow { color: var(--aw-accent); }

/* ---- mobile: horizontal scroll INSIDE the pane -------------------------
   The workspace never hands the phone a body-level horizontal scroll. The
   table keeps its columns and pans inside .hotel-table-container; only the
   thumbnail shrinks, because a 64px image column is 17% of a 375px screen. */
@media (max-width: 700px) {
    /* --- shared pane chrome at phone widths ---------------------------------
       FLAGGED FOR BUILD: these four rules touch chrome the FLIGHT workspace
       also uses, and they are unreachable today (table-first.js only activates
       the pane at >=1024px). They are here because the frame would otherwise
       lie: measured at 375px, .tw-filter-group (flex:1, min-width:0) collapses
       to ~0 and its chips overflow the pane's `overflow:hidden` edge, and the
       nowrap .tw-view-strip is painted over by the two door buttons. Stacking
       the two control-bar groups and letting the strip wrap is the minimum fix;
       nothing here is hotel-specific. */
    .tw-header { padding: 10px 12px; }
    .tw-view-strip { white-space: normal; }
    /* The view strip + applied chips take their own full-width row instead of
       competing with the nav group and the doors for a 351px line. */
    .tw-chips { flex: 1 0 100%; }
    /* One door per state (the app's own rule, index.html's #railNewSearch
       note): at phone widths the mobile header already carries the search
       door, so the pane must not paint a second one. */
    .tw-new-search-btn { display: none; }
    .tw-control-bar { padding: 10px 12px 11px; }
    .tw-scope-group,
    .tw-filter-group { flex: 1 0 100%; }
    .tw-bar-divider { display: none; }

    .hotel-table-container {
        max-height: none;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    .hotel-table {
        /* Wider than the pane on purpose: the cut-off column at the right edge
           IS the scroll affordance. */
        min-width: 620px;
    }
    .ht-thumb,
    .ht-thumb-empty {
        width: 44px;
        height: 33px;
    }
    .ht-thumb-empty { font-size: 0.66rem; }
    .hotel-table td.ht-name-cell { min-width: 160px; }
    .hotel-table td.ht-amen-cell { min-width: 190px; }
    .hotel-table th.ht-bfast-th,
    .hotel-table td.ht-bfast-cell { min-width: 64px; }
    .tw-disclosure {
        padding: 8px 12px 0;
        font-size: 0.72rem;
    }

    /* ---- location score fold (design 2026-09-19-hotel-location-column §7,
       owner ruling: "no new column at phone width") — the ONE column in this
       table that disappears rather than panning with the rest. The dedicated
       column hides and the already-rendered fold line (built unconditionally
       whenever showLocation is on) takes over inside the rating cell — never
       both visible, never neither, by construction: both rules flip on the
       same breakpoint. */
    .hotel-table th.ht-loc-th,
    .hotel-table td.ht-loc-cell { display: none; }
    .ht-loc-fold {
        display: block;
        margin-top: 2px;
    }
}

/* ---- ADDITION 1 (not in the frames): the sub-1024px in-chat host -------
   The frames draw the workspace, and table-first.js only activates the
   workspace at >=1024px. On a phone (and in a `?tf=0` opt-out session) there
   is no middle pane to mount into, so the SAME table renders where the
   retired card grid used to — inside the chat bubble. The card grid is gone
   either way; what survives is the rendering, not a second grammar.

   Three rules only: let the bubble take the full chat width (the rule the
   retired hotel-cards.css opened with, for the same reason), reset the
   `.bubble { white-space: pre-wrap }` inheritance that PR #235 proved
   inflates structured markup inside a bubble, and drop the 72vh scrollbox
   cap that only makes sense inside a pane. */
.bubble:has(.ht-inline-host) {
    max-width: 100%;
    width: 100%;
}
.ht-inline-host {
    margin: 12px 0 2px;
    white-space: normal;
}
.ht-inline-host .hotel-table-container {
    max-height: none;
}
/* The empty state renders on this host too, through the same family
   primitive and with the SAME copy (DESIGN rider (d), 2026-09-13) — only the
   vertical floor degrades. The family's 240px is "the tab bar plus four rows
   of visual weight" in a workspace pane; inside a chat bubble there is no tab
   bar and no rows to balance against, and 240px of empty space reads as a
   broken panel rather than a short answer. The copy never degrades; only this
   number does. */
.ht-inline-host .view-toggle-empty {
    min-height: 160px;
}

/* ---- ADDITION 2: [hidden] guards for the chrome this view toggles ------
   el.hidden does NOT beat an author `display` rule (memory:
   hidden-attr-loses-to-author-display — recurred twice in this codebase, most
   recently #370). The hotel chrome hides the flight-only scope group, the bar
   divider and the per-flight filter chips by attribute, and all three carry
   author display rules in table-first.css. */
.tw-scope-group[hidden],
.tw-bar-divider[hidden],
.tw-filter-group[hidden] {
    display: none;
}

/* ==========================================================================
   ADDITION 3: "Weigh picks" for hotels
   (docs/product/mockups/hotel-weigh-picks/notes.md, frames A1-A3).

   Two things are deliberately NOT here, because the frames proved they are
   not needed:

   1. the PICK COLUMN needs no new CSS at all. The hotel table already carries
      class `flight-table` (§1 of the middle-table notes), so
      `.flight-table th.shortlist-th`, `.flight-table td.shortlist-cell`,
      `.shortlist-checkbox` and the global
      `body:not(.shortlist-active) .shortlist-cell { display: none }` mode rule
      (flight-table.css) all apply verbatim.
   2. the TRAY needs none either: `#shortlistTray` is the shipped node,
      rendered by Shortlist._ensureTray with its own shipped styles.

   One shipped rule does NOT reach this table, and that is correct: the <=700px
   block that pulls a flight's pick cell into the row's top-right corner is
   scoped to `.flight-table-container`, and this table's container is
   `.hotel-table-container` — hotels do not become cards on mobile, they pan
   inside the pane (middle-table §6), so the checkbox stays an ordinary cell.

   The media query sits LAST on purpose: chat.css puts media queries before
   base rules and equal-specificity overrides silently lose the cascade there
   (three real bugs in one week). Every rule below was read back with
   getComputedStyle in the capture that produced the frames.
   ========================================================================== */

/* The weigh view is a VIEW of the picked rows, hosted where the hotels are:
   the workspace slot on desktop, the same slot inside the chat bubble below
   1024px. It REPLACES the table while it is open (Back restores it) rather
   than stacking above it: two scrolling tables in one pane is the duplication
   the middle-table ruling retired, and the picked rows are rows the user just
   chose. */
.hotel-weigh {
    margin: 0 0 14px;
    padding: 14px 16px 12px;
    background: var(--aw-surface);
    border: 1px solid var(--aw-hairline-strong);
    border-radius: var(--aw-radius);
    /* chat.css sets .bubble { white-space: pre-wrap } and a structured child
       inherits it, which is exactly how PR #235 inflated card heights. Reset
       at the block root, the way .best-deal-banner does. */
    white-space: normal;
}

.hotel-weigh-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 0 0 12px;
}

/* mirrors .shortlist-card-title */
.hotel-weigh-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--aw-ink);
}

/* mirrors .shortlist-card-count */
.hotel-weigh-count {
    margin-left: 8px;
    font-family: var(--aw-font-mono);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--aw-muted);
    font-weight: 500;
}

/* mirrors .shortlist-tray-clear (outline, quiet, never the accent) */
.hotel-weigh-back {
    margin-left: auto;
    background: none;
    border: 1px solid var(--aw-hairline-strong);
    color: var(--aw-ink-dim);
    border-radius: 8px;
    padding: 6px 12px;
    font-size: 0.8rem;
    cursor: pointer;
}

.hotel-weigh-back:hover {
    border-color: var(--aw-accent);
    color: var(--aw-ink);
}

.hw-scroll-wrap {
    position: relative;
}

.hw-scroll {
    overflow-x: auto;
}

/* Attributes-as-rows x picks-as-columns — the shortlist insight card's own
   shape. table-layout:fixed + definite widths for the same reason the card
   needs them: auto layout balloons every column to its widest cell. */
.hw-table {
    border-collapse: collapse;
    table-layout: fixed;
    font-size: 0.82rem;
}

.hw-table th,
.hw-table td {
    /* border-box so the declared column width IS the painted width: with
       table-layout:fixed and the app's content-box default, 180 + 20 padding
       silently became ~202 and the third column fell off the pane edge. */
    box-sizing: border-box;
    border: 1px solid var(--aw-hairline);
    padding: 8px 10px;
    text-align: left;
    vertical-align: top;
}

.hw-table thead th:first-child {
    width: 120px;
}

/* 180px, not the insight card's 168px: a hotel name is prose where a flight's
   "Option 2" is a token. Disclosed as a deviation, not an accident. 120 +
   3 x 180 = 660px, which fits the 669px workspace scroll box at 1440 without
   panning; a 4th pick (the shipped MAX_PICKS) takes it to 840 and it pans.
   These numbers are mirrored in hotel-table.js (WEIGH_COL_PX et al) — change
   them together. */
.hw-table thead th:not(:first-child) {
    width: 180px;
}

.hw-table thead th {
    background: var(--aw-surface);
    vertical-align: bottom;
}

/* The attribute-label column stays narrow and quiet — copied from
   .shortlist-card-table tbody th. */
.hw-table tbody th {
    font-family: var(--aw-font-mono);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--aw-muted);
    font-weight: 500;
    white-space: nowrap;
    background: var(--aw-surface);
}

.hw-name {
    font-weight: 600;
    color: var(--aw-ink);
    white-space: normal;
}

.hw-value {
    color: var(--aw-ink);
    font-variant-numeric: tabular-nums;
}

/* Muted second line inside a cell (stay total, review count) — the
   .shortlist-cpp-note idiom. */
.hw-sub {
    display: block;
    margin-top: 2px;
    font-size: 0.72rem;
    color: var(--aw-muted);
    white-space: normal;
}

/* PER-ATTRIBUTE WIN. Deliberately NOT a crown, badge or pill: this reuses the
   effective-cost gradient idiom the app already describes as "math, not a
   crowned winner" (.shortlist-cost + .shortlist-cost-lowest), so a marked
   cell reads as "lowest of what you picked", never as a verdict. Legible in
   both themes: --aw-eff-cheap is the AA-repaired token. There is NO losing
   treatment at all, which is what lets an unknown sit in a marked row without
   being marked down even by accident. */
.hw-win {
    color: var(--aw-eff-cheap);
    font-weight: 700;
}

/* Unknown value. House law: "—", never 0, never "unrated". */
.hw-unknown {
    color: var(--aw-muted);
}

/* Amenities: sameness collapses to ONE muted line, the diffs carry the
   decision. The block sits OUTSIDE the panning table because these are
   statements about SUBSETS of the picks, not per-column values — and inside
   the table at 375px the decision content was the part you had to pan
   sideways to read. */
.hw-amen {
    margin: 12px 0 0;
    padding-top: 10px;
    border-top: 1px solid var(--aw-hairline);
}

.hw-amen-label {
    display: block;
    margin-bottom: 6px;
    font-family: var(--aw-font-mono);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--aw-muted);
}

.hw-amen-shared {
    margin: 0 0 8px;
    font-size: 0.78rem;
    color: var(--aw-muted);
}

.hw-amen-diff {
    margin: 0 0 4px;
    font-size: 0.78rem;
    color: var(--aw-ink-dim);
}

.hw-amen-diff:last-child {
    margin-bottom: 0;
}

.hw-amen-who {
    color: var(--aw-ink);
    font-weight: 600;
}

/* The legend that makes the win tint readable without a badge — the same job
   .shortlist-note does for the effective-cost gradient. Its own rule above,
   so it reads as a footnote on the whole view and not as one more amenity
   line (it ran straight on from them at 375px before the separator). */
.hw-legend {
    margin: 12px 0 0;
    padding-top: 10px;
    border-top: 1px solid var(--aw-hairline);
    font-size: 0.75rem;
    color: var(--aw-muted);
}

/* Degenerate case: one pick is not a comparison. */
.hw-single {
    margin: 0;
    font-size: 0.82rem;
    color: var(--aw-ink-dim);
}

/* ---- Mobile: the columns pan INSIDE the host, labels stay pinned --------
   Last in the file so these win the cascade against the base rules above. */
@media (max-width: 700px) {
    .hotel-weigh {
        padding: 10px;
    }

    /* A touch tighter: the whole view then fits one 375x812 screen without
       the reader having to scroll to reach the legend that explains the
       green marks. */
    .hw-table th,
    .hw-table td {
        padding: 6px 9px;
    }

    /* The count must not shove the Back control onto two lines. */
    .hotel-weigh-head {
        flex-wrap: wrap;
        align-items: center;
    }

    .hotel-weigh-title {
        font-size: 0.9rem;
    }

    .hotel-weigh-back {
        white-space: nowrap;
        padding: 6px 10px;
    }

    .hw-table thead th:first-child {
        width: 92px;
    }

    .hw-table thead th:not(:first-child) {
        width: 168px;
    }

    /* Sticky label column. border-collapse drops the border of a sticky cell
       once it overlaps, so the right-hand rule is painted as an inset shadow
       instead of relying on the collapsed border. */
    .hw-table thead th:first-child,
    .hw-table tbody th {
        position: sticky;
        left: 0;
        z-index: 2;
        background: var(--aw-surface);
        box-shadow: inset -1px 0 0 var(--aw-hairline-strong);
    }

    /* The desktop nowrap label overflowed a 92px sticky cell and printed
       "PRICE / NIGHT$135" across the first value cell. Labels wrap here. */
    .hw-table tbody th {
        white-space: normal;
    }
}

/* ---- ADDITION 4: hotel media ------------------------------------------
   Ported from docs/product/mockups/hotel-media/css/hotel-media.css (owner GO
   2026-09-14 on the frames). Two additions to the hotel table and nothing
   else: the thumbnail HOVER PREVIEW (design ruling 2026-09-14-hotel-depth-
   round §1a) and the expand panel's PHOTOS section (§1b, upgraded by §4 — the
   strip rides the LIST payload, so it costs zero calls).

   Every colour, radius, shadow and spacing value comes from tokens.css.
   Nothing here restyles an existing hotel or flight rule; `.ht-photo-*` and
   `.ht-hover-*` are the only new class prefixes, and the whole block can be
   deleted without touching what the table renders today. */

/* The hover preview: ONE node, created once and reused, `hidden` when closed.
   `position: fixed` because the table lives inside `.hotel-table-container`'s
   own scrollbox (max-height 72vh, overflow auto) and an absolutely-positioned
   preview would be clipped by it.

   `pointer-events: none` is load-bearing, not tidiness: the preview opens
   UNDER the cursor's travel path, and a preview that could take the pointer
   would steal the mouseleave that closes it and swallow a click meant for the
   row beneath.

   z-index 6 sits one step above flight-table.css's sticky header row (5),
   which is the highest thing the table itself paints. A preview that slid
   UNDER the sticky header would be the #255 cash-freeze bug in a new costume. */
.ht-hover-preview {
    position: fixed;
    z-index: 6;
    top: 0;
    left: 0;
    /* border-box so the computed rect IS the rect the placement rule cleared
       against the price column — content-box silently adds the 1px border to
       both axes and pushes the preview 2px past its own guard. */
    box-sizing: border-box;
    /* Size is computed per-hover by the placement rule (hotel-table.js
       `previewPlacement`); these are the ceiling and the shape, not the value. */
    max-width: 480px;
    aspect-ratio: 3 / 2;
    overflow: hidden;
    border: 1px solid var(--aw-hairline-strong);
    border-radius: var(--aw-radius-sm);
    background: var(--aw-elevated);
    box-shadow: var(--aw-shadow);
    pointer-events: none;
    opacity: 1;
}
/* el.hidden loses to an author `display` rule — it has cost this codebase two
   separate bugs (memory: hidden-attr-loses-to-author-display, most recently
   #370). The preview declares no display of its own, but it DOES declare
   layout, so the guard is spelled out rather than trusted. */
.ht-hover-preview[hidden] { display: none; }
.ht-hover-preview img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
/* The thumbnail the preview is anchored to, while it is open. A 1px ring, not
   a scale or a glow: it says WHICH image is enlarged, and a hover affordance
   on a 64x48 cell must not reflow the row. */
.ht-thumb.is-previewing {
    outline: 1px solid var(--aw-accent);
    outline-offset: 1px;
}

/* The expand panel's Photos section. Plain `fd-section` grammar (`.fd-label` +
   content), so the panel gains a section rather than a new visual family — the
   same move Amenities, Stay and Prices across sites already made.

   `flex: 1 0 100%` gives Photos its OWN full-width line at the top of the
   panel. `.flight-detail` is `flex-wrap: wrap`, so without it a 480px hero
   would share line 1 with Amenities and leave the text sections stranded under
   440px of whitespace. */
.ht-detail .fd-section.ht-photos {
    flex: 1 0 100%;
    gap: 8px;
}

/* The enlarged image: 3:2, the same shape and ceiling as the hover preview, so
   "worth a click?" and "worth a booking?" show the same picture at the same
   proportions. `background` is the token block a slow image paints against —
   never a spinner, never a grey flash of a different shape. */
.ht-photo-hero {
    display: block;
    width: 100%;
    max-width: 480px;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-radius: var(--aw-radius-sm);
    background: var(--aw-elevated);
}

/* The strip IS the selector. Up to five slots, one per image the row actually
   carried — a property with two images shows two, and the missing three are
   OMITTED, never drawn as empty slots. A placeholder for an image nobody sent
   would claim the property has a photo we are failing to load. */
.ht-photo-strip {
    display: flex;
    flex-wrap: nowrap;
    gap: 6px;
    margin: 0;
    padding: 0;
}
.ht-photo-thumb {
    display: block;
    flex: 0 0 auto;
    width: 112px;
    height: 84px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: var(--aw-elevated);
    cursor: pointer;
    overflow: hidden;
    /* Unselected slots step back rather than disappear: the strip must still
       read as five available photos, not one photo and four shadows. */
    opacity: 0.55;
    transition: opacity 120ms ease;
}
.ht-photo-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.ht-photo-thumb:hover { opacity: 0.85; }
/* The active treatment. --aw-accent (interactive), deliberately NOT
   --aw-choice: the cyan choice mark is reserved for a DECISION about the trip
   (color-system-proposal §122/§184), and picking which photo to enlarge is a
   view control, not a decision. `outline` rather than `border` so selecting a
   slot never reflows the strip by 2px. */
.ht-photo-thumb.is-selected {
    opacity: 1;
    outline: 2px solid var(--aw-accent);
    outline-offset: 1px;
}
.ht-photo-thumb:focus-visible {
    outline: 2px solid var(--aw-accent);
    outline-offset: 1px;
}
/* A single-image property gets the hero and NO strip: a one-slot selector
   selects nothing. That rule lives in buildPhotosSection; this is its anchor. */

/* The phone host. Below 1024px there is no workspace, so the table renders
   inside the chat bubble and the panel comes with it. Two things change:

   (a) NO hover preview exists here at all — `armHover` gates on
       `(hover: hover) and (pointer: fine)`, a CAPABILITY query rather than a
       width query, so a small window on a desktop keeps its preview and a
       large tablet does not grow a phantom one. The touch path to imagery is
       the strip taps in the panel below.

   (b) The Photos block is capped to the PANE's visible width rather than the
       panel's. The phone table sets `min-width: 620px` and pans inside its
       container, so a hero at `width: 100%` of the panel would put a third of
       the image behind a horizontal pan. The block is left-anchored, which is
       where the container sits at scrollLeft 0, so at rest the whole hero and
       the head of the strip are on screen; the strip then pans on its own
       axis. */
@media (max-width: 700px) {
    .ht-detail .fd-section.ht-photos {
        max-width: calc(100vw - 56px);
    }
    .ht-photo-hero {
        max-width: 100%;
    }
    /* The strip pans horizontally inside the section, independently of the
       table's own pan. `-webkit-overflow-scrolling` matches what
       `.hotel-table-container` already asks for at this width. */
    .ht-photo-strip {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        /* Room for the selected slot's 2px outline + 1px offset, which would
           otherwise be clipped by the scrollbox at both ends. */
        padding: 3px;
        margin: -3px;
    }
    .ht-photo-thumb {
        width: 96px;
        height: 72px;
    }
}

/* ===========================================================================
   RICHER, HONESTER RESULTS — the row wins and the panel enrichment.
   Contract: docs/product/mockups/hotel-richer-results/notes.md (frames 1-4),
   ruling agent-lab/docs/design/2026-09-14-hotel-richer-results.md.

   Every block below is ADDITIVE and deletable: nothing here restyles an
   existing hotel or flight rule, and every element it styles is rendered only
   when its payload field is present (hotel-table.js, absent-not-empty). On
   today's payload — which carries none of those fields — not one of these
   selectors matches anything.

   HONESTY REGISTER, load-bearing: the sponsored tag and the deal chip are
   MUTED STRUCTURAL marks (hairline outline, --aw-muted ink), never an accent
   badge and never the green --aw-eff-cheap "win" tint. A sponsored row must
   never read as a crowned winner, and a deal chip states a payload fact — it
   does not rank the row. Free cancellation is a feature, not a win, so it
   keeps the same quiet register as an amenity chip.
   =========================================================================== */

/* ---- the Sponsored tag (list layer: ads[] membership) ------------------
   Reuses .fd-hub-tag verbatim — the same muted outline chip the "Vacation
   rental" type tag uses — so it reads as what the row IS, a paid placement.
   The class adds no colour of its own; the letter-spacing nudge is what makes
   "Sponsored" read as a structural label rather than a shouty ad badge. */
.hotel-table .fd-hub-tag.ht-sponsored-tag {
    letter-spacing: 0.06em;
}

/* ---- the Deal chip and its reason (list layer) -------------------------
   `deal_description` is the short chip label ("Deal" / "Great Deal"); `deal`
   is the explanation ("23% less than usual"). VERIFIED orientation against a
   real 2026-09-14 response (notes.md §0) — the ruling's first draft had the
   two swapped. Both live on their own muted line in the name cell so the
   right-aligned price stack stays a clean number column. */
.ht-deal-line {
    display: block;
    margin-top: 3px;
    line-height: 1.3;
}
.ht-deal-chip {
    display: inline-block;
    padding: 0 6px 1px;
    border: 1px solid var(--aw-hairline-strong);
    border-radius: var(--aw-radius-pill);
    font-family: var(--aw-font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.04em;
    color: var(--aw-muted);
    white-space: nowrap;
    vertical-align: middle;
}
/* The collapsible half: the reason hides at phone widths (below), where the
   chip alone carries the fact and the panel carries the detail. */
.ht-deal-why {
    margin-left: 6px;
    font-size: 0.72rem;
    color: var(--aw-muted);
    vertical-align: middle;
}

/* ---- "Ratings across sites" (details layer) ----------------------------
   One compact line per source, EACH ON ITS OWN NATIVE SCALE and labelled.
   There is no composite: Google 4.1/5 sitting next to Expedia 8.9/10 is the
   visual proof that blending them into one number would be a fabrication. */
.ht-detail .fd-section.ht-ratings {
    min-width: 210px;
}
.ht-rating-src {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 0.8rem;
    line-height: 1.5;
    font-variant-numeric: tabular-nums;
}
/* The source label carries the meaning — it is the thing the no-composite
   rule turns on, so it never shrinks below the score. */
.ht-rating-src-name {
    min-width: 84px;
    color: var(--aw-ink-dim);
}
.ht-rating-src-score {
    color: var(--aw-ink);
    font-weight: 650;
}
/* The "/5" or "/10" is part of the VALUE, never a tooltip: it is what stops a
   reader comparing 4.5 to 8.9 as if they were one scale. */
.ht-rating-src-max {
    color: var(--aw-muted);
    font-weight: 400;
}
/* Present-only. An absent count renders NOTHING here — never "(—)", never
   "(0)": a source that did not report a count did not promise one. */
.ht-rating-src-count {
    color: var(--aw-muted);
    font-size: 0.74rem;
}

/* ---- "What guests mention" (list layer, free) --------------------------
   Top 3 categories by mentions, each with its lean IN WORDS. The lean is a
   muted word, not a bar or a score, and the mention count rides beside it so
   the reader can weigh how much it rests on. */
.ht-detail .fd-section.ht-sentiment {
    min-width: 210px;
}
.ht-sentiment-line {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 0.8rem;
    line-height: 1.5;
}
.ht-sentiment-cat {
    min-width: 84px;
    color: var(--aw-ink-dim);
    font-weight: 600;
}
.ht-sentiment-lean {
    color: var(--aw-ink);
}
.ht-sentiment-count {
    color: var(--aw-muted);
    font-size: 0.74rem;
    font-variant-numeric: tabular-nums;
}

/* ---- "Getting around" (list layer, free) -------------------------------
   The distance answer, as the payload actually gives it. `nearby_places` is
   free per row but carries named POIs with transit times, no centre reference
   and no category, so there is no honest row-level "x km from centre" line —
   the whole list goes here instead, each place keeping its own transit line
   verbatim. Never a distance we computed. */
.ht-detail .fd-section.ht-nearby {
    min-width: 210px;
}
.ht-nearby-line {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    font-size: 0.8rem;
    line-height: 1.5;
}
.ht-nearby-place {
    color: var(--aw-ink-dim);
}
/* The transit figure, mono so the durations align down the column. */
.ht-nearby-trip {
    font-family: var(--aw-font-mono);
    font-size: 0.76rem;
    color: var(--aw-muted);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* ---- free cancellation on the price ladder (details layer) -------------
   A feature, not a win: the same quiet hairline register as an amenity chip,
   never the green win tint. Absent flag -> no tag, never "paid cancellation"
   (we do not know that). It rides with the OTA NAME, which already lays out
   space-between against the price, so the tag can never push the figure out
   of its tabular column. */
.ht-cancel {
    display: inline-block;
    margin-left: 8px;
    padding: 0 6px 1px;
    border: 1px solid var(--aw-hairline);
    border-radius: var(--aw-radius-pill);
    font-size: 0.66rem;
    color: var(--aw-muted);
    white-space: nowrap;
    vertical-align: middle;
}

/* ---- the phone host (<=700px): the deal reason collapses, and THE PANEL
   STACKS.

   The stack is a real defect fix, caught in a 375px render rather than in
   review (notes.md § Mobile panel). `.flight-detail` is `flex-wrap: wrap`
   inside a table cell that is 620px wide at this width (`.hotel-table {
   min-width: 620px }`), so the sections laid out in TWO COLUMNS and the
   right-hand one sat past the pane's edge: "Stay" clipped to "STA", the
   sentiment leans and counts cut off entirely, "Getting around" truncated.

   The table's own COLUMNS are allowed to pan — the cut-off column at the right
   edge IS the scroll affordance — but a PANEL is not a column set. It is
   prose-shaped content the reader deliberately opened, and none of it may
   require a horizontal pan to read. So the panel caps to the PANE's visible
   width and stacks one section per line, following the pattern the shipped
   Photos rule already set (`max-width: calc(100vw - 56px)`, left-anchored,
   which is where the container sits at scrollLeft 0).

   This block touches the SHARED panel layout (`.flight-detail.ht-detail`),
   not only the new sections, because "Stay" — a shipped section — was one of
   the things clipping. It is unreachable above 700px and changes nothing
   there. */
@media (max-width: 700px) {
    .ht-deal-why { display: none; }
    .ht-rating-src-name,
    .ht-sentiment-cat { min-width: 72px; }

    /* One section per line, the panel capped to the pane. */
    .flight-detail.ht-detail {
        flex-direction: column;
        flex-wrap: nowrap;
        align-items: flex-start;
        max-width: calc(100vw - 56px);
        gap: 12px;
    }
    .ht-detail > .fd-section,
    .ht-detail > .fd-actions {
        width: 100%;
        max-width: calc(100vw - 56px);
        min-width: 0;
    }
    /* SPECIFICITY, not document order (the cascade trap this codebase keeps
       re-learning). The shipped caps are `.ht-detail .fd-section.ht-amen-full`
       (0,3,0) and `.ht-detail .ht-amenities` (0,2,0), both 330px — wider than
       the 319px pane at 375. The two rules below MATCH each of those weights
       and win on order; a lighter selector would lose no matter how late it
       was declared. Verified by computed style, not by reading the diff. */
    .ht-detail > .fd-section.ht-amen-full { max-width: calc(100vw - 56px); }
    .ht-detail .ht-amenities { max-width: calc(100vw - 56px); }

    /* A long POI name wraps INSIDE its own column and the transit figure stays
       right-aligned on the first line, rather than the figure being pushed
       onto a line of its own — place and time must stay one scannable pair.
       `min-width: 0` is what actually lets a flex item wrap its text; without
       it the name refuses to shrink and pushes the figure out. */
    .ht-nearby-line { align-items: baseline; }
    .ht-nearby-place { flex: 1 1 auto; min-width: 0; }
    /* An OTA row carrying the free-cancellation tag may wrap. */
    .ht-ota { flex-wrap: wrap; }
}

/* ---- the expand panel goes two-column on desktop ------------------------
   (DESIGN brief agent-lab/docs/design/2026-09-24-hotel-panel-two-column-brief.md,
   owner GO 2026-09-25; lifted verbatim from its delta.css.) Kept at the END of
   the sheet on purpose: the desktop rules override the shipped Photos rule
   (`.ht-detail .fd-section.ht-photos`, 0,3,0) at EQUAL specificity and so must
   come after it, and the phone block must come after the shipped <=700px
   stack block whose `.ht-detail > .fd-section` rules no longer reach sections
   inside the body.
   Photos become a fixed LEFT column; every other section keeps its proven
   contract order and today's row-major 2-per-line wrap, now inside a BODY
   region beside the photos instead of below them. The 820px cap was a
   READABILITY cap for prose-shaped content; it moves from the whole panel to
   the body, so text never stretches to 2000px but the panel can be wider
   than 820 when photos sit beside it. Scoped to .ht-detail / .ht-detail-body
   only: the flight panel has neither class and is untouched.

   JS delta (hotel-table.js buildDetailRow, ~6 lines): after `photos`, create
   `var body = el('div', 'ht-detail-body')`, append every later section AND
   `actions` to `body` instead of `wrap`, append `body` to `wrap`; the async
   ratings insert uses `body.insertBefore(late, anchor)` and the anchor test
   becomes `actions.parentNode === body`. */

/* base: the body is a wrapping flex region at every width. Below the
   two-column breakpoint it takes the full line under the photos, which is
   byte-for-byte today's layout. */
.ht-detail .ht-detail-body {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 14px 30px;
    flex: 1 1 100%;
    min-width: 0;
}
/* Actions: their own line at the bottom of the body, right-aligned — the
   "done reading, now leave" position; still outline out-links, unchanged. */
.ht-detail .ht-detail-body > .fd-actions {
    flex: 1 0 100%;
    justify-content: flex-end;
    margin-left: 0;
}

/* The two-column switch keys on the PANE the table actually gets, not the
   viewport (real-engine finding, 2026-09-25): with the sidebar and the chat
   rail open, a 1100px window leaves the table pane ~390px wide, and a
   viewport-gated two-column pushed the whole body off-screen to the right
   of the photos. `.hotel-table-container` is the query container: its width
   comes from its block parent, never its content, so inline-size containment
   cannot collapse it; the hover preview is attached to document.body, so no
   fixed-position descendant is re-anchored. The chat-bubble host is excluded:
   `.bubble` is shrink-to-fit, and a contained box inside it would size to 0.
   1000px = panel padding (42) + photos (480) + gap (30) + the body's useful
   minimum (~440). Below it the panel keeps today's photos-then-body layout. */
.hotel-table-container {
    container: htpane / inline-size;
}
.ht-inline-host .hotel-table-container {
    container-type: normal;
}

/* desktop two-column: photos | body. 480 + 30 + 820 = 1330 fits inside the
   raised cap; beyond that the space is the readability cap doing its job. */
@container htpane (min-width: 1000px) {
    .flight-detail.ht-detail.ht-detail--cols {
        max-width: 1360px;           /* was 820 */
        flex-wrap: nowrap;           /* photos and body never wrap */
        align-items: flex-start;
    }
    .ht-detail--cols > .fd-section.ht-photos {
        flex: 0 0 480px;             /* was 1 0 100% */
        max-width: 480px;
    }
    .ht-detail--cols > .ht-detail-body {
        flex: 1 1 auto;
        max-width: 820px;            /* the readability cap, now on the text */
    }

    /* ---- the photo strip fits the photo column in two-column mode -------------
       (DESIGN, agent-lab/docs/design/2026-09-24-hotel-panel-two-column/prices/
       prices-delta.css (1); owner GO 2026-09-25.) A LATENT #480 DEFECT: the
       shipped strip is 5 x 112px + 4 x 6px gaps = 584px inside the 480px photo
       column, so the fifth thumb reached ~74px into the body. It only stayed
       invisible while the body ended above the strip; a taller body (ratings,
       sentiment, getting-around, a longer price list) drew over it. In two-column
       mode the five thumbs now SHARE the column (each at most the shipped 112px,
       same 4:3 shape), so the "one slot per image, never an empty slot" contract
       holds with no pan. Gated to the same pane query as the two-column layout:
       below it (and on the phone) the shipped 112px panning strip is unchanged.
       `.ht-detail--cols .ht-photo-thumb` (0,2,0) outranks the shipped
       `.ht-photo-thumb` (0,1,0) by specificity, not by order. */
    .ht-detail--cols .ht-photo-strip { max-width: 480px; }
    .ht-detail--cols .ht-photo-thumb {
        flex: 1 1 0;
        min-width: 0;
        width: auto;
        height: auto;
        max-width: 112px;
        aspect-ratio: 4 / 3;
    }
    .ht-detail--cols .ht-photo-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
}

/* phone: the existing stack block must reach INTO the body. Same weights it
   already uses for the top-level sections. */
@media (max-width: 700px) {
    .ht-detail .ht-detail-body {
        flex-direction: column;
        flex-wrap: nowrap;
        width: 100%;
        max-width: calc(100vw - 56px);
        gap: 12px;
    }
    .ht-detail .ht-detail-body > .fd-section,
    .ht-detail .ht-detail-body > .fd-actions {
        width: 100%;
        max-width: calc(100vw - 56px);
        min-width: 0;
    }
    .ht-detail .ht-detail-body > .fd-section.ht-amen-full { max-width: calc(100vw - 56px); }
    .ht-detail .ht-detail-body .ht-amenities { max-width: calc(100vw - 56px); }
}

/* ---- "Prices across sites" v2: decision-led, ~10 visible, all reachable ---
   (DESIGN brief agent-lab/docs/design/2026-09-25-hotel-prices-across-sites-v2-
   brief.md, owner GO 2026-09-25; lifted from its prices-delta.css. Its
   section (1), the photo-strip fix, shipped in #486 inside the two-column
   @container block above.) Additive: reuses the shipped
   .ht-ota line grammar and adds a badge, a muted delta, a group hairline, a
   text fold, a hint line, and the actions' new home at the foot of the PHOTOS
   column. Kept at the END of the sheet: `.ht-ota-price` below overrides its
   shipped base at equal weight, and the phone block's `.ht-ota-badge` reset
   must follow the badge's own base rule (the cascade trap; the order is
   pinned in tests/hotel-prices-v2.test.js). Every class here exists only in
   the hotel panel; the flight panel is untouched. */

/* badges: amenity register (muted hairline), NEVER the green win tint —
   "Book direct" / "Cheapest" / "Cheapest refundable" are facts, not verdicts */
.ht-ota-badge {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 6px;
    border: 1px solid var(--aw-hairline-strong);
    border-radius: 999px;
    font-family: var(--aw-font-sans, inherit);
    font-size: 0.62rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--aw-muted);
    vertical-align: middle;
    white-space: nowrap;
}
/* honest arithmetic on two shown numbers, muted, rides with the price */
.ht-ota-delta {
    display: block;
    font-size: 0.68rem;
    color: var(--aw-muted);
    font-weight: 400;
    text-align: right;
}
/* the price cell may now carry a delta under the figure */
.ht-ota-price { display: flex; flex-direction: column; align-items: flex-end; }
/* hairline between the decision rows and the major sites */
.ht-ota-group {
    height: 1px;
    background: var(--aw-hairline);
    margin: 4px 0;
}
/* the fold: a text control, not a button; count is honest and live */
.ht-ota-more {
    align-self: flex-start;
    background: none;
    border: 0;
    padding: 2px 0;
    font-family: var(--aw-font-mono);
    font-size: 0.74rem;
    color: var(--aw-accent);
    cursor: pointer;
}
.ht-ota-more:hover { text-decoration: underline; }
.ht-ota-rest[hidden] { display: none; }
/* muted one-line hint under the Book-direct row: a hedged general fact */
.ht-ota-hint { font-size: 0.68rem; color: var(--aw-muted); font-family: var(--aw-font-sans, inherit); }

/* actions move to the foot of the photo column (owner's layout call): their
   own line under the strip, left-aligned within the 480px column. The base
   .fd-actions sets margin-left:auto for the body; reset it here. When there
   are no photos the actions stay at the body's bottom (today's position). */
.ht-detail .ht-photos > .fd-actions {
    margin-left: 0;
    margin-top: 10px;
    justify-content: flex-start;
    flex-wrap: wrap;
}

/* phone: a long site name + tag + badges must WRAP, never clip */
@media (max-width: 700px) {
    .ht-ota-name { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; min-width: 0; }
    .ht-ota-badge { margin-left: 0; }
    /* The RATE stays a right-aligned column on the entry's FIRST line; the
       name, tags and badges wrap beneath it inside their own column (DESIGN
       amendment 2 to the prices v2 brief, 2026-09-25). The shipped phone rule
       `.ht-ota { flex-wrap: wrap; }` (the #480 block above) let the whole ROW
       wrap: with space-between and one item on the wrapped line, each rate
       dropped flush LEFT onto its own line, directly above the NEXT site's
       name, so it read as that site's price. Same principle as
       `.ht-nearby-line` above: place and figure stay one scannable pair.
       Equal specificity (0,1,0), so this must come after that rule. */
    .ht-ota { flex-wrap: nowrap; align-items: flex-start; }
    .ht-ota-name { flex: 1 1 auto; white-space: normal; }
    .ht-ota-price { flex: 0 0 auto; }
    /* `white-space: normal` on the name: in the phone table cell the row
       computes `nowrap` (inherited), so a long site name ("Four Seasons Hotel
       New York Downtown ↗") could not wrap inside its column and ran UNDER the
       rate (measured in the real engine at 375). The tag and badge keep their
       own `nowrap`, so only the name text wraps, never a pill. */
}
