/* =========================================================
   components.css
   Reusable building blocks: sections, buttons, badges, chips,
   form fields, cards, like button, modals, file drop zone,
   toasts, "load more" button.
   No media query here, they all live in layout.css.
   ========================================================= */

/* ---------------------------------------------------------
   Section : conteneur de base de tout bloc de contenu
   --------------------------------------------------------- */
.sc-section{
  background:var(--c-white);
  border-radius:var(--r-card);
  overflow:hidden;
  box-shadow:var(--shadow-nav);
}
/* Deux blocs qui se suivent doivent se lire comme deux blocs. Aucune règle
   ne les séparait : ils se touchaient bord à bord, et le titre du second
   passait pour un intertitre du premier — visible sur le palmarès (podium,
   course, dotation) comme dans l'onglet Réglages. Le pas est celui que les
   autres blocs de ces écrans utilisent déjà (.recap, .podium-top). */
.sc-section + .sc-section,
.sc-section + .prize,
.prize + .sc-section,
.prize + .prize{ margin-top:var(--sp-6); }

.sc-section__head{
  padding:var(--sp-4) var(--sp-5);
  border-bottom:1px solid var(--c-grey-light);
}
.sc-section__title{
  font-size:var(--fs-section);
  font-weight:600;
}
/* A sentence that explains the section, sitting with its title rather than
   below the head's rule, where it would read as a caption for the first row
   (same reasoning as .race__lead). */
.sc-section__lead{
  margin:4px 0 0;
  font-size:var(--fs-small);
  color:var(--c-text-secondary);
}
.sc-section__empty{
  padding:28px var(--sp-5);
  color:var(--c-text-secondary);
}

/* Failed load: replaces the skeleton, which used to shimmer forever
   when the request failed (see render.viewLoadError). */
.load-error{
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:var(--sp-3);
  padding:28px var(--sp-5);
}
.load-error__msg{
  color:var(--c-text-secondary);
  margin:0;
}

.container{
  max-width:var(--container);
  margin:0 auto;
  padding:0 var(--sp-5);
}

/* ---------------------------------------------------------
   Boutons
   --------------------------------------------------------- */
.btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:var(--sp-2);
  height:40px;
  padding:0 18px;
  border:none;
  border-radius:var(--r-btn);
  font-size:var(--fs-body);
  font-weight:500;
  line-height:1;
  white-space:nowrap;
  cursor:pointer;
  transition:filter .15s ease, background .15s ease, box-shadow .15s ease, color .15s ease;
}
.btn:hover{ filter:brightness(0.95); }
.btn:disabled{ cursor:not-allowed; }
.btn svg{ flex:0 0 auto; }

.btn--primary{ background:var(--c-primary); color:var(--c-white); }

.btn--outline{
  background:transparent;
  color:var(--c-primary);
  box-shadow:inset 0 0 0 1px var(--c-primary-light);
}
.btn--outline:hover{ background:var(--c-primary); color:var(--c-white); box-shadow:none; filter:none; }

.btn--light{ background:var(--c-primary-light); color:var(--c-primary); }

.btn--grey{
  background:transparent;
  color:var(--c-text-secondary);
  box-shadow:inset 0 0 0 1px var(--c-grey-border);
}
.btn--grey:hover{ background:var(--c-grey-lightest); filter:none; }

/* Secondary action of the login screen ("Utiliser une autre adresse",
   see viewAuthSent/viewAuthConfirmLink in render.js). The class used to
   be emitted with no matching rule: the button had neither background
   nor border, only its label floating under the primary button. */
.btn--secondary{
  background:transparent;
  color:var(--c-text-primary);
  box-shadow:inset 0 0 0 1px var(--c-grey-border);
}
.btn--secondary:hover{ background:var(--c-grey-lightest); filter:none; }

.btn--green{ background:var(--c-green); color:var(--c-white); }

.btn--red{
  background:transparent;
  color:var(--c-red);
  box-shadow:inset 0 0 0 1px rgba(243,70,70,.25);
}
.btn--red:hover{ background:var(--c-red); color:var(--c-white); box-shadow:none; filter:none; }

.btn--red-solid{ background:var(--c-red); color:var(--c-white); }

.btn--ghost{ background:transparent; color:var(--c-text-secondary); }
.btn--ghost:hover{ background:var(--c-grey-lightest); filter:none; }

.btn--sm{ height:34px; padding:0 14px; font-size:var(--fs-small); }
/* Icon-only action, square so a run of them lines up. The label lives in
   title/aria-label instead of on screen: in the participants table the
   role column already carries a badge and a role toggle, and three more
   words pushed the pair onto a second line on every row. */
.btn--icon{ width:34px; padding:0; }
.btn--icon svg{ display:block; }
.btn--icon-danger:hover{ color:var(--c-red); }
.btn--block{ width:100%; }

/* pending state: label and icons hidden (still occupy their layout
   space, so the button doesn't resize), spinner centered on top.
   Default spinner is white for solid-background variants; overridden
   below for the light/transparent ones. */
