/* Family Portal — V1 navigation skeleton
   Tablet-first: large touch targets, big text, minimal chrome.
   No child-facing text lives here — copy comes from config/strings/fr.json. */

/* Typography (docs/art/spec_production_visuels_accueil.md item 6, chosen
   2026-09-10): Baloo 2 for titles/accroches (rounded display face, echoes
   the wordmark's bubble letters without duplicating it), Nunito for body
   text (neutral, soft terminals, reads well small for parent-facing
   paragraphs). Self-hosted (web/assets/fonts/) rather than loaded from the
   Google Fonts CDN per the fiche's RGPD note — a CDN call would send every
   visitor's IP/UA to Google on every visit. Only the "latin" subset is
   fetched (covers all of é/è/à/ç/œ — French accents live in the Latin-1
   range U+0000-00FF, œ/Œ is explicitly U+0152-0153, both inside this
   subset), not the other scripts Google Fonts also serves (cyrillic,
   vietnamese, devanagari...), which this project has no use for. Both
   files are variable fonts — one physical file per family covers both
   weights below; the browser picks the right instance from the file
   itself via each @font-face's own font-weight value, same as Google's
   own served CSS does. */
@font-face {
  font-family: 'Baloo 2';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/baloo2-latin.woff2') format('woff2');
}

@font-face {
  font-family: 'Baloo 2';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/baloo2-latin.woff2') format('woff2');
}

@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/nunito-latin.woff2') format('woff2');
}

@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/nunito-latin.woff2') format('woff2');
}

:root {
  --color-bg: #fdf6e9;
  --color-surface: #ffffff;
  --color-text: #2c2c2c;
  --color-text-muted: #6b6b6b;
  --color-primary: #3b6fa4;
  --color-primary-dark: #274b73;
  --color-accent: #f2994a;
  --color-border: #d8d0bd;
  --radius-lg: 24px;
  --radius-md: 16px;
  --touch-min: 56px;
  --font-heading: 'Baloo 2', Verdana, "Segoe UI", sans-serif;
  --font-body: 'Nunito', Verdana, "Segoe UI", sans-serif;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-body);
  background: var(--color-bg);
  color: var(--color-text);
  min-height: 100vh;
}

h1, h2, h3,
.app-title,
.section-title,
.btn,
.hub-card-title {
  font-family: var(--font-heading);
}

.page {
  max-width: 960px;
  margin: 0 auto;
  padding: 32px 24px 64px;
}

.page-narrow {
  max-width: 480px;
}

.top-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 32px;
}

.top-bar-links {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* web/parent/dashboard.php — same button row, reused at the bottom of the
   page instead of in the top-bar. */
.dashboard-footer-links {
  justify-content: center;
  margin-top: 40px;
}

.app-title {
  font-size: 1.75rem;
  margin: 0;
  color: var(--color-primary-dark);
}

.app-title img {
  height: 56px;
  width: auto;
  display: block;
}

.section-title {
  font-size: 1.4rem;
  color: var(--color-primary-dark);
  margin: 32px 0 16px;
}

/* exercise_hub.php's "Les jeux" title — extra clearance above it, since the
   curated exercise cards above (see .hub-grid-exercises) already bleed
   past their own bottom edge with the character art. */
.section-title-spaced {
  margin-top: 64px;
}

/* Buttons — 3 flat button-background assets (Notion "FunDrill v2.2 (charte
   graphique)" > Boutons pour toutes les pages), applied as a stretchable
   border-image (fill keyword renders the source's own center fill too, not
   just its edges) rather than a plain background so buttons of any width
   keep the asset's own rounded corners undistorted. border-image-slice is
   an estimate of the asset's own corner size in source pixels (all 3 share
   roughly the same ~270x153 canvas/corner radius) — re-check visually
   against docs/art/UI/button_*.png if a future asset revision changes the
   corner proportions.
   - button_basic.png (cream/tan): the default/neutral button — most of the
     app's buttons (Recommencer/Quitter, Lancer un exercice, nav links,
     dense parent-zone admin rows...).
   - button_green.png: an affirmative/primary action — Valider, Se
     connecter, Créer un compte.
   - button_red.png: a destructive or session-ending action — Se
     déconnecter, supprimer/désactiver (parent zone).
   Text stays dark ink on the cream background (already readable) but must
   switch to white on the green/red fills — flat colors under thin text
   would fail contrast otherwise. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-min);
  padding: 12px 28px;
  font-size: 1.1rem;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.1s ease;
  background: none;
  border-style: solid;
  border-width: 18px;
  border-image-repeat: stretch;
  border-image-slice: 44 fill;
  border-image-width: 18px;
  /* Fallback in case the border-image asset fails to load (offline, blocked
     request...) — still reads as a button rather than plain text. */
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  /* Same soft-shadow-under-a-pill-button look already used everywhere in
     the exercise/game mechanics (e.g. web/exercises/type_word/play.html's
     .reset/.validate-btn) — a neutral shadow by default, overridden to a
     shadow tinted to match the button's own fill below. */
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
}

