﻿/* ============================================================================
   HotelTRS price grid

   The file .steering/design-system.md names as the proof case. Its <style>
   block was only 61 lines; the real debt was 84 inline styles in the markup and
   27 CSS strings in PriceGrid.razor.cs, where thirteen members built stylesheets
   out of string concatenation:

       protected string StickyCellLeftStyle =>
           "position: sticky; left: 0; z-index: 1; min-width: 200px; " +
           "background-color: #ffffff; border-right: 1px solid #dee2e6;";

   consumed as Style="@StickyCellLeftStyle" eleven times. Moving CSS into a
   .razor.cs was a regression that scored as an improvement until the scanner
   learned to read code-behind files, and this is where it had already happened.

   Those thirteen members are now class builders. What stays inline is the two
   editor popovers' left/top, which are computed from a mouse position and
   cannot be a class.

   ----------------------------------------------------------------------------
   SPECIFICITY

   Bootstrap paints every table cell with

       .table > :not(caption) > * > * { background-color: var(--bs-table-bg); }

   which scores (0,1,1). A bare .pg-cell--closed scores (0,1,0) and would lose,
   which is why every tint below is written as td.pg-... or th.pg-... - (0,1,1),
   a tie that pricing.css wins on document order because it is linked after
   Bootstrap. No !important is needed anywhere in this file.

   The grid sets Striped="false" Hoverable="false", so Bootstrap's inset
   box-shadow accent is transparent and does not paint over these backgrounds.
   ============================================================================ */

/* ----------------------------------------------------------------------------
   WHY :root:root ON EVERY PAINTED RULE BELOW

   dark.css is generated, is linked last, and blankets tables:

       :root[data-theme="dark"] .table > :not(caption) > * > * { background-color: var(--trs-surface); ... }   (0,3,1)
       :root[data-theme="dark"] .table > thead > tr > *        { background-color: var(--trs-fill); ... }      (0,3,2)

   A plain td.pg-cell--closed scores (0,2,1) and loses to both, so in dark mode
   every state tint on this grid was repainted flat surface - found in the
   browser, not by reading. Doubling :root costs nothing at runtime, applies in
   all three theme states (including "system", where no data-theme attribute
   exists), and takes these to (0,4,1), which outranks both.

   Rules that only set geometry are left alone - nothing competes for those.
   ---------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------------
   SCROLL FRAME
   ---------------------------------------------------------------------------- */

/* THE HEIGHT IS INHERITED, NOT CALCULATED.

   `max-height: calc(100vh - 475px)` guessed at the height of everything above
   the grid - title, tabs, toolbar, filter chips, legend - and guessed low.
   MEASURED at 1222x711: the reserve handed the grid 236px in a space with room
   for about 85, so main.b-layout-content overflowed by 151px and the screen
   carried a page scrollbar AND the grid's own at the same time. It needed to
   subtract about 626, which is another way of saying no number belongs here.

   The chain measures itself instead: main gives .content a definite height,
   each box between it and the grid passes it down, and the grid takes what is
   left. Same shape as .content:has(.wd-board) in workdays.css and
   .trs-avail-wrapper--fill in availability.css - this is the third page with
   the same bug, so treat a calc(100vh - N) on an in-page scroller as wrong on
   sight.

   :has() rather than named selectors because the markup between .content and
   the grid is two Blazorise LoadingIndicator wrappers and TrsPageShell's own
   .trs-page, none of which this file should be naming. */