.btn--loading{ position:relative; color:transparent !important; pointer-events:none; }
.btn--loading svg{ visibility:hidden; }
.btn--loading::after{
  content:'';
  position:absolute;
  top:50%; left:50%;
  width:16px; height:16px;
  margin:-8px 0 0 -8px;
  border:2px solid rgba(255,255,255,.35);
  border-top-color:#fff;
  border-radius:50%;
  animation:btn-spin .6s linear infinite;
}
.btn--light.btn--loading::after,
.btn--grey.btn--loading::after,
.btn--outline.btn--loading::after,
.btn--ghost.btn--loading::after,
.btn--secondary.btn--loading::after,
.dropdown__item.btn--loading::after{
  border-color:rgba(25,3,64,.2);
  border-top-color:var(--c-primary);
}
@keyframes btn-spin{ to{ transform:rotate(360deg); } }

/* ---------------------------------------------------------
   Badges de statut
   --------------------------------------------------------- */
.badge{
  display:inline-flex;
  align-items:center;
  gap:6px;
  height:24px;
  padding:0 11px;
  border-radius:var(--r-pill);
  font-size:12px;
  font-weight:600;
  line-height:1;
  white-space:nowrap;
}
.badge--green{ background:var(--c-green-soft); color:var(--c-green-text); }
.badge--orange{ background:var(--c-orange-soft); color:var(--c-orange-text); }
.badge--red{ background:var(--c-red-soft); color:var(--c-red-text); }
.badge--grey{ background:var(--c-grey-lightest); color:var(--c-text-secondary); }
.badge--purple{ background:var(--c-purple-soft); color:var(--c-purple); }
.badge--gold{ background:var(--c-gold-soft); color:var(--c-gold-text); }
.badge--dot::before{
  content:"";
  width:6px; height:6px;
  border-radius:var(--r-pill);
  background:currentColor;
}

/* category label */
.cat-tag{
  display:inline-flex;
  align-items:center;
  height:22px;
  padding:0 10px;
  border-radius:var(--r-pill);
  background:var(--c-primary-light);
  color:var(--c-cat-text);
  font-size:12px;
  font-weight:600;
  white-space:nowrap;
}

/* the tag carries the category's sentence as a native tooltip (cf catTags):
   the cursor is the only hint it does, a question mark on every tag would
   put a dozen glyphs in one screenful of cards */
.cat-tag--noted{ cursor:help; }

/* a photo can carry several category pills: this container groups them
   so they stay a single item in the flex layouts around them
   (card__foot, rank-row, lightbox...) */
.cat-tags{ display:flex; flex-wrap:wrap; align-items:center; gap:6px; min-width:0; }

/* ---------------------------------------------------------
   Chips de filtre
   --------------------------------------------------------- */
.chips{ display:flex; flex-wrap:wrap; gap:var(--sp-2); }
.chip{
  display:inline-flex;
  align-items:center;
  gap:7px;
  height:36px;
  padding:0 15px;
  border-radius:var(--r-pill);
  border:1px solid var(--c-grey-border);
  background:var(--c-white);
  font-size:var(--fs-small);
  font-weight:500;
  color:var(--c-text-secondary);
  cursor:pointer;
  transition:background .15s ease, border-color .15s ease, color .15s ease;
}
.chip:hover{ border-color:var(--c-primary); color:var(--c-primary); }
.chip.is-active{ background:var(--c-primary); border-color:var(--c-primary); color:var(--c-white); }
.chip__n{ font-size:12px; opacity:.75; }

/* ---------------------------------------------------------
   Champs de formulaire
   --------------------------------------------------------- */
.field{ margin-bottom:var(--sp-4); }
.field:last-child{ margin-bottom:0; }
.field label{
  display:block;
  font-size:var(--fs-small);
  font-weight:600;
  margin-bottom:7px;
}
fieldset.field{ border:0; padding:0; min-width:0; }
fieldset.field legend{
  display:block;
  width:100%;
  padding:0;
  font-size:var(--fs-small);
  font-weight:600;
  margin-bottom:7px;
}

/* picking the categories when submitting a photo: checkboxes presented
   as chips, several selections allowed */
.checkbox-group{ display:flex; flex-wrap:wrap; gap:var(--sp-2); }
/* specificity deliberately higher than ".field label", which would
   otherwise put these labels back to display:block and break the
   alignment */
.checkbox-group .checkbox-chip{
  display:inline-flex;
  align-items:center;
  gap:8px;
  height:36px;
  margin:0;
  padding:0 14px;
  border-radius:var(--r-pill);
  border:1px solid var(--c-grey-border);
  background:var(--c-white);
  font-size:var(--fs-small);
  font-weight:500;
  color:var(--c-text-primary);
  cursor:pointer;
}
.checkbox-group .checkbox-chip:hover{ border-color:var(--c-primary); }
.checkbox-chip input{
  width:16px;
  height:16px;
  margin:0;
  flex:0 0 auto;
  accent-color:var(--c-primary);
  cursor:pointer;
}

/* slider toggle: platform on/off settings
   (see viewAdminSettingsTab, hideParticipantNames) */