.btn:active {
  transform: scale(0.97);
}

/* "In alpha" until a form's required fields are all filled (see
   web/assets/js/form_gate.js) — a plain opacity fade rather than a
   greyed-out fill swap, since buttons use an image border (border-image)
   that doesn't respond to a background-color change. */
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn:disabled:active {
  transform: none;
}

.btn-primary {
  border-image-source: url('../images/ui/button_green.png');
  color: #ffffff;
  box-shadow: 0 6px 14px rgba(60, 174, 112, 0.4);
}

.btn-secondary {
  border-image-source: url('../images/ui/button_basic.png');
  color: var(--color-text);
}

.btn-danger {
  border-image-source: url('../images/ui/button_red.png');
  color: #ffffff;
  box-shadow: 0 6px 14px rgba(241, 106, 84, 0.4);
}

/* Child picker */

.child-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 24px;
}

/* Home page (web/index.php) child picker only — pushes the "Qui joue
   aujourd'hui" heading further down and adds breathing room around the
   card grid, without touching parent/dashboard.php's or
   child/select_profile.php's own use of .section-title/.child-grid. */
.child-prompt-title {
  margin-top: 72px;
}

/* .child-grid's own grid-template-columns (minmax(160px, 1fr)) always
   divides the full row width evenly across however many cards there are —
   fine for the 120px avatar it was tuned for, but with the doubled
   .child-avatar-xl (240px) that even share can come out narrower than the
   avatar itself (e.g. 3 cards in the page's 960px max-width), and the
   overflowing avatar then renders hard against the card's left edge
   instead of centered. Fixed-width flex cards that wrap and center
   (per CLAUDE.md's "wrapped item rows" rule) sidestep this instead of
   trying to out-guess the grid's column math. */
.child-grid-spaced {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin: 32px 0 48px;
}

.child-grid-spaced .child-card {
  width: 340px;
}

.child-card {
  padding: 24px 16px;
  text-align: center;
  text-decoration: none;
  color: var(--color-text);
  transition: transform 0.15s ease;
  background: none;
  border-style: solid;
  border-width: 20px;
  border-image-source: url('../images/ui/button_basic.png');
  border-image-slice: 44 fill;
  border-image-width: 20px;
  border-image-repeat: stretch;
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
}

.child-card:active {
  transform: scale(0.96);
}

.child-card-inactive {
  opacity: 0.55;
}

.child-avatar {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--color-border);
  font-size: 2.5rem;
  font-weight: 700;
  color: #ffffff;
  margin: 0 auto 12px;
}

.child-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Home page (web/index.php) only — doubles the shared .child-avatar size for
   the child picker's own cards, without touching the base size still used by
   parent/dashboard.php and child/select_profile.php. */