.content:has(.price-grid-scroll) {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.content:has(.price-grid-scroll) *:has(.price-grid-scroll) {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

.price-grid-scroll {
    overflow: auto;
    max-width: 100%;
    flex: 1 1 auto;
    min-height: 0;
    border: 1px solid var(--trs-border);
    border-radius: var(--trs-radius-sm);
}

.price-grid-table {
    margin-bottom: 0;
}

/* ----------------------------------------------------------------------------
   DENSITY

   The grid is a hand-built <Table>, not a TrsDataGrid, so it never picked up
   the density tokens - components.css wires those through .trs-grid, which this
   does not carry. On the densest screen in the app that was the wrong way round.

   Only compact is wired up. Adopting --trs-cell-pad wholesale would also change
   comfortable, from Bootstrap's 8px to 8px/12px, and widening thirty-odd date
   columns to make a grid roomier is the opposite of what anyone picking
   comfortable wants. So compact tightens the rows - 8px to 4px vertically, the
   horizontal padding unchanged - and comfortable is left exactly as it ships.

   (0,3,1) against Bootstrap's `.table > :not(caption) > * > *` at (0,1,1).
   ---------------------------------------------------------------------------- */

:root[data-density="compact"] .price-grid-table > :not(caption) > * > * {
    padding: var(--trs-cell-pad);
}

/* ----------------------------------------------------------------------------
   STICKY HEADER

   Was #343a40 with #495057 weekends - a dark slate bar hardcoded into the
   markup, which is the mirror image of the defect the ops screen had: that one
   was pinned light, this one is pinned dark, and neither followed the theme.

   The ground is NOT set here. app.css already fills every table header in the
   application:

       .table > thead > :last-child > * {
           background: var(--trs-fill);
           color: var(--trs-on-primary);
       }

   This file first tried to paint the header var(--trs-surface) instead, which
   was a mistake found in the browser: that rule ties with app.css's on
   specificity and wins on load order, so the ground went white - but it set no
   colour, so the text stayed var(--trs-on-primary) and the whole header row
   went white-on-white. Letting the app-wide fill through is both the fix and
   the more consistent answer: this is a real <table>, and every other one in
   the app has a eucalyptus header.
   ---------------------------------------------------------------------------- */

.price-grid-table th.pg-head-label,
.price-grid-table th.pg-head-type,
.price-grid-table th.pg-head-day {
    position: sticky;
    top: 0;
    z-index: 3;
    /* The grid scrolls in both axes; without this the sticky cells repaint a
       frame behind the rest of the table. */
    will-change: transform;
}

.price-grid-table th.pg-head-label {
    left: 0;
    min-width: 200px;
    max-width: 240px;
}

.price-grid-table th.pg-head-type {
    left: 200px;
    min-width: 60px;
    max-width: 80px;
}

.price-grid-table th.pg-head-day {
    z-index: 1;
    min-width: 48px;
    max-width: 60px;
    text-align: center;
}

/* A shade deeper than the header fill, so a weekend reads as a weekend without
   leaving the header's own colour. */
:root:root .price-grid-table th.pg-head-day--weekend {
    background-color: var(--trs-fill-hover);
}

.pg-head-day-name {
    font-size: var(--trs-text-xs);
    font-weight: var(--trs-weight-medium);
}

.pg-head-day-num {
    font-size: var(--trs-text-md);
}

/* ----------------------------------------------------------------------------
   TODAY COLUMN

   A wayfinding band down the whole grid. It deliberately takes no state hue -
   every one of those is spoken for by a cell state below, and a column marker
   that looked like "closed" or "override" would be read as one.
   ---------------------------------------------------------------------------- */

:root:root .price-grid-table td.price-grid-today-col {
    background-color: var(--trs-today-band);
    border-left: 2px solid var(--trs-fill);
    border-right: 2px solid var(--trs-fill);
}

/* The header cell has to differ from the fill around it, so it takes the
   darkest primary step rather than the fill itself. */
:root:root .price-grid-table th.price-grid-today-col {
    background-color: var(--trs-p-800);
    border-left: 2px solid var(--trs-p-800);
    border-right: 2px solid var(--trs-p-800);
}

/* ----------------------------------------------------------------------------
   STICKY ROW LABELS
   ---------------------------------------------------------------------------- */

:root:root .price-grid-table td.pg-sticky-label,
:root:root .price-grid-table td.pg-sticky-type {
    position: sticky;
    z-index: 1;
    background-color: var(--trs-surface);
    border-right: 1px solid var(--trs-border);
}

.price-grid-table td.pg-sticky-label {
    left: 0;
    min-width: 200px;
    max-width: 240px;
}

.price-grid-table td.pg-sticky-type {
    left: 200px;
    min-width: 60px;
    max-width: 80px;
}

.price-grid-table td.pg-row-label {
    cursor: pointer;
}

/* Indented to sit under the plan sub-header, which itself nests under the room
   type - the three-level hierarchy the Booking.com extranet uses. */
.price-grid-table td.pg-row-label--indent {
    padding-left: 1.9rem;
}

:root:root .price-grid-table td.pg-row-label--selected {
    background-color: var(--trs-info-bg);
    font-weight: var(--trs-weight-medium);
}

/* ----------------------------------------------------------------------------
   CELL STATES

   Six tints, each one a token pair that was already audited for contrast:

     disabled   the date cannot be edited (reserved, or a locked rate)
     closed     the date is closed for sale - the strongest signal here
     override   a value typed into this cell, differing from the default
     rule       a value a recurring rule produced, which is NOT cleared here -
                tint plus an inset bar, because the tints alone are too close
     nocap      ambient wash: nothing left to sell that day, mirroring the
                Booking.com extranet. Explicit state outranks it, which is why
                it is the soft token and not the full danger ground.
   ---------------------------------------------------------------------------- */

.price-grid-table td.pg-cell {
    min-width: 48px;
    max-width: 60px;
    user-select: none;
    cursor: pointer;
}

:root:root .price-grid-table td.pg-cell--disabled {
    background-color: var(--trs-surface-sunken);
    color: var(--trs-text-muted);
    cursor: not-allowed;
}

:root:root .price-grid-table td.pg-cell--closed {
    background-color: var(--trs-danger-bg);
    color: var(--trs-danger);
    font-weight: var(--trs-weight-bold);
}

:root:root .price-grid-table td.pg-cell--override {
    background-color: var(--trs-success-bg);
    color: var(--trs-success);
    font-weight: var(--trs-weight-medium);
}

/* The tint alone is not enough here. Before the rebrand these two were a green
   and a blue; on a monochrome palette the nearest pair available measures 1.066
   against each other, which is no difference at all - measured in the browser
   on the Booking.com grid, where both appear in the same column. The
   distinction is operational, not decorative: an override is cleared from this
   cell, a rule-produced value is not. So a rule cell also carries a bar, which
   is a channel the palette cannot take away. */
:root:root .price-grid-table td.pg-cell--rule {
    background-color: var(--trs-info-bg);
    color: var(--trs-info);
    font-weight: var(--trs-weight-medium);
    box-shadow: inset 0 -3px 0 var(--trs-p-400);
}

:root:root .price-grid-table td.pg-cell--nocap {
    background-color: var(--trs-danger-bg-soft);
}

/* Read-only informational cell - the sold/derived figures under a rate plan. */
:root:root .price-grid-table td.pg-cell--readonly {
    min-width: 48px;
    max-width: 60px;
    text-align: center;
    color: var(--trs-text-muted);
}

/* Selection outlines sit on top of whatever tint the cell already has. */
.price-grid-table td.pg-cell--selected {
    outline: 2px solid var(--trs-focus);
    outline-offset: -2px;
}

:root:root .price-grid-table td.pg-cell--pending {
    outline: 2px dashed var(--trs-focus);
    outline-offset: -2px;
    background-color: var(--trs-p-50);
}

/* ----------------------------------------------------------------------------
   UNIT-STATUS RUN

   One cell spanning a run of consecutive days with the same availability.
   max-width:0 with ellipsis is what stops a long label stretching the column
   the whole grid is built on.
   ---------------------------------------------------------------------------- */

.price-grid-table td.pg-run {
    font-size: 0.75em;
    padding: 0.1rem var(--trs-space-1);
    max-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

:root:root .price-grid-table td.pg-run--available {
    background-color: var(--trs-success-bg);
    color: var(--trs-success);
}

:root:root .price-grid-table td.pg-run--full {
    background-color: var(--trs-danger-bg);
    color: var(--trs-danger);
}

/* ----------------------------------------------------------------------------
   INVENTORY ROW

   Amber is this row's identity, not a state: it separates the physical-count
   row from the price rows below it at a glance. The stale case - a live
   override sitting ABOVE the physical count - borrows the closed tint, because
   it is the same kind of "this is wrong, look here" signal.
   ---------------------------------------------------------------------------- */

:root:root .price-grid-table td.pg-cell--inventory {
    background-color: var(--trs-warning-bg-soft);
}

:root:root .price-grid-table td.pg-cell--inventory-override {
    background-color: var(--trs-warning-bg);
    color: var(--trs-warning);
    font-weight: var(--trs-weight-medium);
}

:root:root .price-grid-table td.pg-row-label--inventory {
    background-color: var(--trs-warning-bg-soft);
}

.price-grid-table td.pg-row-label--inventory-selected {
    outline: 2px solid var(--trs-focus);
}

/* Booking.com is being sent a different number than a direct booking could take
   that night (closed on Booking.com, a Rooms-to-sell limit, or the type closed
   for direct). A bar, not a tint: the row's amber identity and the override and
   stale tints already use the fill, so a fourth tint would not read. */
:root:root .price-grid-table td.pg-cell--channel-diff {
    box-shadow: inset 0 -4px 0 var(--trs-info);
    color: var(--trs-info);
    font-weight: var(--trs-weight-bold);
}

/* ----------------------------------------------------------------------------
   GROUPING BANDS
   ---------------------------------------------------------------------------- */

:root:root .price-grid-table td.pg-band-roomtype {
    background-color: var(--trs-surface-sunken);
    color: var(--trs-text);
    font-weight: var(--trs-weight-bold);
    font-size: 1.06em;
    padding: 0.7rem var(--trs-space-3);
}

.price-grid-table tr.pg-band-roomtype-row {
    border-top: 3px solid var(--trs-border-strong);
}

.price-grid-table td.pg-band-rateplan {
    padding: 0.45rem var(--trs-space-3) 0.45rem 1.2rem;
    border-top: 1px solid var(--trs-border);
    /* The inset rule is the plan's left edge marker - a border would be eaten
       by the sticky cell that scrolls over it. */
    box-shadow: inset 4px 0 0 var(--trs-p-300);
}

.pg-band-rateplan-name {
    font-weight: var(--trs-weight-medium);
    color: var(--trs-text-primary);
}

.price-grid-table td.pg-band-note {
    padding: var(--trs-space-1) var(--trs-space-3);
}

.price-grid-table td.pg-band-subhead {
    padding: 0.6rem var(--trs-space-3);
    font-weight: var(--trs-weight-medium);
}

:root:root .price-grid-table tr.pg-band-clickable {
    background-color: var(--trs-surface-sunken);
    cursor: pointer;
}

/* ----------------------------------------------------------------------------
   META CHIP

   DefaultRates carried a byte-identical copy under the name .rate-meta-chip,
   with a comment saying it was "kept local here too rather than shared,
   matching how each page already owns its own inline styles". That is the
   habit this whole phase exists to end, so there is now one chip.
   ---------------------------------------------------------------------------- */

.pg-meta-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem var(--trs-space-2);
    margin: 0 0.4rem 0.4rem 0;
    border-radius: var(--trs-radius-full);
    background: var(--trs-surface-sunken);
    color: var(--trs-text-secondary);
    font-size: var(--trs-text-xs);
    font-weight: var(--trs-weight-medium);
    white-space: nowrap;
}

/* A bound OTA rate the channel reports as switched off. */
.pg-meta-chip--inactive {
    background: var(--trs-danger-bg);
    color: var(--trs-danger);
}

/* ----------------------------------------------------------------------------
   LEGEND
   ---------------------------------------------------------------------------- */

.pg-swatch {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    margin-right: var(--trs-space-1);
    vertical-align: middle;
}

.pg-swatch--nocap {
    background-color: var(--trs-danger-bg-soft);
}

.pg-swatch--inventory-override {
    background-color: var(--trs-warning-bg);
}

.pg-swatch--closed {
    background-color: var(--trs-danger-bg);
}

.pg-swatch--channel-diff {
    background-color: var(--trs-warning-bg-soft);
    box-shadow: inset 0 -4px 0 var(--trs-info);
}

.pg-swatch--disabled {
    background-color: var(--trs-surface-sunken);
    border: 1px solid var(--trs-border-strong);
}

.pg-legend-badge {
    margin-right: var(--trs-space-1);
}

/* ----------------------------------------------------------------------------
   TOOLBAR AND EMPTY STATES
   ---------------------------------------------------------------------------- */

.pg-field-xl { min-width: 220px; }
.pg-field-lg { min-width: 200px; }
.pg-field-md { min-width: 140px; }
.pg-field-sm { min-width: 110px; }

.pg-count-badge {
    font-size: var(--trs-text-md);
    padding: var(--trs-space-2) var(--trs-space-3);
}

.pg-empty-icon {
    font-size: 2.5rem;
}

.pg-empty-icon--sm {
    font-size: 2rem;
}

/* An icon that only explains something - the cursor says so. */
.pg-help-icon {
    cursor: help;
    font-size: 1rem;
}

.pg-hint-icon {
    font-size: var(--trs-text-xs);
}

/* ----------------------------------------------------------------------------
   TIP AND CELL-EDITOR POPOVERS

   Only the popovers' left/top stay inline - they are a mouse position.
   ---------------------------------------------------------------------------- */

.price-grid-tip-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1080;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, .45);
}