.switch{
  display:inline-flex;
  align-items:center;
  gap:10px;
  cursor:pointer;
  user-select:none;
}
.switch input{
  position:absolute;
  opacity:0;
  width:1px;
  height:1px;
  margin:0;
}
.switch__track{
  position:relative;
  width:44px;
  height:26px;
  border-radius:var(--r-pill);
  background:var(--c-grey-border);
  flex:0 0 auto;
  transition:background .18s ease;
}
.switch__track::after{
  content:'';
  position:absolute;
  top:3px;
  left:3px;
  width:20px;
  height:20px;
  border-radius:50%;
  background:var(--c-white);
  box-shadow:var(--shadow-nav);
  transition:transform .18s ease;
}
.switch input:checked + .switch__track{ background:var(--c-primary); }
.switch input:checked + .switch__track::after{ transform:translateX(18px); }
.switch input:focus-visible + .switch__track{ outline:2px solid var(--c-primary); outline-offset:2px; }
.switch__label{
  font-size:var(--fs-small);
  font-weight:500;
  color:var(--c-text-primary);
  min-width:56px;
}

/* A setting still being saved: the value on screen is already the one
   asked for (see App.ui.busy), only the server has yet to answer. The
   control is locked for the round-trip, so a slow request cannot be
   toggled twice. */
.switch--busy{ cursor:progress; }
.switch--busy .switch__track{ opacity:.55; }
.switch--busy .switch__label{ color:var(--c-text-secondary); }
.setting-saving{
  font-size:var(--fs-xsmall);
  color:var(--c-text-secondary);
}
/* Conséquence d'un réglage, dite avant qu'on l'enregistre : un second
   email d'annonce, ou une révélation que personne n'apprendra. Posée sous
   la ligne entière, pas dans la colonne de droite, pour tenir en une seule
   phrase lisible. */
.setting-warning{
  /* .setting-line is a wrapping flex row: a full basis puts the warning
     on its own line under both columns rather than beside the control */
  flex:1 1 100%;
  margin-top:var(--sp-1);
  padding:10px 14px;
  border-left:3px solid #E9A23B;
  border-radius:0 var(--r-input) var(--r-input) 0;
  background:rgba(233,162,59,.12);
  font-size:var(--fs-small);
  line-height:1.5;
  color:var(--c-text-primary);
}
/* ----- Classement sur un intranet -----
   Le mode d'emploi vit dans l'écran qui porte le réglage : ce qu'il faut
   coller dans l'intranet est à côté de l'interrupteur qui publie la page,
   pas dans un document qu'il faudrait penser à envoyer. */
.section-help{
  display:inline-flex;
  align-items:center;
  gap:6px;
  margin-top:8px;
  padding:0;
  color:var(--c-primary);
  font-size:var(--fs-small);
  font-weight:600;
  cursor:pointer;
}
.section-help:hover{ text-decoration:underline; }
.section-help svg{ flex:0 0 auto; }
.embed-help{
  padding:var(--sp-4) var(--sp-5);
  border-bottom:1px solid var(--c-grey-light);
  background:var(--c-grey-lightest);
}
.embed-help__step{
  margin:0 0 10px;
  font-size:var(--fs-small);
  line-height:1.55;
}
.embed-help__step:not(:first-child){ margin-top:14px; }
.embed-help__note{
  margin:12px 0 0;
  font-size:var(--fs-xsmall);
  color:var(--c-text-secondary);
  line-height:1.55;
}
.embed-help code{
  padding:1px 5px;
  border-radius:4px;
  background:var(--c-grey-light);
  font-size:.92em;
}
.embed-opts{
  display:flex;
  flex-wrap:wrap;
  gap:var(--sp-3);
  margin-bottom:12px;
}
.embed-opt{
  display:flex;
  flex-direction:column;
  gap:4px;
  min-width:140px;
}
.embed-opt > span{
  font-size:var(--fs-xsmall);
  color:var(--c-text-secondary);
}
/* le code est large et ne se plie pas : il défile horizontalement dans
   son propre cadre plutôt que d'élargir la page */
.embed-snippet{
  margin:0;
  padding:12px 14px;
  overflow-x:auto;
  border:1px solid var(--c-grey-border);
  border-radius:var(--r-input);
  background:var(--c-white);
  font-size:12.5px;
  line-height:1.5;
}
.embed-snippet code{
  padding:0;
  background:none;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  white-space:pre;
}
.embed-help__actions{
  display:flex;
  flex-wrap:wrap;
  gap:var(--sp-2);
  margin-top:10px;
}
/* L'adresse est longue et ses deux boutons vont ensemble : le champ prend
   la ligne entière pour que « Copier » et « Renouveler » se retrouvent
   côte à côte en dessous, plutôt qu'un de chaque côté du retour à la
   ligne. Même dessin que la ligne des domaines autorisés au-dessus. */
.setting-line__value .input.input--wide#embed-url{
  flex:1 1 100%;
  max-width:none;
}