.child-avatar-xl {
  width: 240px;
  height: 240px;
  font-size: 5rem;
}

.child-avatar-sm {
  width: 56px;
  height: 56px;
  font-size: 1.4rem;
  margin: 0;
}

/* exercise_hub.php only (2x .child-avatar-sm) — not applied to
   all_exercises.php/child_detail.php's own .child-avatar-sm usage. */
.child-avatar-lg {
  width: 112px;
  height: 112px;
  font-size: 2.8rem;
  margin: 0;
}

.child-header {
  display: flex;
  align-items: center;
  gap: 12px;
}

.child-name {
  font-size: 1.3rem;
  font-weight: 700;
  display: block;
}

.child-level {
  font-size: 0.95rem;
  color: var(--color-text-muted);
  display: block;
}

.token-badge {
  background: var(--color-accent);
  color: #ffffff;
  padding: 10px 20px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 1.1rem;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Colored shadow (tinted to the badge's own fill, same pattern as
     .btn-primary/.btn-danger above) rather than the neutral black shadow
     used elsewhere — makes the star counter pop more. */
  box-shadow: 0 6px 14px rgba(242, 153, 74, 0.45);
}

/* Replaces the "jetons" text label on the token balance badge — a young
   non-reading child recognizes the star icon faster than the word. */
.token-badge-star {
  width: 22px;
  height: 22px;
}

/* Forms */

.form-card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  padding: 24px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 20px;
}

.form-field label {
  font-size: 1.05rem;
  font-weight: 700;
}

.form-field input,
.form-field select,
.form-field textarea {
  min-height: var(--touch-min);
  padding: 12px 14px;
  font-size: 1.05rem;
  border-radius: var(--radius-md);
  border: 2px solid var(--color-border);
  background: #ffffff;
  font-family: inherit;
}

.form-field textarea {
  font-family: monospace;
  resize: vertical;
}

.form-field span {
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

.form-field-checkbox label {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 0.95rem;
  font-weight: 400;
}

.form-field-checkbox input[type="checkbox"] {
  min-height: unset;
  width: 22px;
  height: 22px;
  flex: none;
  margin-top: 2px;
}

.form-error {
  color: #b3261e;
  font-weight: 700;
  margin-bottom: 16px;
}

/* Exercise / game hub */

.hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
}

/* "Voir tous les exercices" under the curated hub grid (see
   Family\Exercise\ExerciseCurator) — a low-key escape hatch, not another
   button competing with the exercise cards themselves. */
.link-discreet {
  display: inline-block;
  margin-top: 12px;
  font-size: 0.9rem;
  color: var(--color-text-muted);
  text-decoration: underline;
}

/* Dev hub sort/filter bar — client-side only (see web/dev/index.php's inline
   script), so it never touches the server-rendered card list itself, just
   reorders/hides the existing .hub-card elements. */
.dev-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  margin-bottom: 20px;
  padding: 12px 16px;
  background: var(--color-surface);
  border-radius: var(--radius-md);
}

.dev-filters label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

.dev-filters select {
  font-size: 0.9rem;
  padding: 4px 6px;
}

.hub-card {
  position: relative;
  padding: 24px 20px 24px 26px;
  min-height: 100px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  text-decoration: none;
  color: var(--color-text);
  background: none;
  border-style: solid;
  border-width: 16px;
  border-image-source: url('../images/ui/button_basic.png');
  border-image-slice: 44 fill;
  border-image-width: 16px;
  border-image-repeat: stretch;
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
  transition: transform 0.15s ease;
}

/* Liseré : la couleur du sujet (--card-accent, posée inline par PHP —
   web/dev/index.php, web/child/exercise_hub.php/all_exercises.php) ou,
   pour un jeu (pas de sujet), --color-accent — remplace l'ancien
   border-left plat maintenant qu'un border-image commun occupe les 4
   côtés (border-image, une fois actif, couvre tout le cadre, on ne peut
   plus garder un côté en couleur unie via border-left-color). */
