/* =========================================================
   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: base container for every content block
   --------------------------------------------------------- */
.sc-section{
  background:var(--c-white);
  border-radius:var(--r-card);
  overflow:hidden;
  box-shadow:var(--shadow-nav);
}
/* Two consecutive blocks must read as two blocks. No rule separated
   them: they touched edge to edge, and the second one's title passed for
   a subheading of the first — visible on the palmarès (podium, race,
   prize) as well as in the Réglages tab. The gap is the one the other
   blocks on these screens already use (.recap, .podium-top). */
.sc-section + .sc-section,
.sc-section + .prize,
.prize + .sc-section,
.prize + .prize,
/* the ranking in card view is a bare .grid, and the block now closes that
   screen: without this it sat flush against the last row of thumbnails */
.grid + .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);
}

/* ---------------------------------------------------------
   Buttons
   --------------------------------------------------------- */
.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); } }

/* ---------------------------------------------------------
   Status badges
   --------------------------------------------------------- */
.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; }

/* ---------------------------------------------------------
   Filter chips
   --------------------------------------------------------- */
.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; }

/* ---------------------------------------------------------
   Form fields
   --------------------------------------------------------- */
.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);
}
/* The consequence of a setting, stated before it is saved: a second
   announcement email, or a reveal nobody will hear about. Placed under
   the whole row, not in the right-hand column, so it fits in a single
   readable sentence. */
.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);
}
/* ----- Ranking on an intranet -----
   The instructions live on the screen that holds the setting: what has
   to be pasted into the intranet sits next to the toggle that publishes
   the page, not in a document someone would have to remember to send. */
.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);
}
/* the code is wide and does not wrap: it scrolls horizontally inside
   its own frame rather than widening the 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;
}
/* The address is long and its two buttons belong together: the field
   takes the whole row so that « Copier » and « Renouveler » end up side
   by side underneath, rather than one on each side of the line break.
   Same layout as the allowed-domains row above. */
.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);
}

/* ---------------------------------------------------------
   Like button
   --------------------------------------------------------- */
.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, user menu
   --------------------------------------------------------- */
.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); }

/* theme toggle in the profile menu: same divider as the header, the
   toggle spans the full width of the 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; }

/* ---------------------------------------------------------
   Stat tiles
   --------------------------------------------------------- */
.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;
}

/* ---------------------------------------------------------
   Modals
   --------------------------------------------------------- */
.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;
  /* the upload progress bar takes a line of its own, above the buttons
     (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: "load more" button
   --------------------------------------------------------- */
.load-more{ display:flex; justify-content:center; margin-top:26px; }

/* ---------------------------------------------------------
   Like celebration (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;
}

/* =========================================================
   Communication tab: the presentation films
   Each card carries its own player. The frame is 16:9 with
   `object-fit:contain`: the films exist in landscape and in
   portrait, and letting the player set its own height would
   make the grid jump from one card to the next.
   ========================================================= */
.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);
}