.setting-line__value select:disabled,
.setting-line__value .input:disabled{ opacity:.6; cursor:progress; }
.input,.select,.textarea{
  width:100%;
  height:44px;
  padding:0 14px;
  border:1px solid var(--c-grey-border);
  border-radius:var(--r-input);
  background:var(--c-white);
  color:var(--c-text-primary);
  font-size:var(--fs-body);
  transition:border-color .15s ease, box-shadow .15s ease;
}
.textarea{
  height:auto;
  min-height:82px;
  padding:12px 14px;
  line-height:1.5;
  resize:vertical;
}
.input:focus,.select:focus,.textarea:focus{
  outline:none;
  border-color:var(--c-primary);
  box-shadow:0 0 0 3px var(--c-primary-light);
}
.input.has-error{ border-color:var(--c-red); }
.input:disabled,.input[readonly]{
  background:var(--c-grey-lightest);
  color:var(--c-text-secondary);
  cursor:not-allowed;
}
.field-error{
  display:block;
  font-size:var(--fs-xsmall);
  color:var(--c-red);
  margin-top:7px;
}
.field-hint{
  display:block;
  font-size:var(--fs-xsmall);
  color:var(--c-text-secondary);
  margin-top:7px;
}
.select{
  appearance:none;
  -webkit-appearance:none;
  cursor:pointer;
  padding-right:38px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1.5L6 6.5L11 1.5' stroke='%23605E74' stroke-width='1.7' fill='none' stroke-linecap='round'/></svg>");
  background-repeat:no-repeat;
  background-position:right 14px center;
}

/* ---------------------------------------------------------
   Card grid, flex-wrap
   The per-column widths are defined in layout.css
   --------------------------------------------------------- */
.grid{ display:flex; flex-wrap:wrap; gap:var(--gap); }
.grid > .grid-item{ width:100%; }

.card{
  display:flex;
  flex-direction:column;
  height:100%;
  gap:10px;
  background:var(--c-white);
  border:1px solid var(--c-grey-border);
  border-radius:var(--r-card);
  overflow:hidden;
  transition:box-shadow .18s ease;
}
.card:hover,
.card:focus-within{ box-shadow:var(--shadow-hover); }

/* the button only wraps the "open detail" part (thumbnail + caption):
   the like stays a button of its own in .card__foot, so a button is
   never nested inside another */
.card__open{
  display:flex;
  flex-direction:column;
  width:100%;
  outline:none;
}

.thumb{
  position:relative;
  width:100%;
  padding-top:72%;
  background:var(--c-grey-lightest);
  overflow:hidden;
}
.thumb img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  transition:transform .25s ease;
}
.card__open:hover .thumb img{ transform:scale(1.04); }
.thumb__badge{ position:absolute; top:10px; left:10px; }

/* Blur placeholder: a 20px version of the photo, inlined in the feed
   payload (cf blurDataUrl) and set as the frame's background, so a card
   shows the shape of its photo instead of a grey box while the real
   thumbnail downloads. It sits behind the <img>, which simply covers it
   once painted. */
.thumb--blur,
.mini-thumb--blur{
  background-size:cover;
  background-position:center;
}

/* ---------------------------------------------------------
   Loading: animated skeleton
   .thumb--loading dresses up both .thumb and .mini-thumb (the
   ranking/moderation thumbnail) until the photo payload — and with it
   the thumbnail URLs — has arrived. Photos uploaded before the blur
   placeholder existed keep it for the download itself. */
.thumb--loading{
  background:linear-gradient(100deg,
    var(--c-grey-lightest) 30%, var(--c-grey-light) 50%, var(--c-grey-lightest) 70%);
  background-size:200% 100%;
  animation:sk-shimmer 1.4s ease-in-out infinite;
}
.thumb--loading img{ opacity:0; }

.card--skeleton{ pointer-events:none; }
.sk-bar{
  height:12px;
  border-radius:var(--r-input);
  background:linear-gradient(100deg,
    var(--c-grey-lightest) 30%, var(--c-grey-light) 50%, var(--c-grey-lightest) 70%);
  background-size:200% 100%;
  animation:sk-shimmer 1.4s ease-in-out infinite;
}
/* the avatar during the instant before GET /me answers (cf viewNavbar):
   same shimmer as the bars, in the disc the initials will take */
.avatar--pending{
  background:linear-gradient(100deg,
    var(--c-grey-lightest) 30%, var(--c-grey-light) 50%, var(--c-grey-lightest) 70%);
  background-size:200% 100%;
  animation:sk-shimmer 1.4s ease-in-out infinite;
}
.sk-bar--title{ width:80%; height:15px; }
.sk-bar--button{ width:100%; height:44px; border-radius:var(--r-btn); }
.sk-bar--meta{ width:45%; }
.card__foot .sk-bar--tag{ width:70px; }
.card__foot .sk-bar--like{ width:40px; }

@keyframes sk-shimmer{
  0%{ background-position:200% 0; }
  100%{ background-position:-200% 0; }
}

@media (prefers-reduced-motion:reduce){
  .thumb--loading, .sk-bar, .avatar--pending, .btn--loading::after,
  .lightbox__spin, .upload-progress.is-indeterminate .upload-progress__fill{
    animation:none;
  }
}

.card__top{
  display:flex;
  flex-direction:column;
  gap:10px;
  padding:14px var(--sp-4) 0;
}
.card__caption{ font-size:var(--fs-body); font-weight:600; line-height:1.35; }
.card__meta{
  display:flex;
  align-items:center;
  gap:var(--sp-2);
  flex-wrap:wrap;
  font-size:var(--fs-xsmall);
  color:var(--c-text-secondary);
}
.card__foot{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  margin-top:auto;
  padding:0 var(--sp-4) var(--sp-4);
}
.dot{
  width:3px; height:3px;
  border-radius:var(--r-pill);
  background:var(--c-grey-darkest);
  flex:0 0 auto;
  display:inline-block;
}