.hub-card::before {
  content: '';
  position: absolute;
  left: 6px;
  top: 10px;
  bottom: 10px;
  width: 6px;
  border-radius: 3px;
  background: var(--card-accent, var(--color-primary));
}

.hub-card-game::before {
  background: var(--color-accent);
}

.hub-card:active {
  transform: scale(0.97);
}

.hub-card-title {
  font-size: 1.15rem;
  font-weight: 700;
  /* Leaves room so long titles don't run under the subject badge, which is
     absolutely positioned over the top-right corner (see .hub-card-subject
     below) rather than laid out in flow — the badge needs to sit at a fixed
     corner regardless of how tall the title/status text stack gets. */
  padding-right: 64px;
}

.hub-card-level {
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

.hub-card-level-star {
  width: 16px;
  height: 16px;
  vertical-align: -3px;
  margin-left: 3px;
}

/* Subject badge (top-right) + colored left border ("liseré") — see
   docs/family_technical.md "Theme system": this is the `subject` metadata
   field (math/French/English...), a different concept from the cosmetic
   `theme` system (vehicles/farm_animals/toys), so it gets its own config
   (config/subjects.json) and its own class names here rather than reusing
   "theme" anywhere in code. Color comes from that config via a plain inline
   `style="border-left-color: ..."` set per card in PHP, not a fixed set of
   CSS classes, since new subjects can be added without a matching CSS rule.
   Added 2026-08-13 at the user's request, closing the "exercise theme/
   subject tagging" item that docs/family_design.md had deliberately listed
   as a later addition. */

.hub-card-subject {
  position: absolute;
  top: 12px;
  right: 16px;
  font-size: 0.75rem;
  font-style: italic;
  color: var(--color-text-muted);
  text-align: right;
}

/* Subject icon (web/assets/images/subjects/<code>.png) — a faint background
   illustration on the card's right side, purely decorative (the color
   liseré + name label above already carry the actual subject information,
   this is just a visual hint for non-reading children). Rendered as the
   *first* child in the card markup so later elements (title/level/subject
   spans) simply paint over it in normal DOM order, no z-index needed.
   Added 2026-08-16 at the user's request, child hub only — not shown on
   web/dev/index.php's cards. */
.hub-card-subject-icon {
  position: absolute;
  top: 8px;
  right: 8px;
  bottom: 8px;
  width: 60%;
  background-repeat: no-repeat;
  background-position: right center;
  background-size: contain;
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.hub-card-game:hover .hub-card-subject-icon,
.hub-card-game:focus-visible .hub-card-subject-icon {
  opacity: 1;
}

/* Exercise card v5 (2026-09-10): 2/3-width rows, alternating left/right
   (every other row flush against its own side, no shared gutter), with a
   costumed character at a fixed true size (250px wide, see
   .hub-card-character) — its own box height is independent of the card's,
   so shrinking the card never shrinks the character (v4's mistake: tying
   the character's box height to the card's made it shrink along with a
   shorter card, since background-size:contain was then height-limited).
   The character bleeds upward past the card's top edge as needed and its
   feet poke a little past the bottom edge; horizontally it stays flush
   with the card's own left edge, never bleeding into the page margin. At
   the opposite end, the subject icon (flag/calculator/globe/...) at 40%
   opacity with its subject name printed underneath as a caption.
   Deliberately its own class set rather than variants of .hub-grid/
   .hub-card — those stay exactly as they are for the games section,
   web/child/all_exercises.php and web/dev/index.php, none of which this
   redesign covers. */
.hub-grid-exercises {
  display: flex;
  flex-direction: column;
  /* Wider than a plain card gap (was 24px) — the character art (see
     .hub-card-character) bleeds above/below each card, so a tighter gap
     let neighbouring rows crowd each other. */
  gap: 48px;
  /* Clearance so the first row's character (taller than its own card, see
     .hub-card-character) doesn't bleed up into the section title above it. */
  margin-top: 60px;
}

.hub-card-exercise {
  position: relative;
  display: flex;
  align-items: center;
  width: 66%;
  height: 134px;
  text-decoration: none;
  color: var(--color-text);
  background: none;
  border-style: solid;
  border-width: 16px;
  border-image-source: url('../images/ui/button_basic.png');
  border-image-slice: 44 fill;
  border-image-width: 16px;
  border-image-repeat: stretch;
  border-radius: var(--radius-md);
  background-color: var(--color-bg);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
  /* Visible (not hidden) so the character (see .hub-card-character) can
     bleed past the card's top edge and its feet past the bottom edge. */
  overflow: visible;
  transition: transform 0.15s ease;
}

/* Liseré : voir la même note sur .hub-card::before — --card-accent (posé
   inline par web/child/exercise_hub.php) remplace l'ancien border-left
   plat. Offset un peu plus généreux ici (border-image plus épais que sur
   .hub-card) pour rester visuellement à l'intérieur du cadre. */
.hub-card-exercise::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 14px;
  bottom: 14px;
  width: 6px;
  border-radius: 3px;
  background: var(--card-accent, var(--color-primary));
}

/* "Calés ou à gauche ou à droite" — odd rows flush against the container's
   left edge, even rows flush against its right edge, every other row. */
.hub-grid-exercises .hub-card-exercise:nth-child(odd) {
  align-self: flex-start;
}

.hub-grid-exercises .hub-card-exercise:nth-child(even) {
  align-self: flex-end;
}

.hub-card-exercise:active {
  transform: scale(0.97);
}

/* Fixed box size, deliberately NOT derived from the card's own height —
   tying it to the card (top:0/bottom:-20px with height:auto) squashed the
   character down every time the card got shorter, since
   background-size:contain is limited by whichever box dimension runs out
   first (found the hard way: shrinking the card by a third visibly shrank
   the character too, which wasn't the intent). 220px is the box height
   that read right against a 250px-wide character during design — kept as
   a fixed constant here so a future card-height tweak never resizes the
   character again; it bleeds upward past the card's top edge as needed
   (into the row above, not literally full natural size — a truly
   unclipped 250px-wide character needs ~420px of box height given these
   costumes' aspect ratios, which bled 2+ rows up and was worse) plus the
   feet poking past the bottom. */
.hub-card-character {
  position: absolute;
  left: 0;
  /* -20px shifted to -50px (30px further down) per request. */
  bottom: -50px;
  width: 250px;
  height: 220px;
  background-repeat: no-repeat;
  background-position: bottom center;
  background-size: contain;
  pointer-events: none;
}

.hub-card-exercise-body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 100%;
  height: 100%;
  padding: 10px 118px 10px 260px;
  overflow: hidden;
}

