﻿html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: #0071c1;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.loading-progress {
    position: absolute;
    display: block;
    width: 8rem;
    height: 8rem;
    inset: 20vh 0 auto 0;
    margin: 0 auto 0 auto;
}

    .loading-progress circle {
        fill: none;
        stroke: #e0e0e0;
        stroke-width: 0.6rem;
        transform-origin: 50% 50%;
        transform: rotate(-90deg);
    }

        .loading-progress circle:last-child {
            stroke: #1b6ec2;
            stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
            transition: stroke-dasharray 0.05s ease-in-out;
        }

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: bold;
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}

    .loading-progress-text:after {
        content: var(--blazor-load-percentage-text, "Loading");
    }

code {
    color: #c02d76;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
/* ---------------------------------------------------------------------------
   Debertz table — composition ported from GameTableView.xaml.

   Structure follows the XAML: an outer Padding="14,12,14,16" grid with
   RowDefinitions="Auto,*,Auto", and inside row 1 a further Auto,*,Auto for the
   three player areas. Card positions are NOT computed here - CardFanLayout in
   the shared assembly emits per-card transforms, so this file only supplies the
   containers those transforms are measured from.
   --------------------------------------------------------------------------- */

:root {
    --nav-height: 82px;
    /* Installed on iOS the page runs full-bleed under the home indicator, so
       the fixed bar and the scrolling content both need to clear it. Zero in a
       browser tab and on every other platform. */
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --felt: #17663F;
    --felt-dark: #0B3D24;
    --panel: #F6EED8;
    --panel-stroke: #ABC4A2;
    --ink: #365445;
    --accent: #5D9B42;
}

html, body { height: 100%; margin: 0; }

.app-shell {
    min-height: 100dvh;
    background: radial-gradient(ellipse at 50% 38%, #1C7A4B 0%, var(--felt) 45%, var(--felt-dark) 100%);
    color: #F8F1DA;
    font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}

.table {
    display: grid;
    grid-template-rows: auto auto 1fr auto;   /* bar, north, centre, south */
    row-gap: 6px;
    height: 100dvh;
    padding: 12px 14px 16px;
    box-sizing: border-box;
    overflow: hidden;
}
.table--busy { cursor: progress; }

/* Top bar: icons start-aligned, contract badge end-aligned. */
.table__bar { position: relative; display: flex; align-items: flex-start; gap: 2px; }
.icon { background: none; border: 0; padding: 9px; border-radius: 18px;
        width: 44px; height: 40px; cursor: pointer; }
.icon img { width: 100%; height: 100%; object-fit: contain; }
.icon:hover { background: rgb(255 255 255 / 0.1); }

.table__contract {
    position: absolute; right: 0; top: 0;
    width: 44px; height: 40px; padding: 8px; box-sizing: border-box;
    background: #DDF6EED8; border: 1px solid var(--panel-stroke); border-radius: 8px;
    /* MAUI sets Opacity to 0 on desktop and 1 on phone. */
    opacity: 0;
}
.table__contract img { width: 100%; height: 100%; object-fit: contain; }
.table__contract-owner { position: absolute; right: 2px; bottom: 2px;
                         width: 16px !important; height: 16px !important; }

/* Player rows. Each is a positioning context for absolutely placed cards. */
.table__row { position: relative; }
.table__row--north  { min-height: 178px; }
.table__row--center { min-height: 280px; display: grid; grid-template-columns: 220px 1fr 120px;
                      align-items: center; align-content: center; }
.table__row--south  { min-height: 238px; align-self: end; }

/* grid-row: 1 is required, not decorative: .table__trick spans 1 / -1 in row 1,
   so without it the stock auto-places into an implicit second row and sits
   ~90px below centre. */
.table__stock { grid-column: 1; grid-row: 1; justify-self: start; align-self: center; position: relative;
                width: 185px; height: 160px; margin-left: 6px;
                transition: transform 180ms ease; }
.table__trick { grid-column: 1 / -1; grid-row: 1; justify-self: center;
                position: relative; width: 260px; height: 180px; }
/* During the close the overlay copies stand in for these; showing both drew a
   face-down copy on top of a still-face-up original. */
.table__trick.is-closing { visibility: hidden; }

/* Cards ---------------------------------------------------------------------
   Every fan is a positioning context; CardFanLayout supplies offsets from the
   centre, so each card is pinned there and translated out. */

.cards { position: relative; width: 100%; height: var(--fan-height); }
.cards--arc { height: var(--fan-height); }

.card {
    position: absolute; left: 50%; top: 50%;
    width: var(--card-w); height: var(--card-h);
    padding: 0; border: 0; background: none; cursor: default;
    transition: filter 120ms ease;
}
.card img { width: 100%; height: 100%; display: block; }
/* touch-action:none so an upward flick reaches pointerup as a swipe. Without it
   the browser can claim the vertical drag as a scroll and deliver pointercancel
   instead, which loses the gesture on exactly the devices it exists for. */
.card--playable { cursor: pointer; touch-action: none; }
.card--playable:hover, .card--playable:focus-visible { filter: brightness(1.1); }
.card--dimmed { filter: brightness(0.6) saturate(0.55); }

/* Arc fans anchor at the card's bottom edge, so pin the row to its top. */
.cards--arc .card { top: 0; }

/* Floating choices ---------------------------------------------------------- */

.choices { grid-column: 1 / -1; grid-row: 1; justify-self: center; align-self: center;
           position: relative; z-index: 5;
           display: flex; flex-direction: column; align-items: center; gap: 40px; }
/* FlexLayout Direction="Row" JustifyContent="Center" Wrap="NoWrap". */
.choices__group { display: flex; flex-direction: row; justify-content: center;
                  align-items: center; flex-wrap: nowrap; gap: 10px; }

.action { display: inline-flex; align-items: center; justify-content: center; gap: 8px;
          width: 112px; height: 48px; box-sizing: border-box; cursor: pointer;
          background: var(--panel); color: #102016;
          border: 1px solid var(--panel-stroke); border-radius: 8px;
          padding: 6px 12px; font-size: 13px; font-weight: 700;
          box-shadow: 0 2px 6px rgb(0 0 0 / 0.25); }
.action:hover { filter: brightness(1.04); }
.action img { width: 26px; height: 26px; }

/* YesNoOverlayView / round-summary buttons. The primary action is LIGHT green
   with dark text, not dark green with light text - an earlier guess made the
   label almost the same value as its background and effectively unreadable. */
.action--yes { background: #CFE6BB; color: #102016; border-color: #7C9876; }
.action--no  { background: #F0E9D6; color: #102016; border-color: #9B8F72; }
.overlay__answers .action { width: 104px; height: 42px; }

/* Yes/no confirmations DO dim the table - they interrupt play. */
.overlay { position: fixed; inset: 0; display: grid; place-items: center;
           background: rgb(0 0 0 / 0.45); z-index: 10; }
.overlay__panel { background: var(--panel); color: var(--ink); border-radius: 1rem;
                  padding: 1.25rem; min-width: min(90vw, 26rem); max-width: 90vw;
                  box-shadow: 0 1.5rem 3rem rgb(0 0 0 / 0.35); }
.overlay__title, .overlay__question { margin-top: 0; text-align: center; }
.overlay__cards { position: relative; height: 130px; margin: 0.75rem 0; }
.overlay__answers { display: flex; gap: 0.75rem; justify-content: center; margin-top: 1rem; }

.balloon { position: absolute; left: 50%; translate: -50% 0; top: 0;
           display: flex; align-items: center; gap: 0.4rem;
           background: var(--panel); color: var(--ink);
           border-radius: 0.75rem; padding: 0.3rem 0.6rem; z-index: 4; }
.balloon img { width: 1.1rem; height: 1.1rem; }

/* Round summary ------------------------------------------------------------- */

.summary__row { display: grid; grid-template-columns: 8rem 1fr 3rem; gap: 0.4rem 0.75rem;
                align-items: center; margin-bottom: 0.75rem; }
.summary__who { display: flex; align-items: center; gap: 0.4rem; }
.summary__who img { width: 1.25rem; height: 1.25rem; }
.summary__bar { height: 0.6rem; border-radius: 999px; background: #D8E2D3; overflow: hidden; }
.summary__bar-fill { height: 100%; background: var(--accent); }
.summary__total { text-align: right; font-weight: 700; }
.summary__formula { grid-column: 1 / -1; margin: 0; font-size: 0.8rem; opacity: 0.75; }
.summary__continue { width: 154px; height: 46px; margin: 0.5rem auto 0;
                     display: flex; align-items: center; justify-content: center; gap: 10px; }
.summary__continue img { width: 22px; height: 22px; }

/* Generic pages ------------------------------------------------------------- */

/* Info / statistics / settings share GameInfoPage's two-row layout: a scrolling
   centred column over a fixed 82px navigation bar. Pinning the bar means the
   tiles hold the same position across all three pages instead of jumping with
   content height. Widths follow the XAML: 620 for info, 560 for the others. */

.page { max-width: 620px; margin: 0 auto;
        padding: 54px 34px calc(24px + var(--nav-height) + var(--safe-bottom)); }
.stats, .settings { max-width: 560px; }
.page__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1.5rem; }

@media (prefers-reduced-motion: reduce) { .card { transition: none; } }

/* Info + settings pages ------------------------------------------------------ */

.page { color: #F8F1DA; }
.page__header h1 { margin: 0 0 0.25rem; }
.info__subtitle, .settings__status { margin: 0; opacity: 0.8; }
.info__menu { margin: 1.25rem 0 1.5rem; }

.info__scores { display: grid; gap: 0.75rem; margin-bottom: 1rem; }
/* GameInfoPage.xaml: Grid "*,Auto" for name/total, ProgressBar on its own line
   beneath, formula under that. Deliberately NOT the summary's 3-column form. */
.score-row { display: grid; gap: 6px; }
.score-row__top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.score-row__name { display: inline-flex; align-items: center; gap: 8px;
                   font-size: 18px; font-weight: 700; }
.score-row__name img { width: 20px; height: 20px; }
.score-row__total { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.score-row__bar { height: 8px; border-radius: 999px;
                  background: rgb(255 255 255 / 0.22); overflow: hidden; }
.score-row__bar-fill { height: 100%; background: var(--accent); }
.score-row__formula { margin: 0; font-size: 14px; opacity: 0.75; }

/* Border Padding=18 panels. Each is headed by a plain 13px caption, matching
   the Label the XAML puts at the top of every section. */
.info__section { background: rgb(0 0 0 / 0.18); border-radius: 0.75rem;
                 padding: 0.9rem 1.1rem; margin-bottom: 0.9rem; }
.info__caption { margin: 0 0 0.6rem; font-size: 13px; font-weight: 400;
                 opacity: 0.75; letter-spacing: 0.02em; }
.info__outcome { margin: 0 0 0.9rem; font-size: 18px; font-weight: 700; text-align: center; }
.info__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.info__who { width: 20px; height: 20px; flex: none; }
.info__suit { width: 18px; height: 18px; flex: none; }

/* Bidding: FlexLayout Row/Wrap of Border chips, Margin="0,0,8,8". */
.info__chips { display: flex; flex-wrap: wrap; gap: 8px; }
.info__chip { display: inline-flex; align-items: center; gap: 6px;
              padding: 6px 8px; border-radius: 0.4rem; font-size: 14px;
              border: 1px solid rgb(255 255 255 / 0.20);
              background: rgb(255 255 255 / 0.07); }
.info__chip .info__who { width: 18px; height: 18px; }

/* Announces: Grid "Auto,Auto,*,Auto" ColumnSpacing=8. */
/* Dark text: the row background comes from the view model and is light, while
   .page sets a light colour for the felt behind it. Without this the announce
   name and its score render light-on-light and vanish. */
.info__announce { display: grid; grid-template-columns: auto auto 1fr auto;
                  align-items: center; gap: 8px; padding: 6px 8px;
                  border-radius: 0.4rem; font-size: 14px; color: #132218; }
.info__announce-cards { display: inline-flex; }
.info__announce-cards img { width: 38px; height: 58px; }
.info__announce-cards img + img { margin-left: -12px; }   /* HStack Spacing="-12" */
.info__announce-score { font-weight: 700; font-variant-numeric: tabular-nums; }

/* Tricks: Grid "Auto,Auto,Auto,Auto,Auto,Auto" ColumnSpacing=10 with explicit
   widths 22/34/24/34/34/34. The two scores are separate right-aligned columns;
   the south one carries TranslationX="-10", which closes the column gap so the
   pair reads as one figure. */
.info__trick, .info__totals { display: grid; align-items: center; gap: 10px;
                              grid-template-columns: 22px 34px 24px 34px 34px 34px; }
.info__trick-title { font-size: 13px; opacity: 0.75; }
.info__trick-card { width: 34px; height: 52px; }
.info__trick-card img { width: 34px; height: 52px; display: block; }
.info__arrow { font-size: 22px; text-align: center; opacity: 0.75; line-height: 1; }
.info__trick-score { font-size: 14px; text-align: right; font-variant-numeric: tabular-nums; }
.info__trick-score--south { transform: translateX(-10px); }

.info__totals { margin-top: 8px; padding-top: 8px;
                border-top: 1px solid rgb(255 255 255 / 0.18); }
.info__total { position: relative; font-size: 14px; font-weight: 700;
               text-align: right; font-variant-numeric: tabular-nums; }
.info__total img { position: absolute; top: 50%; width: 18px; height: 18px; }
.info__total--north img { left: -4px; transform: translateY(-50%); }
.info__total--south { transform: translateX(-10px); }
.info__total--south img { right: 0; transform: translate(22px, -50%); }

.info__rules { margin: 0.5rem 0 0; font-size: 16px; }   /* Label FontSize="16" */

.settings__group { border: 1px solid rgb(255 255 255 / 0.18); border-radius: 0.75rem;
                   padding: 0.75rem 1rem; margin-bottom: 0.75rem; }
.settings__group legend { padding: 0 0.4rem; font-weight: 600; }
.settings__choices { display: flex; flex-wrap: wrap; gap: 1rem; }
.settings__choices label, .settings__toggle { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }
.settings__field { display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.6rem; max-width: 14rem; }
.settings__field input { padding: 0.4rem 0.6rem; border-radius: 0.4rem; border: 1px solid rgb(0 0 0 / 0.2); }
.settings__storage { margin: 0; font-variant-numeric: tabular-nums; }
.settings__storage.is-warning { color: #F6C453; }
.settings__hint { font-size: 0.85rem; opacity: 0.8; margin: 0.4rem 0 0; }
/* user-select so a tester can copy the identifier into a report. */
.settings__build { margin: 0.25rem 0 0; font-size: 0.75rem; opacity: 0.6;
                   font-variant-numeric: tabular-nums; user-select: text; }

/* Game-info page: header, tappable rules link, bottom tile bar ---------------- */


.info__rules-link { background: none; border: 0; padding: 0; cursor: pointer;
                    color: #CFE8C4; font-size: 1rem; font-weight: 600;
                    text-decoration: underline; }

/* FlexLayout HeightRequest=82, JustifyContent=SpaceAround.
   DELIBERATE DIVERGENCE (requested 2026-07-31): MAUI paints this bar solid
   #132218, which reads as heavy against the web build's lighter felt. It uses
   the same rgb(0 0 0 / 0.18) as the .info__section panels instead, so the bar
   and the content cards share one treatment. The blur keeps labels legible
   when long content scrolls beneath. */
.info__tiles { position: fixed; left: 0; right: 0; bottom: 0;
               height: calc(var(--nav-height) + var(--safe-bottom));
               padding-bottom: var(--safe-bottom);
               box-sizing: border-box;   /* keep the border inside the 82px */
               display: flex; flex-direction: row; align-items: center;
               justify-content: space-around;
               background: rgb(0 0 0 / 0.18);
               backdrop-filter: blur(10px);
               border-top: 1px solid rgb(255 255 255 / 0.10);
               z-index: 30; }
.tile { display: grid; justify-items: center; align-content: center; gap: 2px;
        width: 120px; height: 100%; cursor: pointer;
        background: none; border: 0; color: #F8F1DA; font-size: 0.85rem; font-weight: 600; }
.tile:hover { background: rgb(255 255 255 / 0.07); }
.tile img { width: 22px; height: 22px; }

/* ---------------------------------------------------------------------------
   Service-worker update prompt. Web-only; MAUI updates through the store.

   Fixed just above the navigation bar so it reads as a transient system notice
   rather than page content, and so it lands in the same place whether it is
   shown over the table or over a scrolling info page.
   --------------------------------------------------------------------------- */

.update-banner { position: fixed; z-index: 40;
                 left: 50%; transform: translateX(-50%);
                 bottom: calc(var(--nav-height) + var(--safe-bottom) + 12px);
                 width: min(560px, calc(100vw - 24px)); box-sizing: border-box;
                 display: flex; align-items: center; gap: 12px;
                 padding: 10px 12px 10px 16px;
                 background: #DDE7D3; color: #132218;
                 border: 1px solid #6A7D61; border-radius: 8px;
                 box-shadow: 0 6px 20px rgb(0 0 0 / 0.28); }
.update-banner__text { flex: 1; font-size: 14px; font-weight: 600; }
.update-banner__note { display: block; font-weight: 400; font-size: 12px; color: #486255; }
.update-banner__action { flex: none; cursor: pointer; padding: 10px 16px;
                         font-size: 14px; font-weight: 600; border-radius: 6px;
                         border: 1px solid transparent;
                         background: #176B45; color: #F8F1DA; }
.update-banner__action:disabled { opacity: 0.55; cursor: default; }

/* Statistics page: legend + donut, matching StatisticsPage.xaml -------------- */

.stats__record { margin: 0 0 1.25rem; font-weight: 600; }

.stats__body { display: grid; grid-template-columns: 1fr auto; gap: 1.5rem;
               align-items: center; min-height: 230px; }
.stats__legend { display: grid; gap: 1.25rem; }
.legend__item { display: grid; grid-template-columns: auto 1fr; gap: 0.75rem; align-items: start; }
.legend__dot { width: 16px; height: 16px; border-radius: 50%; margin-top: 0.25rem; }
.legend__item > div { display: grid; }
.legend__label { font-size: 0.9rem; opacity: 0.85; }
.legend__value { font-size: 1.6rem; font-weight: 700; line-height: 1.1; }
.legend__rate { font-size: 0.85rem; opacity: 0.75; }

.stats__chart { position: relative; display: grid; place-items: center; }
.donut { display: block; }
.stats__chart-centre { position: absolute; display: grid; justify-items: center; }
.stats__rate { font-size: 1.9rem; font-weight: 700; }
.stats__rate-label { font-size: 0.85rem; opacity: 0.8; }

/* Grid "*,*,*" ColumnSpacing=8; Button HeightRequest=46, CornerRadius=6,
   BorderColor="#17663F" BorderWidth=1. */
.stats__modes { display: grid; grid-template-columns: repeat(3, 1fr);
                gap: 8px; margin: 1.5rem 0 0; }
.stats__modes button { cursor: pointer; height: 46px; box-sizing: border-box;
                       border: 1px solid #17663F; border-radius: 6px;
                       font-size: 1rem; font-weight: 600; }
/* The dark MAUI border is invisible on the selected button's dark fill, so the
   current mode gets a light rim instead. */
.stats__modes button.is-selected { border-color: #CFE6BB; box-shadow: 0 0 0 1px #CFE6BB; }

.settings__entry { margin-top: 0.6rem; padding: 0.5rem 0.7rem; border-radius: 0.4rem;
                   border: 1px solid rgb(0 0 0 / 0.2); background: #F4F7F1; color: #132218;
                   max-width: 14rem; width: 100%; box-sizing: border-box; }

/* ---------------------------------------------------------------------------
   Phone breakpoint — the CSS half of MAUI's OnIdiom values.

   768px stands in for the Phone idiom; MAUI switches on device class, which the
   web has no equivalent for, and width is what actually decides whether the
   table fits. The C# half (CardHeight, LinearSpacingFactor, trick FanStyle,
   donut size) is in Services/ViewportService.cs, which uses the same boundary.
   --------------------------------------------------------------------------- */

@media (max-width: 767.98px) {

    /* Views/Game/TableCenterView.xaml */
    .table__row--center { min-height: 230px; grid-template-columns: 1fr; }
    .table__stock { width: 170px; height: 156px; margin-left: -36px; }

    /* Views/Game/BottomPlayerAreaView.xaml */
    .table__row--south { min-height: 176px; }

    /* GameTableView.xaml: the contract badge is Phone=1, Default=0. */
    .table__contract { opacity: 1; }

    /* GameTableView.xaml round summary: Padding 12, columns '78,*,48', spacing 8. */
    .overlay__panel { padding: 12px; }
    .summary__row { grid-template-columns: 78px 1fr 48px; gap: 0.4rem 8px; }

    /* Controls/YesNoOverlayView.xaml: fills the width, taller buttons. */
    .overlay__panel--question { min-width: 0; width: calc(100vw - 16px); margin: 0 8px; }
    .overlay__answers .action { height: 50px; }

    /* GameInfoPage.xaml: Padding 22,54,22,24 and full-width content. */
    .page { padding: 54px 22px calc(24px + var(--nav-height) + var(--safe-bottom));
            max-width: none; }

    /* SessionDialogOverlay's phone rules are NOT here. Its base rules are
       declared further down the file, and a media query carries no extra
       specificity — anything written here would lose to them on source order
       alone. They live in their own block directly after those base rules. */

    /* StatisticsPage.xaml: donut column collapses, tighter spacing. */
    .stats__body { grid-template-columns: 1fr; gap: 8px; min-height: 180px; }
    .stats__rate { font-size: 18px; }

    /* Tiles are 82 wide on phone, 120 on desktop. */
    .tile { width: 82px; }

    /* Choices stack tighter so the hand stays visible beneath them. */
    .choices { gap: 8px; }
    .action { padding: 10px 18px; }
}

/* Desktop tile width, for symmetry with the phone rule above. */


/* Cards in flight ------------------------------------------------------------
   The overlay covers the table but never takes pointer events, matching MAUI's
   InputTransparent AbsoluteLayout. Flying cards are positioned in viewport
   space because their coordinates come from getBoundingClientRect. */

.animation-overlay { position: fixed; inset: 0; pointer-events: none; z-index: 20; }
.flying-card { position: fixed; left: 0; top: 0; will-change: transform; }

.callout { position: absolute; left: 50%; top: 30%; translate: -50% 0;
           display: grid; justify-items: center; gap: 0.5rem; opacity: 0;
           background: var(--panel); color: var(--ink);
           border: 1px solid var(--panel-stroke); border-radius: 12px;
           padding: 14px 20px; box-shadow: 0 6px 20px rgb(0 0 0 / 0.35);
           pointer-events: none; z-index: 21; }
.callout__cards { display: flex; gap: 4px; }
.callout__cards img { width: 44px; aspect-ratio: 161 / 247; }

/* (3) Per-side contract badge — TopPlayerAreaView / BottomPlayerAreaView.
   54x42, right-aligned, shown on desktop (OnIdiom Phone=0, Default=1). The
   side it appears on IS the owner indication; the top-bar badge with an owner
   glyph is the phone variant. */

.side-contract { position: absolute; right: 24px; width: 54px; height: 42px;
                 padding: 8px; box-sizing: border-box;
                 background: #DDF6EED8; border: 1px solid var(--panel-stroke);
                 border-radius: 8px; }
.side-contract img { width: 100%; height: 100%; object-fit: contain; }
.side-contract--north { top: 36px; }
.side-contract--south { bottom: 26px; }

/* (5) Exchange swap layout — YesNoOverlayView's three-column grid. */

.overlay__swap { display: grid; grid-template-columns: auto auto auto;
                 gap: 12px; align-items: center; justify-content: center;
                 margin: 0.75rem 0; }
.overlay__swap-side { position: relative; width: 78px; height: 112px; }
.overlay__swap-icon { width: 42px; height: 42px; object-fit: contain; }
.overlay__cards { position: relative; width: 220px; height: 100px; margin: 0.75rem auto; }

@media (max-width: 767.98px) {
    /* Phone hides the per-side badges and shows the top-bar one instead. */
    .side-contract { display: none; }
}

/* Shared title bar — same treatment as the fixed navigation bar, so the top and
   bottom of every info page frame the content identically. Sticky rather than
   static so it stays put while the trick list on /info scrolls; the blur keeps
   the caption legible over content passing beneath. */

.page-header { position: sticky; top: 12px; z-index: 5;
               display: flex; align-items: center; gap: 10px;
               height: 52px; box-sizing: border-box; padding: 0 16px;
               margin-bottom: 16px;
               background: rgb(0 0 0 / 0.18);
               backdrop-filter: blur(10px);
               border: 1px solid rgb(255 255 255 / 0.10);
               border-radius: 14px; }
.page-header img { width: 24px; height: 24px; flex: none; }
.page-header span { font-size: 1rem; font-weight: 600;
                    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ImageButton WidthRequest/HeightRequest=36, Padding=7, BackgroundColor=Transparent. */
/* flex: none on the container as well as the buttons. Without it the row shrinks
   this box while its now-unshrinkable children keep their width, so the buttons
   simply overflow its right edge instead of the title giving way. */
.page-header__actions { margin-left: auto; flex: none;
                        display: flex; align-items: center; gap: 6px; }
/* flex: none, or the header row shrinks these below their content. The icon-only
   buttons survive it (their content is an image that scales), but the labelled
   one gets squeezed to 75px around a 112px caption and the text spills across
   its neighbours on a 360px phone. The title beside them is the element meant to
   give way, and does: it has overflow:hidden and ellipsizes. */
.header-action { width: 36px; height: 36px; padding: 7px; box-sizing: border-box;
                 flex: none; display: grid; place-items: center; cursor: pointer;
                 background: none; border: 0; border-radius: 8px; }
.header-action img { width: 100%; height: 100%; }
.header-action:hover:not(:disabled) { background: rgb(255 255 255 / 0.12); }
.header-action:disabled { opacity: 0.35; cursor: default; }
/* Statistics reset: a glyph button, WidthRequest/HeightRequest=44, FontSize=26. */
.header-action--reset { width: 44px; height: 44px; padding: 0;
                        font-size: 26px; line-height: 1; color: inherit; }

/* Labelled variant: icon plus caption, for the report action. The other header
   buttons are icon-only because MAUI's are; this one has no MAUI counterpart
   and names itself, since a speech bubble alone says nothing about what
   pressing it does. */
.header-action--labelled { width: auto; height: 36px; padding: 0 10px 0 7px;
                           grid-auto-flow: column; gap: 7px; color: inherit; }
.header-action--labelled img { width: 22px; height: 22px; }
/* Overrides .page-header span, which is sized for the page title and would
   render this at 1rem/600 with the title's ellipsis rules. */
.header-action--labelled span { font-size: 13px; font-weight: 600;
                                white-space: nowrap; }

/* ---------------------------------------------------------------------------
   Round-session dialog — SessionDialogOverlay in GameInfoPage.xaml.

   This one keeps MAUI's own light palette rather than the felt used elsewhere
   in the web head: it is a modal sheet over a dimmed page, so it is not sitting
   against the table background that motivated the darker treatment.
   --------------------------------------------------------------------------- */

.session-dialog { position: fixed; inset: 0; z-index: 60;
                  display: grid; place-items: center; padding: 40px;
                  /* Bounds the implicit column to the overlay. Without it the
                     column auto-sizes to the panel, which makes the panel's own
                     max-width:100% resolve against its own width and therefore
                     constrain nothing — the dialog kept its desktop 520px on a
                     360px phone and ran 200px off the right edge. */
                  grid-template-columns: minmax(0, 1fr);
                  /* XAML "#88000000" is ARGB; CSS 8-digit hex is RGBA, so the
                     literal would parse as fully transparent dark red. */
                  background: rgb(0 0 0 / 0.53); }
/* min() rather than a media query: the width has to shrink with the overlay at
   every size, and the phone block below only adjusts padding. */
.session-dialog__panel { width: min(520px, 100%); max-width: 560px;
                         box-sizing: border-box; overflow: hidden;
                         background: #DDE7D3; color: #132218;
                         border: 1px solid #6A7D61; border-radius: 8px; }
.session-dialog__title { margin: 0; padding: 11px 18px; text-align: center;
                         font-size: 20px; font-weight: 700; letter-spacing: 1.2px;
                         color: #102016; background: #BFD0B4;
                         border-bottom: 1px solid #91A184; }
.session-dialog__body { display: grid; gap: 16px; padding: 24px; }
.session-dialog__message { margin: 0; font-size: 14px; }
.session-dialog__message--info { color: #486255; }
.session-dialog__message--warn { color: #7A4E19; }
.session-dialog__message--error { color: #8A2F2F; }
.session-dialog__field { display: grid; gap: 8px; }
.session-dialog__field label, .session-dialog__hint { font-size: 13px; color: #486255; }
.session-dialog__entry { padding: 8px 10px; font-size: 15px;
                         background: #F4F7F1; color: #132218;
                         border: 1px solid #B8C9B1; border-radius: 4px; }

.session-dialog__files { list-style: none; margin: 0; padding: 0;
                         height: 340px; overflow-y: auto; }
.session-dialog__file { display: grid; grid-template-columns: auto 1fr auto;
                        align-items: center; gap: 10px; padding: 7px 8px; margin-bottom: 7px;
                        background: #F4F7F1; border: 1px solid #B8C9B1; cursor: pointer; }
.session-dialog__file input { accent-color: #176B45; width: 16px; height: 16px; }
.session-dialog__file-name { font-size: 15px; color: #132218;
                             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.session-dialog__file-date { font-size: 12px; color: #486255; }

/* Grid "Auto,Auto,*,Auto" — the star column pushes Cancel to the far side. */
.session-dialog__buttons { display: flex; align-items: center; gap: 10px; }
.session-dialog__spacer { flex: 1; }
.session-button { cursor: pointer; border-radius: 6px; font-size: 15px; font-weight: 600;
                  border: 1px solid transparent; }
.session-button:disabled { opacity: 0.45; cursor: default; }
.session-button--delete { padding: 10px 16px; background: #D8E5D2; color: #24583C;
                          border-color: #6A9D48; }
.session-button--primary { padding: 10px 18px; background: #176B45; color: #F8F1DA; }
.session-button--cancel { padding: 10px 16px; background: #F4F7F1; color: #24583C;
                          border-color: #6A9D48; }

/* Diagnostic report — shares the session dialog's chrome, adds the two text
   areas. Taller than either session mode, so the panel is capped and the body
   scrolls. The button row sits OUTSIDE that scroller (min-height:0 is what lets
   a flex child shrink below its content and actually scroll), because Copy is
   the one control the dialog exists for and it must stay reachable. */
.report-dialog { max-height: 92vh; display: flex; flex-direction: column; }
.report-dialog .session-dialog__body { overflow-y: auto; min-height: 0; }
.report-dialog__actions { flex: none; padding: 16px 24px 24px;
                          border-top: 1px solid #C3D3BA; }
.report-dialog__description, .report-dialog__text {
    width: 100%; box-sizing: border-box; padding: 8px 10px; resize: vertical;
    background: #F4F7F1; color: #132218;
    border: 1px solid #B8C9B1; border-radius: 4px; }
.report-dialog__description { font-family: inherit; font-size: 15px; }
/* Monospace: the report's leading columns only line up in a fixed-width font,
   and the round session below them is JSON. */
.report-dialog__text { font-size: 12px; line-height: 1.45;
                       font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; }
/* Turnstile injects an iframe of its own size. Empty until a send attempt, so
   it must collapse rather than reserve a gap in the dialog. */
.report-dialog__challenge:empty { display: none; }
.report-dialog__challenge { display: flex; justify-content: center; }

/* SessionDialogOverlay on phone: Padding 20, body Padding 18, list height 310.
   This block MUST stay after the base rules above — a media query adds no
   specificity, so up in the main phone block it would silently lose. */
@media (max-width: 767.98px) {
    .session-dialog { padding: 20px; }
    .session-dialog__body { padding: 18px; }
    .session-dialog__files { height: 310px; }
    /* Delete/Import/Cancel across a 320px panel; the spacer alone left Cancel
       hanging off the edge. */
    .session-dialog__buttons { flex-wrap: wrap; gap: 8px; }
    .session-button--delete, .session-button--primary,
    .session-button--cancel { padding: 10px 12px; }

    /* A labelled action and a page title do not both fit on a phone: at 375px
       the title collapses to an 11px ellipsis stub, which reads as broken
       rather than as truncation. Drop the title instead - it is the redundant
       half, since you have just navigated here and the content says what it is,
       whereas the button is the thing nobody can identify from its icon.

       :has() scopes this to headers that actually carry a labelled action, so
       the statistics and settings headers keep their titles. */
    .page-header:has(.header-action--labelled) > span { display: none; }
}

/* The page already pads for the fixed nav; give the sticky header room at the top. */
.page { padding-top: 12px; }