/* square thumbnail reused by the ranking and by moderation */
.mini-thumb{
  width:64px; height:48px;
  border-radius:var(--r-input);
  overflow:hidden;
  flex:0 0 auto;
  background:var(--c-grey-lightest);
}
.mini-thumb img{ width:100%; height:100%; object-fit:cover; }

/* wrapper that turns a thumbnail into the button opening the photo,
   where the row itself can't be one: the moderation and Photos rows
   carry their own action buttons, the race rows are animated. */
.thumb-open{
  display:block;
  flex:0 0 auto;
  line-height:0;
  border-radius:var(--r-input);
}

/* ---------------------------------------------------------
   Bouton like
   --------------------------------------------------------- */
.like-btn{
  display:inline-flex;
  align-items:center;
  gap:7px;
  height:36px;
  padding:0 14px;
  border:1px solid var(--c-grey-border);
  border-radius:var(--r-pill);
  background:var(--c-white);
  color:var(--c-text-secondary);
  font-size:var(--fs-small);
  font-weight:600;
  cursor:pointer;
  transition:background .15s ease, border-color .15s ease, color .15s ease;
}
/* the ink of the celebration (cf js/burst.js). A custom property
   rather than `color`, which is mid-transition when the animation
   starts: getComputedStyle would still read the colour from before
   the like. */
.like-btn{ --burst-ink:var(--c-green-dark); }
.like-btn svg{ width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:1.9; }
.like-btn:hover:not(:disabled){ border-color:var(--c-green); color:var(--c-green-dark); }
.like-btn.is-liked{
  background:var(--c-green-soft);
  border-color:transparent;
  color:var(--c-green-dark);
}
.like-btn.is-liked svg{ fill:currentColor; stroke:currentColor; }
.like-btn:disabled{
  background:var(--c-grey-lightest);
  border-color:var(--c-grey-light);
  color:var(--c-grey-darkest);
  cursor:not-allowed;
}
/* counter only: votes closed, or the current user's own photo (see
   likeButton() in render.js). Same metrics, nothing clickable. */
/* A like whose server round-trip has not answered yet: the heart
   already shows the optimistic state, this only explains why a second
   click does nothing (see store.isLikePending). */
.like-btn.is-pending{ cursor:progress; }
.like-btn--static{ cursor:default; }
.like-btn--static:hover{ border-color:var(--c-grey-border); color:var(--c-text-secondary); }
.like-btn--static.is-liked:hover{ border-color:transparent; color:var(--c-green-dark); }

/* ---------------------------------------------------------
   Avatar, menu utilisateur
   --------------------------------------------------------- */
.avatar{
  width:38px; height:38px;
  border-radius:var(--r-pill);
  background:var(--c-primary-light);
  color:var(--c-primary);
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:var(--fs-small);
  font-weight:600;
  flex:0 0 auto;
}
.dropdown{
  position:absolute;
  top:calc(100% + 10px);
  right:0;
  min-width:236px;
  padding:var(--sp-2);
  background:var(--c-white);
  border-radius:var(--r-card);
  box-shadow:var(--shadow-drop);
  z-index:160;
}
.dropdown__head{
  padding:10px var(--sp-3) var(--sp-3);
  border-bottom:1px solid var(--c-grey-light);
  margin-bottom:6px;
}
.dropdown__head b{ display:block; font-size:var(--fs-small); }
.dropdown__head span{
  display:block;
  font-size:12px;
  color:var(--c-text-secondary);
  word-break:break-all;
}
.dropdown__item{
  display:flex;
  width:100%;
  align-items:center;
  gap:9px;
  padding:9px var(--sp-3);
  border:none;
  background:transparent;
  border-radius:var(--r-btn);
  font-size:var(--fs-small);
  color:var(--c-text-primary);
  text-align:left;
  cursor:pointer;
}
.dropdown__item:hover{ background:var(--c-grey-lightest); }

/* bascule de thème du menu profil : même trait de séparation que
   l'en-tête, la bascule occupe toute la largeur du menu */
.dropdown__pref{
  display:flex;
  flex-direction:column;
  gap:6px;
  padding:2px var(--sp-3) 10px;
  margin-bottom:6px;
  border-bottom:1px solid var(--c-grey-light);
}
.dropdown__pref-label{
  font-size:12px;
  color:var(--c-text-secondary);
}
.dropdown__pref .view-toggle{ display:flex; width:100%; }
.dropdown__pref .view-toggle button{ flex:1 1 0; padding:0; }

/* ---------------------------------------------------------
   Cartouches de statistiques
   --------------------------------------------------------- */
.stat-row{ display:flex; flex-wrap:wrap; gap:var(--gap); margin-bottom:var(--sp-5); }
.stat{
  flex:1 1 160px;
  padding:var(--sp-4) 18px;
  background:var(--c-white);
  border-radius:var(--r-card);
  box-shadow:var(--shadow-nav);
}
.stat b{ display:block; font-size:var(--fs-page); font-weight:700; line-height:1.2; }
.stat span{ display:block; font-size:var(--fs-xsmall); color:var(--c-text-secondary); margin-top:3px; }

/* The figure of a tile is a node of its own now (cf render.counter) and
   it sits inside the <b>, where .stat span — the label underneath —
   caught it: 12px, uppercase, grey, and on its own line, which also
   pushed the ordinal suffix of "3e" down under it. The display matters
   twice over: a block as wide as the tile makes the pop slide sideways,
   a scale being centred on the box and not on the digits.

   Three classes on purpose: the v2 layer styles .stat span too, and a
   weaker selector would lose to it whatever the order of the files. */