.hub-card-exercise-body .hub-card-title {
  padding-right: 0;
  line-height: 1.2;
}

/* "avec <enfant>" caption, printed under the exercise title — names the
   character randomly drawn per-card (see exercise_hub.php's per-exercise
   $character draw), later reused as the same character shown top-left on
   that exercise's own play.html. */
.hub-card-character-caption {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-muted);
}

/* Hidden here — the subject name now lives in .hub-card-subject-caption
   instead (under the endcap icon), not as a second line under the title. */
.hub-card-exercise-body .hub-card-subject {
  display: none;
}

/* Right-end cluster: the subject's own icon (flag/calculator/globe/...)
   at 40% opacity with its name printed underneath as a plain caption —
   one uniform treatment for every subject, language ones included (their
   "icon" already is the flag, so no separate opaque badge is needed on
   top of it any more). */
.hub-card-subject-end {
  position: absolute;
  top: 50%;
  right: 16px;
  transform: translateY(-50%);
  width: 90px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  pointer-events: none;
}

.hub-card-subject-end-icon {
  width: 85px;
  height: 85px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  opacity: 0.4;
}

.hub-card-subject-caption {
  font-size: 0.75rem;
  font-style: italic;
  color: var(--color-text-muted);
  text-align: center;
}

/* Shown on a game card when web/assets/js/exercise_launch.js's log_start.php
   call is rejected (insufficient balance, unknown/unpublished game) — unlike
   an exercise, a failed game launch must actually block navigation (see that
   file's comment), so the child needs some feedback instead of just nothing
   happening on tap. */
