/* ==========================================================================
   Reveal mosaic hero 1.0 — full-bleed hero behind an erasable mosaic
   --------------------------------------------------------------------------
   Plain CSS. No framework, no build step, no external request.

   The photo sits in the background. Over it, a <canvas> paints a mosaic of
   opaque squares that hides most of it, leaving a clearing where the picture
   shows through. Moving the pointer erases squares under a round brush, and
   they grow back behind it. So the picture is never inside the canvas — the
   canvas only ever holds the squares that hide it.

   Without the script the canvas stays empty, so a plain gradient takes over
   to keep the text readable. See "9. Fallbacks".

   PROPORTIONS
   Every metric derives from --ph-font-size, which is the size of the link
   text. The ratios were measured on the live original — getBoundingClientRect
   for the boxes, getComputedStyle for the type, and the mosaic was verified
   cell by cell against the original canvas (README.md, "Provenance"):

     link text        1                (the reference unit)
     padding          4.875 em         (78 px, 2.75 em / 44 px when narrow)
     block max-width  42.5 em          (680 px)
     headline         5.25 em          (84 px, weight 400)
     headline leading 0.95             (79.8 px)
     headline tracking -0.05 em        (-4.2 px)
     headline to link 1.375 em         (22 px, 1.125 em / 18 px when narrow)
     arrow icon       0.875 em         (14 px)
     icon to label    0.5 em           (8 px)
     link to rule     0.75 em          (12 px)
     rule minimum     1.875 em         (30 px), 1 px tall, 45 % opaque
     mosaic cell      3.3125 em        (53 px, capped at 64 px when narrow)
     brush radius     4.1875 em        (67 px, so a 134 px ring)

   So changing --ph-font-size rescales the whole component, mosaic included,
   and nothing else needs to be touched.

   Metrics that feed a calculation are written calc(ratio * font-size) rather
   than in em: a custom property in em resolves against whoever consumes it,
   so --ph-marge read from the 84 px headline would come out five times too
   big. As an absolute value it is 78 px everywhere.

   Order of this file:
     1. Face declarations
     2. Design tokens
     3. Shell: photo, mosaic, no-script veil
     4. Content: headline, link, rule
     5. Brush indicator
     6. Measuring probe
     7. Variants
     8. Narrow screens
     9. Fallbacks: no script, reduced motion, forced colours

   Naming follows BEM: .reveal-mosaic-hero, .reveal-mosaic-hero__part, .reveal-mosaic-hero--variant.
   ========================================================================== */

/* ------------------------------------------------- 1. Face declarations --- */