.stat .count[data-count]{
  display:inline-block;
  margin:0;
  font:inherit;
  font-variant-numeric:tabular-nums;
  letter-spacing:inherit;
  text-transform:none;
  color:inherit;
}

/* ---------------------------------------------------------
   Modales
   --------------------------------------------------------- */
.modal-backdrop{
  position:fixed;
  inset:0;
  z-index:300;
  background:rgba(25,3,64,.45);
  display:flex;
  align-items:center;
  justify-content:center;
  padding:var(--sp-6);
}
.modal{
  display:flex;
  flex-direction:column;
  width:100%;
  max-width:750px;
  max-height:90vh;
  overflow:hidden;
  background:var(--c-white);
  border-radius:var(--r-input);
  box-shadow:var(--shadow-hover);
}
.modal--sm{ max-width:520px; }
.modal__head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:var(--sp-4);
  padding:18px var(--sp-6);
  border-bottom:1px solid var(--c-grey-light);
  flex:0 0 auto;
}
.modal__head h3{ font-size:18px; font-weight:600; }
.modal__close{
  width:34px; height:34px;
  border:none;
  border-radius:var(--r-btn);
  background:transparent;
  color:var(--c-text-secondary);
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  flex:0 0 auto;
}
.modal__close:hover{ background:var(--c-grey-lightest); }
.modal__body{ padding:var(--sp-6); overflow-y:auto; flex:1; }
.modal__body--flush{ padding:0; }
.modal__foot{
  display:flex;
  flex-direction:row-reverse;
  align-items:center;
  /* la barre de progression de l'envoi occupe sa propre ligne, au-dessus
     des boutons (cf .upload-progress) */
  flex-wrap:wrap;
  gap:var(--sp-3);
  padding:var(--sp-4) var(--sp-6);
  border-top:1px solid var(--c-grey-light);
  flex:0 0 auto;
}
.modal__foot-note{
  margin-right:auto;
  font-size:var(--fs-xsmall);
  color:var(--c-text-secondary);
}

/* ---------------------------------------------------------
   File drop zone and preview
   --------------------------------------------------------- */
.drop{
  padding:28px var(--sp-5);
  border:1.5px dashed var(--c-grey-border);
  border-radius:var(--r-card);
  background:var(--c-grey-lightest);
  text-align:center;
  cursor:pointer;
  transition:background .15s ease, border-color .15s ease;
}
.drop:hover,.drop.is-over{ border-color:var(--c-primary); background:var(--c-primary-light); }
.drop svg{
  width:34px; height:34px;
  stroke:var(--c-primary);
  fill:none;
  stroke-width:1.6;
  margin:0 auto 10px;
}
.drop b{ display:block; font-size:var(--fs-body); font-weight:600; }
.drop span{ display:block; font-size:var(--fs-xsmall); color:var(--c-text-secondary); margin-top:5px; }

.preview{
  position:relative;
  border-radius:var(--r-card);
  overflow:hidden;
  background:var(--c-grey-lightest);
}
.preview img{ width:100%; max-height:280px; object-fit:cover; }
.preview__change{
  position:absolute;
  top:var(--sp-3);
  right:var(--sp-3);
  height:34px;
  padding:0 14px;
  border:none;
  border-radius:var(--r-btn);
  background:rgba(255,255,255,.94);
  color:var(--c-text-primary);
  font-size:var(--fs-small);
  font-weight:600;
  cursor:pointer;
}

/* Photo picked but not decoded for the preview yet: the box already has
   the final preview's metrics, so the layout does not jump when the
   image lands. Also the fallback when no preview is possible, which
   never blocks the upload itself. */