.hub-card-blocked-toast {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 10px;
  background: rgba(58, 46, 42, 0.9);
  color: #fff;
  font-size: 0.95rem;
  font-weight: 600;
  border-radius: var(--radius-md);
}

.hub-card-blocked-toast-star {
  width: 18px;
  height: 18px;
  vertical-align: -3px;
  margin: 0 2px;
}

/* Interaction/mechanic type — dev hub only, never shown to parents/children
   (see CLAUDE.md "Language convention": this is an internal/code-facing
   label, not runtime content, so it stays in English and only appears where
   web/dev/index.php renders it). */
.hub-card-interaction {
  position: absolute;
  bottom: 10px;
  right: 16px;
  font-size: 0.7rem;
  font-style: italic;
  color: var(--color-text-muted);
  opacity: 0.65;
}

/* Per-exercise token-gain multiplier badge ("xN", FUN-32) — dev-only ledger
   in config/exercise_multipliers.json (see web/dev/multipliers.php), only
   rendered on the card when set above the implicit default of 1. Visual is
   deliberately plain per the issue ("on ajustera le visuel plus tard") —
   black text in a yellow circle, bottom-right corner. On the dev hub, where
   .hub-card-interaction also sits bottom-right, that label is shifted left
   inline (see web/dev/index.php) when both are present on the same card. */
.hub-card-multiplier {
  position: absolute;
  bottom: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #ffd60a;
  color: #000;
  font-size: 0.75rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Admin/parent-dashboard tables — plain and functional on purpose, see
   parental_controls_brief.md ("not meant to be polished"). */

.admin-table-wrap {
  overflow-x: auto;
  margin-bottom: 16px;
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--color-surface);
  border-radius: var(--radius-md);
  overflow: hidden;
  font-size: 0.95rem;
}

.admin-table th,
.admin-table td {
  padding: 10px 12px;
  text-align: left;
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
}

.admin-table th {
  background: var(--color-bg);
  font-weight: 700;
  color: var(--color-primary-dark);
}

.admin-table tr:last-child td {
  border-bottom: none;
}

.inline-form {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 2px 0;
}

.admin-table td.stat-cell {
  text-align: center;
}

/* Empty spacer column between success/abandon rate in the subject-stats
   table (FUN-40) — just breathing room, no border/background of its own. */
.admin-table th.stat-spacer,
.admin-table td.stat-spacer {
  padding: 0;
  width: 24px;
  border-bottom: none;
  background: transparent;
}

/* Activity-history chart (FUN-42): period/subject filter controls above a
   hand-rolled SVG line chart (no charting library — see
   renderHistoryChart() in child_detail.php). Same green/red as the
   subject-stats table's rateBarStyle() rgb() values, kept as CSS classes
   here instead since these are strokes/fills, not background bars. */
.history-chart-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 12px;
}

.history-chart-wrap {
  background: var(--color-surface);
  border-radius: var(--radius-md);
  padding: 12px;
  margin-bottom: 16px;
}

.history-chart {
  width: 100%;
  height: auto;
  display: block;
}

.history-chart-grid {
  stroke: var(--color-border);
  stroke-width: 1;
}