@font-face {
  font-family: "Reveal mosaic hero Inter";
  src: url("Inter-400-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Reveal mosaic hero Inter";
  src: url("Inter-400-latin-ext.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Reveal mosaic hero Inter";
  src: url("Inter-600-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Reveal mosaic hero Inter";
  src: url("Inter-600-latin-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------ 2. Design tokens --- */

.reveal-mosaic-hero {
  /* The one variable that rescales everything. */
  --ph-font-size: 16px;

  /* Colours */
  --ph-texte: #000;
  --ph-fond: #fff;
  --ph-mosaique: #fff;
  --ph-indicateur: rgb(255 255 255 / 0.55);
  --ph-pastille: #fff;

  /* Photo. Override --ph-photo from your own sheet to change the image: the
     component never sets a style attribute, the CSP forbids it. */
  --ph-photo: url("fond.webp");
  --ph-cadrage: 50% 50%;
  --ph-retouche: saturate(1.02) contrast(1.02);

  /* Box */
  --ph-hauteur: 100svh;
  --ph-marge: calc(4.875 * var(--ph-font-size));
  --ph-bloc-max: calc(42.5 * var(--ph-font-size));

  /* Type */
  --ph-titre: calc(5.25 * var(--ph-font-size));
  --ph-titre-interligne: 0.95;
  --ph-titre-approche: -0.05em;
  --ph-titre-graisse: 400;
  --ph-lien-interligne: 1.2;
  --ph-lien-approche: -0.01em;
  --ph-lien-graisse: 600;
  --ph-ecart-titre: calc(1.375 * var(--ph-font-size));
  --ph-icone: calc(0.875 * var(--ph-font-size));
  --ph-ecart-icone: calc(0.5 * var(--ph-font-size));
  --ph-ecart-filet: calc(0.75 * var(--ph-font-size));
  --ph-filet-min: calc(1.875 * var(--ph-font-size));
  --ph-filet-opacite: 0.45;

  /* Mosaic and brush. The script reads these lengths and the two durations
     from the probe in part 6, so they are tuned here, in CSS. */
  --ph-cellule: calc(3.3125 * var(--ph-font-size));
  --ph-rayon: calc(4.1875 * var(--ph-font-size));
  /* Bounds of the radius when the handle is dragged. */
  --ph-rayon-min: calc(2.5 * var(--ph-font-size));
  --ph-rayon-max: calc(32.5 * var(--ph-font-size));
  /* Below this, the mosaic takes its tighter form. It is the original's own
     threshold, and the one the media query in part 8 uses. */
  --ph-seuil-etroit: 420px;

  /* Durations */
  --ph-trainee: 520ms;
  --ph-fondu: 200ms;
  --ph-entree-titre: 600ms;
  --ph-entree-pied: 650ms;
  --ph-entree-delai: 60ms;
  --ph-entree-courbe: cubic-bezier(0.22, 1, 0.36, 1);
  --ph-montee-titre: calc(0.625 * var(--ph-font-size));
  --ph-montee-pied: calc(0.5 * var(--ph-font-size));

  /* Written by the script: brush position and current radius. The starting
     values keep the sheet valid until the script has run. */
  --ph-x: 50%;
  --ph-y: 50%;
  --ph-rayon-actuel: var(--ph-rayon);

  /* The headline stays at 84 px for as long as it fits. Below roughly 390 px
     it shrinks rather than get clipped: "Creative" is 3.52 times its own font
     size, and 3.6 leaves a margin. The original keeps 84 px and cuts the word
     in half. */
  --ph-titre-plafond: calc((100vw - 2 * var(--ph-marge)) / 3.6);
}

/* ------------------------------------- 3. Shell: photo, mosaic, no-script --- */

.reveal-mosaic-hero,
.reveal-mosaic-hero *,
.reveal-mosaic-hero *::before,
.reveal-mosaic-hero *::after {
  box-sizing: border-box;
}

.reveal-mosaic-hero {
  position: relative;
  width: 100%;
  min-height: var(--ph-hauteur);
  overflow: hidden;
  background-color: var(--ph-fond);
  color: var(--ph-texte);
  font-family: "Reveal mosaic hero Inter", "Inter", system-ui, -apple-system, sans-serif;
  font-size: var(--ph-font-size);
  /* The brush is driven by touch as well: without this, the gesture would
     scroll the page instead. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

/* With interaction off, the section has no reason to capture the gesture. */
.reveal-mosaic-hero[data-ph-interaction="off"] {
  touch-action: auto;
}

.reveal-mosaic-hero__photo {
  position: absolute;
  inset: 0;
  background-image: var(--ph-photo);
  background-size: cover;
  background-position: var(--ph-cadrage);
  background-repeat: no-repeat;
  filter: var(--ph-retouche);
}

.reveal-mosaic-hero__voile {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Squares must stay squares: no smoothing when the canvas is scaled. */
  image-rendering: pixelated;
}

/* -------------------------------------- 4. Content: headline, link, rule --- */

.reveal-mosaic-hero__contenu {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  width: 100%;
  min-height: var(--ph-hauteur);
  padding: var(--ph-marge);
}

.reveal-mosaic-hero__bloc {
  max-width: var(--ph-bloc-max);
}

.reveal-mosaic-hero__titre {
  margin: 0;
  font-size: min(var(--ph-titre), var(--ph-titre-plafond));
  font-weight: var(--ph-titre-graisse);
  line-height: var(--ph-titre-interligne);
  letter-spacing: var(--ph-titre-approche);
  /* A word longer than the column breaks instead of overflowing. */
  overflow-wrap: break-word;
}

.reveal-mosaic-hero__pied {
  display: flex;
  align-items: center;
  gap: var(--ph-ecart-filet);
  /* The footer row is a <p>: its default bottom margin would make the block
     sixteen pixels taller, and the component assumes no reset. */
  margin: var(--ph-ecart-titre) 0 0;
  opacity: 0.95;
}

.reveal-mosaic-hero__lien {
  display: inline-flex;
  align-items: center;
  gap: var(--ph-ecart-icone);
  color: inherit;
  font-size: 1em;
  font-weight: var(--ph-lien-graisse);
  line-height: var(--ph-lien-interligne);
  letter-spacing: var(--ph-lien-approche);
  text-decoration: none;
  white-space: nowrap;
}

.reveal-mosaic-hero__lien:hover {
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.reveal-mosaic-hero__lien:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
  border-radius: 2px;
}

.reveal-mosaic-hero__fleche {
  display: inline-flex;
  align-items: center;
  flex: none;
}

.reveal-mosaic-hero__fleche svg {
  display: block;
  width: var(--ph-icone);
  height: var(--ph-icone);
}

.reveal-mosaic-hero__libelle {
  min-width: max-content;
}

.reveal-mosaic-hero__filet {
  flex: 1;
  min-width: var(--ph-filet-min);
  height: 1px;
  background-color: currentColor;
  opacity: var(--ph-filet-opacite);
  /* The rule sits a hair too high optically: the original drops it by one
     pixel to line it up with the label's baseline. */
  transform: translateY(1px);
}

/* Text entrance: both blocks rise as they fade in, the link just after the
   headline. Each keyframe describes only its starting state, so "backwards"
   leaves the end state to the CSS rule — which is what preserves the 0.95
   opacity of the footer row and the one-pixel translateY of the rule. */
@keyframes reveal-mosaic-hero-titre {
  from {
    opacity: 0;
    translate: 0 var(--ph-montee-titre);
  }
}

@keyframes reveal-mosaic-hero-pied {
  from {
    opacity: 0;
    translate: 0 var(--ph-montee-pied);
  }
}

.reveal-mosaic-hero__titre {
  animation: reveal-mosaic-hero-titre var(--ph-entree-titre) var(--ph-entree-courbe) backwards;
}

.reveal-mosaic-hero__pied {
  animation: reveal-mosaic-hero-pied var(--ph-entree-pied) var(--ph-entree-delai)
    var(--ph-entree-courbe) backwards;
}

/* -------------------------------------------------- 5. Brush indicator --- */

.reveal-mosaic-hero__pointeur {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--ph-fondu) ease;
}

.reveal-mosaic-hero[data-ph-actif] .reveal-mosaic-hero__pointeur {
  opacity: 1;
}

.reveal-mosaic-hero__cercle {
  position: absolute;
  left: var(--ph-x);
  top: var(--ph-y);
  width: calc(2 * var(--ph-rayon-actuel));
  height: calc(2 * var(--ph-rayon-actuel));
  margin: calc(-1 * var(--ph-rayon-actuel));
  border: 1px solid var(--ph-indicateur);
  border-radius: 999px;
}

.reveal-mosaic-hero__poignee {
  position: absolute;
  left: calc(var(--ph-x) + var(--ph-rayon-actuel));
  top: var(--ph-y);
  width: var(--ph-icone);
  height: var(--ph-icone);
  margin: calc(var(--ph-icone) / -2);
  border-radius: 999px;
  background: var(--ph-pastille);
  pointer-events: auto;
  cursor: ew-resize;
}

/* Keyboard-adjustable: the handle becomes a slider, so it has to be seen
   taking focus even while the indicator is at rest. */
.reveal-mosaic-hero__poignee:focus-visible {
  outline: 2px solid var(--ph-pastille);
  outline-offset: 3px;
}

.reveal-mosaic-hero:has(.reveal-mosaic-hero__poignee:focus-visible) .reveal-mosaic-hero__pointeur {
  opacity: 1;
}

/* ---------------------------------------------------- 6. Measuring probe --- */

/* The script needs --ph-cellule, --ph-rayon and the two durations in pixels
   and milliseconds. A custom property reads back exactly as written,
   "calc(3.3125 * 16px)" and not "53px", whereas the properties of a real box
   are always resolved. So we measure on this probe, which cannot be seen but
   still occupies a box — display: none would report "auto". */
.reveal-mosaic-hero__sonde {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--ph-cellule);
  max-width: var(--ph-seuil-etroit);
  height: var(--ph-rayon);
  min-height: var(--ph-rayon-min);
  max-height: var(--ph-rayon-max);
  visibility: hidden;
  pointer-events: none;
  color: var(--ph-mosaique);
  transition-property: opacity, transform;
  transition-duration: var(--ph-trainee), var(--ph-fondu);
}

/* --------------------------------------------------------- 7. Variants --- */

/* For a panel, a card or a product page: same drawing, smaller. */
.reveal-mosaic-hero--compact {
  --ph-font-size: 11px;
  --ph-hauteur: 26rem;
}

/* Light photo, dark text: the reverse takes three tokens. */
.reveal-mosaic-hero--sombre {
  --ph-texte: #fff;
  --ph-fond: #000;
  --ph-mosaique: #000;
  --ph-indicateur: rgb(0 0 0 / 0.55);
  --ph-pastille: #000;
}

/* ---------------------------------------------------- 8. Narrow screens --- */

/* The threshold is the original's: the layout only changes below 420 px,
   and the type does not move at all. */
@media (max-width: 420px) {
  .reveal-mosaic-hero {
    --ph-marge: calc(2.75 * var(--ph-font-size));
    --ph-ecart-titre: calc(1.125 * var(--ph-font-size));
    --ph-bloc-max: 100%;
    /* The original scales the cell by 1.6 then caps it at 64 px:
       starting from 53 px, the cap always wins. */
    --ph-cellule: calc(4 * var(--ph-font-size));
  }
}

/* ---------------------------------------------------------- 9. Fallbacks --- */

/* Without the script the canvas stays empty and the photo shows bare: black
   text would then sit on the dark greens. This veil does not try to imitate
   the mosaic, only to keep the text corner readable. */
.reveal-mosaic-hero__repli {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(100deg, var(--ph-fond) 0 34%, transparent 64%),
    linear-gradient(to bottom, var(--ph-fond) 0 10%, transparent 44%);
}

.reveal-mosaic-hero[data-ph-pret] .reveal-mosaic-hero__repli,
.reveal-mosaic-hero[data-ph-pret] .reveal-mosaic-hero__poignee[hidden] {
  display: none;
}

/* Until the script has taken over, the indicator has nothing to indicate. */
.reveal-mosaic-hero:not([data-ph-pret]) .reveal-mosaic-hero__pointeur {
  display: none;
}

/* A delay is motion spread over time, so delays go to zero along with the
   durations. The mosaic stays — it is a picture, not a movement. */
@media (prefers-reduced-motion: reduce) {
  .reveal-mosaic-hero {
    --ph-trainee: 0ms;
    --ph-fondu: 0ms;
    --ph-entree-delai: 0ms;
  }

  .reveal-mosaic-hero__pointeur {
    transition: none;
  }

  .reveal-mosaic-hero__titre,
  .reveal-mosaic-hero__pied {
    animation: none;
  }
}

/* In forced colours the photo and the mosaic no longer mean anything: the
   section goes back to the system and the text stays. */
@media (forced-colors: active) {
  .reveal-mosaic-hero {
    background: Canvas;
    color: CanvasText;
  }

  .reveal-mosaic-hero__photo,
  .reveal-mosaic-hero__voile,
  .reveal-mosaic-hero__repli,
  .reveal-mosaic-hero__pointeur {
    display: none;
  }

  .reveal-mosaic-hero__filet {
    background-color: CanvasText;
    opacity: 1;
  }
}