.preview--pending .preview__placeholder{
  width:100%;
  height:220px;
  background:var(--c-grey-lightest);
}
.preview__name{
  position:absolute;
  left:var(--sp-3);
  bottom:var(--sp-3);
  max-width:calc(100% - 2 * var(--sp-3));
  padding:5px 10px;
  border-radius:var(--r-btn);
  background:rgba(255,255,255,.94);
  color:var(--c-text-primary);
  font-size:var(--fs-xsmall);
  font-weight:600;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

/* Upload progress: the only genuinely long wait in the application (the
   original goes straight to R2 in a PUT). The bar is measured during the
   PUT and indeterminate during the two API round-trips around it — one
   of which is the confirm, where the thumbnails are generated. */
.upload-progress{
  position:relative;
  flex:1 0 100%;
  display:flex;
  flex-direction:column;
  gap:6px;
}
/* .upload-progress sets display:flex, which would win over the default
   display:none of [hidden] */
.upload-progress[hidden]{ display:none; }
.upload-progress__track{
  position:relative;
  width:100%;
  height:6px;
  border-radius:var(--r-pill);
  background:var(--c-grey-light);
  overflow:hidden;
}
.upload-progress__fill{
  display:block;
  height:100%;
  width:0;
  border-radius:var(--r-pill);
  background:var(--c-primary);
  transition:width .2s ease;
}
.upload-progress.is-indeterminate .upload-progress__fill{
  width:35% !important;
  transition:none;
  animation:upload-sweep 1.1s ease-in-out infinite;
}
.upload-progress__label{
  font-size:var(--fs-xsmall);
  color:var(--c-text-secondary);
}

/* The tick that closes the wait, drawn stroke by stroke once the photo
   is really in (cf motion.drawCheck). Placed on the label's line, at the
   far end of the bar it completes. */
.upload-progress__check{
  position:absolute;
  right:0;
  bottom:0;
  display:none;
  width:18px;
  height:18px;
  color:var(--c-green-text);
}
.upload-progress.is-done .upload-progress__check{ display:block; }
.upload-progress.is-done .upload-progress__fill{ background:var(--c-green); }
.upload-progress__check svg{
  width:100%;
  height:100%;
  fill:none;
  stroke:currentColor;
  stroke-width:2.6;
  stroke-linecap:round;
  stroke-linejoin:round;
}
@keyframes upload-sweep{
  0%{ transform:translateX(-100%); }
  100%{ transform:translateX(300%); }
}

/* ---------------------------------------------------------
   Detail lightbox: full-screen viewer, dark background, caption
   and actions overlaid at the bottom. Reuses .modal-backdrop for
   the background and for closing on an outside click.
   --------------------------------------------------------- */
.modal-backdrop--lightbox{ background:rgba(10,4,26,.92); }
.lightbox{
  position:relative;
  display:flex;
  flex-direction:column;
  width:100%;
  max-width:1100px;
  max-height:92vh;
  border-radius:var(--r-input);
  overflow:hidden;
  background:#0F0526;
  box-shadow:var(--shadow-hover);
}
.lightbox__stage{
  position:relative;
  flex:1;
  min-height:0;
  display:flex;
  align-items:center;
  justify-content:center;
  background:#0F0526;
}
/* positioned: without this the image (inline, unpositioned content) is
   painted BEFORE the stage's absolute ::before veil, and therefore ends
   up permanently blurred */
.lightbox__img{
  position:relative;
  z-index:1;
  max-width:100%;
  max-height:70vh;
  object-fit:contain;
}

.lightbox__close,
.lightbox__nav{
  position:absolute;
  z-index:2;
  border:0;
  border-radius:var(--r-pill);
  background:rgba(255,255,255,.14);
  color:var(--c-white);
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  outline:none;
  transition:background .15s ease;
}
.lightbox__close:hover,
.lightbox__nav:hover{ background:rgba(255,255,255,.24); }
body.using-keyboard .lightbox__close:focus,
body.using-keyboard .lightbox__nav:focus{ outline:2px solid var(--c-white); outline-offset:2px; }
.lightbox__close{ top:var(--sp-4); right:var(--sp-4); width:38px; height:38px; }
.lightbox__nav{
  top:50%;
  transform:translateY(-50%);
  width:44px; height:44px;
  font-size:24px;
  line-height:1;
}
.lightbox__nav--prev{ left:var(--sp-4); }
.lightbox__nav--next{ right:var(--sp-4); }
.lightbox__nav[aria-disabled="true"]{ opacity:.3; cursor:default; }
.lightbox__nav[aria-disabled="true"]:hover{ background:rgba(255,255,255,.14); }

.lightbox__img,
.lightbox__info{ transition:opacity .16s ease; }
.lightbox__img.is-swapping,
.lightbox__info.is-swapping{ opacity:0; }

/* The 1200px variant is not the one the card displayed: opening or
   navigating waits on a real download. The stage carries the same 20px
   blurred placeholder as the cards, plus a spinner, until the image is
   actually painted (see watchLightboxImage). */
.lightbox__stage{
  background-size:cover;
  background-position:center;
}
/* veil over the placeholder: without it, the bands left by a photo that
   does not fill the stage would show a raw blow-up of the 20px blur. It
   lightens while loading, the moment that placeholder is precisely all
   there is to look at. */
.lightbox__stage::before{
  content:'';
  position:absolute;
  inset:0;
  background:rgba(15,5,38,.72);
  -webkit-backdrop-filter:blur(18px);
  backdrop-filter:blur(18px);
  transition:background .16s ease;
  pointer-events:none;
}
.lightbox__stage.is-loading::before{ background:rgba(15,5,38,.5); }
.lightbox__stage.is-loading .lightbox__img{ opacity:0; }
.lightbox__spin{
  position:absolute;
  top:50%; left:50%;
  width:28px; height:28px;
  margin:-14px 0 0 -14px;
  border:2px solid rgba(255,255,255,.3);
  border-top-color:var(--c-white);
  border-radius:50%;
  opacity:0;
  z-index:3;
  pointer-events:none;
  animation:btn-spin .7s linear infinite;
}
.lightbox__stage.is-loading .lightbox__spin{ opacity:1; }

.lightbox__info{
  position:relative;
  z-index:1;
  padding:var(--sp-5) var(--sp-6);
  padding-bottom:max(var(--sp-5), env(safe-area-inset-bottom));
  background:#0F0526;
  color:var(--c-white);
}
.lightbox__tags{ display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin-bottom:10px; }
.lightbox__pos{ font-size:var(--fs-xsmall); color:rgba(255,255,255,.68); margin-left:auto; }
.lightbox__caption{ font-size:var(--fs-section); font-weight:700; margin-bottom:8px; }
.lightbox__meta{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap;
  font-size:var(--fs-small);
  color:rgba(255,255,255,.78);
  margin-bottom:14px;
}
.lightbox__actions{ display:flex; align-items:center; gap:var(--sp-3); flex-wrap:wrap; }
.lightbox__quota{ font-size:var(--fs-xsmall); color:rgba(255,255,255,.68); }

/* the pastel badges are designed for a white background: on the dark
   lightbox panel the same hues are kept, but opaque, so they stay
   readable */
.lightbox__info .badge--green{ background:#DFF5DC; }
.lightbox__info .badge--orange{ background:#FFE9CC; }
.lightbox__info .badge--red{ background:#FBDADA; }
.lightbox__info .cat-tag{ background:#DCE6FA; }

.reason-box{
  display:flex;
  flex-direction:column;
  gap:var(--sp-2);
  padding:14px;
  margin-bottom:14px;
  border-radius:var(--r-btn);
  background:var(--c-grey-lightest);
}
.reason-box b{ font-size:var(--fs-small); }
.reason-box span{ font-size:var(--fs-small); color:var(--c-text-secondary); }
.lightbox__info .reason-box{ background:rgba(255,255,255,.08); }
.lightbox__info .reason-box b{ color:var(--c-white); }
.lightbox__info .reason-box span{ color:rgba(255,255,255,.75); }

@media (prefers-reduced-motion: reduce){
  .lightbox__img,
  .lightbox__info{ transition:none; }
  .card__open:hover .thumb img{ transition:none; }
}

/* ---------------------------------------------------------
   Notifications
   --------------------------------------------------------- */
.toast-zone{
  position:fixed;
  top:calc(var(--nav-h) + var(--demo-h) + 14px);
  left:0;
  right:0;
  z-index:500;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:var(--sp-2);
  pointer-events:none;
}
.toast{
  max-width:90vw;
  padding:11px 18px;
  border-radius:var(--r-btn);
  background:var(--c-text-primary);
  color:var(--c-white);
  font-size:var(--fs-small);
  font-weight:500;
  box-shadow:var(--shadow-drop);
  animation:toast-in .22s ease;
}
.toast--ok{ background:var(--c-green-dark); }
.toast--err{ background:var(--c-red); }
@keyframes toast-in{
  from{ opacity:0; transform:translateY(-8px); }
  to{ opacity:1; transform:none; }
}

/* ---------------------------------------------------------
   Pagination : bouton "charger plus"
   --------------------------------------------------------- */
.load-more{ display:flex; justify-content:center; margin-top:26px; }

/* ---------------------------------------------------------
   Célébration du like (cf js/burst.js)

   The layer sits under <body>, outside the redrawn tree: neither the
   morph nor the lightbox's innerHTML can wipe it out mid-animation.
   It is inert to the pointer, below the toasts (500) and above the
   lightbox (300), so liking from the full-screen view still shows it.
   --------------------------------------------------------- */
#burst-layer{
  position:fixed;
  inset:0;
  z-index:400;
  pointer-events:none;
  overflow:hidden;
}
#burst-layer > *{
  position:absolute;
  will-change:transform, opacity;
}
.burst__ring{
  width:38px; height:38px;
  border-radius:var(--r-pill);
  border:1px solid currentColor;
}
.burst__spark{ border-radius:var(--r-pill); }
.burst__heart{ line-height:0; }
.burst__heart svg{
  width:16px; height:16px;
  fill:currentColor;
  stroke:none;
}

/* ---------------------------------------------------------
   Animated figures
   A counter carries its value in data-count, and motion.js rolls or
   pops it when that value changes (cf motion.countUps).
   inline-block because a transform does nothing on a plain inline box,
   and tabular figures so a rolling number keeps its width instead of
   shuffling the line it sits on.
   --------------------------------------------------------- */
[data-count]{
  display:inline-block;
  font-variant-numeric:tabular-nums;
}

/* =========================================================
   Onglet Vidéos : les films de présentation
   Chaque carte porte son lecteur. Le cadre est en 16:9 avec
   `object-fit:contain` : les films existent en paysage et en
   portrait, et laisser le lecteur imposer sa hauteur ferait
   sauter la grille d'une carte à l'autre.
   ========================================================= */
.media-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(320px,1fr));
  gap:var(--gap);
  padding:var(--sp-5);
}
.media-card{
  display:flex;
  flex-direction:column;
  border:1px solid var(--c-grey-border);
  border-radius:var(--r-card);
  overflow:hidden;
  background:var(--c-white);
}
.media-card__frame{
  aspect-ratio:16/9;
  background:var(--c-background);
}
.media-card__frame video{
  display:block;
  width:100%;
  height:100%;
  object-fit:contain;
  background:transparent;
}
.media-card__foot{
  display:flex;
  flex-direction:column;
  gap:4px;
  padding:var(--sp-4) var(--sp-5) var(--sp-5);
}
.media-card__foot b{ font-size:var(--fs-section); }
.media-card__foot span{ font-size:var(--fs-small); color:var(--c-text-secondary); }
.media-card__save{ align-self:flex-start; margin-top:var(--sp-3); }
.media-empty{
  padding:var(--sp-6) var(--sp-5);
  color:var(--c-text-secondary);
}