.history-chart-axis-label {
  fill: var(--color-text-muted, #8a8378);
  font-size: 10px;
}

.history-chart-line-success {
  fill: none;
  stroke: rgb(76, 175, 80);
  stroke-width: 2;
}

.history-chart-line-abandon {
  fill: none;
  stroke: rgb(244, 67, 54);
  stroke-width: 2;
}

.history-chart-line-success-dot {
  fill: rgb(76, 175, 80);
}

.history-chart-line-abandon-dot {
  fill: rgb(244, 67, 54);
}

.history-chart-legend {
  display: flex;
  gap: 16px;
  margin-top: 4px;
  font-size: 0.85rem;
}

.history-chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.history-chart-legend-swatch {
  display: inline-block;
  width: 14px;
  height: 3px;
  border-radius: 2px;
}

/* .history-chart-line-success/-abandon set SVG fill/stroke for the chart's
   own <polyline> elements, which have no effect on a plain <span> — the
   legend swatch needs its own background-color instead. */
.history-chart-legend-swatch.history-chart-line-success {
  background: rgb(76, 175, 80);
}

.history-chart-legend-swatch.history-chart-line-abandon {
  background: rgb(244, 67, 54);
}

.history-day {
  background: var(--color-surface);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  margin-bottom: 8px;
}

.history-day summary {
  cursor: pointer;
  font-size: 0.95rem;
}

.history-day summary::marker {
  color: var(--color-primary-dark);
}

.history-day[open] summary {
  margin-bottom: 8px;
}

.inline-form input[type="number"],
.inline-form input[type="text"],
.inline-form select {
  min-height: 36px;
  padding: 4px 8px;
  font-size: 0.9rem;
  border-radius: 8px;
  border: 2px solid var(--color-border);
  font-family: inherit;
  width: 110px;
}

/* Wider than the plain 110px input width above — a period/subject select's
   own option text ("Dernière semaine", "Depuis le début"...) was cramped at
   that width, unlike the numeric +/- amount field it was sized for. */
.inline-form select {
  width: 210px;
}

.btn-sm {
  min-height: 36px;
  padding: 4px 12px;
  font-size: 0.9rem;
  border-radius: 8px;
  /* Smaller than .btn's default 18px — at full size the border-image eats
     most of a button this small (dense admin rows: +100/+1000/réinitialiser). */
  border-width: 9px;
  border-image-width: 9px;
}

.badge-inactive {
  color: var(--color-text-muted);
  font-style: italic;
}

.subject-factor-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.subject-factor-row label {
  width: 110px;
  font-weight: 700;
}

.subject-factor-row output {
  width: 36px;
  text-align: right;
  font-weight: 700;
}

/* Legal pages (web/legal/cgu.php, confidentialite.php) — plain long-form
   text, not a touch-target UI like the rest of the app. */

.legal-wip-banner {
  background: #fff3cd;
  border: 2px solid #f2994a;
  border-radius: var(--radius-md);
  padding: 12px 16px;
  font-weight: 700;
  color: #7a4a12;
  margin-bottom: 24px;
}

.legal-content {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  padding: 32px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
  line-height: 1.6;
}

.legal-article-heading {
  font-size: 1.1rem;
  color: var(--color-primary-dark);
  margin: 28px 0 8px;
}

.legal-article-heading:first-child {
  margin-top: 0;
}

.legal-content blockquote {
  margin: 8px 0;
  padding: 8px 16px;
  border-left: 4px solid var(--color-border);
  color: var(--color-text-muted);
  font-style: italic;
}

/* Legal-links bar — kept visually minimal (small text, muted) so it doesn't
   compete with the "top-bar" it sits next to. Most pages place it above the
   app title; the home page (web/index.php) places it at the bottom instead. */
.legal-links-bar {
  display: flex;
  justify-content: center;
  gap: 16px;
  font-size: 0.8rem;
  padding: 6px 0;
  margin-bottom: 12px;
}

.legal-links-bar a {
  color: var(--color-text-muted);
  text-decoration: underline;
}
