/* ===========================================================================
   app.css — the project layer. Loads AFTER the extracted design-system bands.
   Everything here is either a documented deviation (docs/ds-deviations.md) or a
   utility the design system does not ship. No component is redrawn here.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   DEVIATION D9 — dark mode background and surface.

   design_system.md:291 ships `.dark{--background:#fff;--surface:#f8f8f7;…}` while
   `body` and `.dm-content` both paint `var(--surface)`. As shipped, dark mode is a
   WHITE page behind #141414 cards, with a near-white sidebar — it fails the first
   three boxes of §21's own dark-mode checklist.

   Only the dark values are touched. The light ones are asserted verbatim by
   ds-parity.mjs, which measures without `.dark`, so parity still passes on all nine
   tokens. --card stays #141414: one step lighter than the page, so cards still read
   as raised.
   --------------------------------------------------------------------------- */
.dark {
  --background: #0f0f0f;
  --surface:    #0f0f0f;
}

/* ---------------------------------------------------------------------------
   Fonts — DEVIATION D10.

   §0 says to load DM Sans and DM Mono "with this exact link" (Google Fonts), but
   explicitly permits vendoring "with the opsz axis intact". A render-blocking
   third-party request is the single largest first-paint cost on a phone on 4G in a
   club car park, and self-hosting also keeps visitor IPs off a third party, which
   matters for a French club under the GDPR.

   The variable files must keep their optical-size axis: a static subset renders
   letterforms and spacing slightly differently and the parity test will show it.
   --------------------------------------------------------------------------- */
/* The variable file is named DMSans-Variable.woff2, NOT the DMSans[opsz,wght].woff2 that
   Google ships. Square brackets are not valid in a URL path (RFC 3986 reserves them for
   IPv6 literals in the authority): browsers fail to resolve the relative url() and fall
   back to the document base, so the request goes to /fr/assets/fonts/... and 404s — and
   some shared hosts and mod_security rulesets reject bracketed paths outright. Rename the
   file when you convert it; see assets/fonts/README.md. */