.price-grid-tip-card {
    position: relative;
    width: 90%;
    max-width: 520px;
    padding: var(--trs-space-6) 1.75rem;
    background: var(--trs-surface);
    border-radius: var(--trs-radius-md);
    box-shadow: var(--trs-elevation-2);
}

.price-grid-tip-close {
    position: absolute;
    top: var(--trs-space-2);
    right: var(--trs-space-2);
}

.price-grid-cell-editor-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1090;
    background: transparent;
}

.price-grid-cell-editor {
    position: fixed;
    z-index: 1091;
    min-width: 180px;
    max-width: 240px;
    padding: var(--trs-space-3);
    background: var(--trs-surface);
    border: 1px solid var(--trs-border-strong);
    border-radius: var(--trs-radius-md);
    box-shadow: var(--trs-elevation-2);
}

/* ----------------------------------------------------------------------------
   DEFAULT RATES

   app.css clips every .b-datagrid cell with overflow:hidden + ellipsis for the
   fixed-layout grid - fine for short text, but it clips the channel-status
   badge's rounded pill instead of just truncating text. Let this one cell size
   to its wrapped content instead.
   ---------------------------------------------------------------------------- */

.room-types-grid td:has(.ota-health-cell) {
    overflow: visible;
    text-overflow: unset;
}

.ota-health-cell {
    display: inline-block;
    max-width: 100%;
}

/* Bootstrap's .badge hard-sets white-space:nowrap, so the pill never wraps and
   grows past the column. Let long status text wrap instead. */
.ota-health-cell .badge {
    white-space: normal;
    word-break: break-word;
}