@font-face {
  font-family: 'DM Sans';
  src: url('../fonts/DMSans-Variable.woff2') format('woff2-variations');
  font-weight: 100 1000;
  font-stretch: 100%;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Mono';
  src: url('../fonts/DMMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'DM Mono';
  src: url('../fonts/DMMono-Medium.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}

/* ---------------------------------------------------------------------------
   Page frame
   --------------------------------------------------------------------------- */
* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--base-size);
  color: var(--foreground);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
}

/* Every number in the app. §1: "Numbers: always font-variant-numeric: tabular-nums."
   Times in a grid that jitter as the digits change are unreadable. */
time, .num, .money, td.num, .tabular { font-variant-numeric: tabular-nums; }

a { color: inherit; }

/* The design system's own base rule, restated because a component that sets display
   (`.btn` is inline-flex, `.overlay` is flex) silently beats the user-agent rule for
   [hidden] — a hidden button still renders, still measures, and still lands in the
   accessibility tree. */
[hidden] { display: none !important; }

/* Skip link: the first thing in the tab order, invisible until focused.
 * Hidden ABOVE the page, never to the side: a left of -9999px becomes scrollable
 * space in right-to-left pages (Arabic), which then slid sideways on phones. */
.skip-link {
  position: absolute;
  inset-inline-start: 8px;
  top: 8px;
  transform: translateY(-200%);
  z-index: 2000;
  padding: 10px 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-weight: 600;
}
.skip-link:focus { transform: none; }

/* Visually hidden but reachable by a screen reader. Used by the grid's live region
   and by labels that would otherwise be duplicated visually. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------------
   Small utilities. Nothing here is a component.
   --------------------------------------------------------------------------- */
.stack       { display: flex; flex-direction: column; gap: 16px; }
.stack-sm    { display: flex; flex-direction: column; gap: 8px; }
.row         { display: flex; align-items: center; gap: 8px; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.row-wrap    { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.grow        { flex: 1 1 auto; min-width: 0; }
.muted       { color: var(--foreground-subdued); }
/* A failure reason printed inline — an SMTP error, a rejected import line. Not a
   badge and not an alert: it belongs to one row, and §2 routes a message that belongs
   to one row to the row. */
.tc-danger   { color: var(--destructive-text); }
/* Une majuscule à la PREMIÈRE lettre seulement. `text-transform: capitalize` en mettait
   une à chaque mot, ce qui n'est juste qu'en anglais : « Sábado 3 De Octubre »,
   « Samedi 3 Octobre ». ::first-letter ne s'applique pas à un élément en ligne, d'où
   l'inline-block sur un <span> ; et rien en arabe, où isoler la première lettre casse
   sa liaison avec la suivante (l'arabe n'a de toute façon pas de majuscules). */
span.cap-first { display: inline-block; }
html:not([dir="rtl"]) .cap-first::first-letter { text-transform: uppercase; }
.strong      { font-weight: 600; }
.center      { text-align: center; }
.nowrap      { white-space: nowrap; }
.truncate    { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mt-0  { margin-top: 0; }
.mb-0  { margin-bottom: 0; }
.mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; }

/* Wide content — tables, the booking grid on a small desktop — scrolls inside its own
   container. The page body must never scroll horizontally. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---------------------------------------------------------------------------
   Field-level validation.
   §2: validate on blur, re-validate on input only after a field has errored, never
   on the first keystroke. The message says what is wrong AND what to do, and sits
   under its own field.
   --------------------------------------------------------------------------- */
.field-error {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  line-height: 16px;
  color: var(--destructive-text);
}
.form-input[aria-invalid="true"],
.form-select[aria-invalid="true"],
.form-textarea[aria-invalid="true"] {
  border-color: var(--destructive-border);
}

/* ---------------------------------------------------------------------------
   Print. Clubs pin the day's grid to a corkboard, and members print a confirmation.
   --------------------------------------------------------------------------- */
@media print {
  .tc-tabbar, .tc-topnav, .dm-sidebar, .dm-topbar,
  .no-print, #toast-container, .sg-dayhead .btn { display: none !important; }

  body { background: #fff; }
  .card, .tc-card { box-shadow: none; border: 1px solid #ddd; }
  a[href]::after { content: ""; }   /* printed URLs are noise on a one-page slip */
}

/* Une case à cocher qui porte une aide.
   components.css donne à .switch-desc un margin-top mais pas de display:block, et
   .check-row centre ses éléments verticalement : l'aide se collait donc à la fin du
   libellé, sur la même ligne, et la case flottait au milieu d'un pavé de trois lignes.
   Le DS est extrait et ne se retouche pas — la correction vit ici. */
.check-row .switch-desc { display: block; }
.check-row:has(.switch-desc) { align-items: flex-start; }
.check-row:has(.switch-desc) .check-box { margin-top: 2px; }

/* ---------------------------------------------------------------------------
   Le score set par set (partials/score-form, migration 010). Ici et pas dans
   front.css : le même formulaire sert sur la page de l'e-mail, sur la réservation du
   membre et dans le back-office, qui ne charge pas front.css.
   --------------------------------------------------------------------------- */
.score-outcome { border: 0; padding: 0; margin: 0 0 16px; display: grid; gap: 10px; }
.score-table { width: 100%; border-collapse: separate; border-spacing: 0 8px; table-layout: fixed; }
.score-table thead th {
  font-size: 13px; font-weight: 600; text-align: center; padding: 0 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.score-table thead th:first-child, .score-table tbody th { width: 92px; }
.score-table tbody th {
  text-align: left; font-size: 13px; font-weight: 500; color: var(--foreground-subdued);
  padding-right: 8px; white-space: nowrap;
}
.score-table td { text-align: center; padding: 0 4px; }
.score-input {
  height: 48px; max-width: 96px; margin: 0 auto; display: block; padding: 0 4px;
  font-size: 20px; font-weight: 600; text-align: center; font-variant-numeric: tabular-nums;
  -moz-appearance: textfield; appearance: textfield;
}
.score-input::-webkit-outer-spin-button,
.score-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.score-tb th { font-size: 12px; font-weight: 400; padding-left: 12px; }
.score-tb .score-input { height: 34px; max-width: 64px; font-size: 14px; font-weight: 500; }
.score-form [data-set-add] { margin: 4px 0 12px; }
.score-option { margin-top: 10px; font-size: 13px; }
.score-summary { margin: 14px 0 0; min-height: 1.4em; font-weight: 600; }
.score-summary.is-warn { font-weight: 500; color: var(--foreground-subdued); }
.score-submit { width: 100%; justify-content: center; margin-top: 16px; }

/* Le détail d'un score déjà enregistré : une ligne par équipe, un chiffre par set. */
.score-board { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.score-board td { padding: 6px 0; border-bottom: 1px solid var(--border-soft); }
.score-board tr:last-child td { border-bottom: 0; }
.score-board .score-board-name { width: 100%; padding-right: 12px; font-size: 14px; }
.score-board .score-board-set { min-width: 30px; text-align: center; font-size: 16px; color: var(--foreground-subdued); }
.score-board .score-board-set.is-won { color: var(--foreground); font-weight: 700; }
.score-board .score-board-set sup { font-size: 10px; font-weight: 500; margin-left: 1px; }
.score-board .is-winner .score-board-name { font-weight: 700; }

/* ---------------------------------------------------------------------------
   Right-to-left (Arabic). Flex and grid mirror on their own once <html dir="rtl">;
   what follows swaps the rules written with physical sides (left/right) across
   app.css, front.css and site.css, and turns direction-bearing icons around.
   --------------------------------------------------------------------------- */
[dir="rtl"] .i-chevron-left, [dir="rtl"] .i-chevron-right,
[dir="rtl"] .i-arrow-left, [dir="rtl"] .i-arrow-right,
[dir="rtl"] .i-external-link { transform: scaleX(-1); }

[dir="rtl"] th, [dir="rtl"] td { text-align: right; }
[dir="rtl"] th.num, [dir="rtl"] td.num, [dir="rtl"] .num,
[dir="rtl"] .tool-score, [dir="rtl"] .bk-score, [dir="rtl"] .tn-table td.tn-score { text-align: left; }
[dir="rtl"] .rr-grid tbody th { text-align: right; }

[dir="rtl"] .site-prose ul, [dir="rtl"] .site-prose ol,
[dir="rtl"] .tc-prose ul, [dir="rtl"] .tc-install-steps, [dir="rtl"] .shop-bullets { padding-left: 0; padding-right: 22px; }
[dir="rtl"] .site-prose .callout { border-left: 0; border-right: 3px solid var(--foreground); }
[dir="rtl"] .tn-upcoming li, [dir="rtl"] .tn-event { border-left: 1px solid var(--border); border-right: 4px solid var(--sport); }
[dir="rtl"] .site-prose .steps-list > li { padding-left: 0; padding-right: 44px; }
[dir="rtl"] .site-prose .steps-list > li::before { left: auto; right: 0; }

[dir="rtl"] .mm-sport-toggle, [dir="rtl"] .tc-listrow, [dir="rtl"] .tc-search-result { text-align: right; }
[dir="rtl"] .tc-install-glyph { margin-left: 0; margin-right: 4px; }
[dir="rtl"] .mm-sport-toggle > svg:last-child { margin-left: 0; margin-right: auto; }
[dir="rtl"] .site-burger { margin-right: 0; margin-left: -10px; }
[dir="rtl"] .tc .form-file::file-selector-button { margin-right: 0; margin-left: 12px; }
[dir="rtl"] .tc-adminfab { left: auto; right: 12px; }
[dir="rtl"] .lb-table .lb-rank { padding-left: 8px; padding-right: 16px; }
[dir="rtl"] .lb-table .lb-name .badge, [dir="rtl"] .tn-nat, [dir="rtl"] .mk-round h3 span { margin-left: 0; margin-right: 6px; }
[dir="rtl"] .site-crumbs .flag, [dir="rtl"] .mk-share .share-label { margin-right: 0; margin-left: 4px; }
[dir="rtl"] .dir-badge-flag { right: auto; left: -6px; }
[dir="rtl"] .brand-chip span:only-child { padding-left: 0; padding-right: 6px; }
[dir="rtl"] .yt-lite-play svg { margin-left: 0; }
@media (min-width: 62em) {
  [dir="rtl"] .tc #toast-container { right: auto; left: 20px; }
}

/* Arabic reads better a touch larger and looser than Latin at the same size. */
[dir="rtl"] body { font-family: "Segoe UI", Tahoma, "Noto Naskh Arabic", "Noto Sans Arabic", system-ui, sans-serif; line-height: 1.7; }
