/* ==========================================================================
   Curtain Hero 1.0 — framed hero with a rising curtain
   --------------------------------------------------------------------------
   Plain CSS. No framework, no build step, no external request. The typeface
   and the photograph ship next to this file.

   A photograph sits inside a white mount, like a print in a passe-partout.
   On load a black curtain slides up off the frame, the photograph settles out
   of a slight zoom, and the type rises out of its own mask: the title first,
   then the two lines of the caption, one after the other.

   The whole entrance is CSS. Without JavaScript the component plays exactly
   the same, which is why it is declared here and not in the script.

   PROPORTIONS
   The reference is built on two modules, because the original is:

     · a drawing module — the mount, the height and the gutters are a share of
       the VIEWPORT HEIGHT, not of the text. --ch-unit carries it.
     · a type module — the caption, 20 px on the reference. --ch-font-size
       carries it.

   Measured on the live original at 1915, 1440, 1200, 810 and 390 px:

     stage height       90    units          (972 px on a 1080 px viewport)
     mount             3.5    units          (37 px, left and right)
     inner padding     3.5    units          (37.797 px, top and bottom)
     gutter            3.5    units          (37.797 px, caption to title)
     band padding      3.5    units          (37.797 px, left and right)
     caption text        1    module         (20 px — the reference)
     caption leading   1.4    em             (28 px per line)
     caption width    17.5    rem            (280 px, i.e. 14 modules here)
     title height   13.0132   % of the band  (229.734 px for 1765.406 wide)
     title size     17.3497   % of the band  (fills the width, ink to ink)

   The title is set in real text where the original shipped an SVG outline, so
   its ink is nudged by the two offsets below to sit flush against the band —
   type leaves side bearings, an outline does not.

   Order of this file:
     1. Design tokens
     2. Typeface
     3. Block: the mounted stage
     4. Photograph
     5. Bands
     6. Caption
     7. Title
     8. Curtain
     9. Entrance motion
    10. Responsive steps
    11. Fallbacks: reduced motion, forced colours

   Naming follows BEM: .curtain-hero, .curtain-hero__part.
   ========================================================================== */

/* ==========================================================================
   1. Design tokens
   ========================================================================== */

.curtain-hero {
  /* ── Scale ────────────────────────────────────────────────────────────── */
  /* The drawing module. The original pins its mount to the height of the
     window, so the frame stays a constant share of the screen whatever the
     width. Override it with a length — calc(100vw / 40), 8px — to rescale the
     drawing without touching anything else. */
  --ch-unit: 1vh;
  /* The type module: the caption is the running text of the component. */
  --ch-font-size: 1.25rem;

  /* ── Drawing ──────────────────────────────────────────────────────────── */
  --ch-hauteur: calc(90 * var(--ch-unit));
  --ch-passe-partout: calc(3.5 * var(--ch-unit));
  --ch-marge: calc(3.5 * var(--ch-unit)); /* top and bottom of the stage */
  --ch-gouttiere: calc(3.5 * var(--ch-unit)); /* caption to title */
  --ch-bande-marge: calc(3.5 * var(--ch-unit)); /* left and right of a band */

  /* ── Colour ───────────────────────────────────────────────────────────── */
  --ch-mount: #fff;
  --ch-rideau: #000;
  --ch-encre: #fff;
  --ch-fond: #000; /* seen only while the photograph loads */

  /* ── Type ─────────────────────────────────────────────────────────────── */
  --ch-famille: "Inter Tight", "Inter", system-ui, sans-serif;
  --ch-meta-graisse: 500;
  --ch-meta-interligne: 1.4;
  --ch-meta-largeur: 17.5rem;
  --ch-titre-graisse: 700;
  /* Share of the band the title occupies. Both are percentages of the band's
     inner width, so the title rescales with the component and never with the
     window. */
  --ch-titre-hauteur: 13.0132cqw;
  --ch-titre-corps: 17.3497cqw;
  /* Ink offsets, measured on Inter Tight 700 for the title as set: the left
     side bearing, and the height that centres the ink inside its mask. Written
     in em on purpose — a custom property in em resolves where it is used, and
     here that is the title, so both offsets follow the title's size. */
  --ch-titre-decalage-x: -0.04625em;
  --ch-titre-decalage-y: -0.12497em;

  /* ── Framing ──────────────────────────────────────────────────────────── */
  --ch-photo-cadrage: 50% 50%;

  /* ── Motion ───────────────────────────────────────────────────────────── */
  /* Every duration and every delay is read straight from the original's
     interaction data, easing included. Set them to 0s to land on the final
     state without moving. */
  --ch-courbe: cubic-bezier(0.165, 0.84, 0.44, 1);
  --ch-rideau-duree: 1500ms;
  --ch-rideau-retard: 0ms;
  --ch-rideau-course: -101%;
  --ch-photo-duree: 2500ms;
  --ch-photo-retard: 0ms;
  --ch-photo-zoom: 1.05;
  --ch-titre-duree: 1000ms;
  --ch-titre-retard: 0ms;
  --ch-ligne-duree: 500ms;
  --ch-ligne-1-retard: 500ms;
  --ch-ligne-2-retard: 750ms;
}

/* ==========================================================================
   2. Typeface
   --------------------------------------------------------------------------
   One variable file per subset. The browser fetches latin-ext only when the
   text actually needs it, so ordinary latin copy costs a single 44 kB file.
   ========================================================================== */

@font-face {
  font-family: "Inter Tight";
  src: url("InterTight-variable-latin.woff2") format("woff2-variations");
  font-weight: 100 900;
  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+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter Tight";
  src: url("InterTight-variable-latin-ext.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
    U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ==========================================================================
   3. Block: the mounted stage
   --------------------------------------------------------------------------
   The mount is a border, not padding: a border clips the photograph, padding
   would not. The stage keeps its own box-sizing so it does not depend on the
   page having a reset.
   ========================================================================== */

.curtain-hero {
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: flex-start;
  gap: var(--ch-gouttiere);

  height: var(--ch-hauteur);
  padding-block: var(--ch-marge);
  border-inline: var(--ch-passe-partout) solid var(--ch-mount);

  overflow: hidden;
  /* The original leaves the photograph on z-index -1, which puts it behind any
     background the host page gives the stage. Isolating the stage keeps the
     stacking order inside the component, whatever it is dropped into. */
  isolation: isolate;

  background-color: var(--ch-fond);
  color: var(--ch-encre);
  font-family: var(--ch-famille);
  font-size: var(--ch-font-size);
  line-height: var(--ch-meta-interligne);
}

.curtain-hero *,
.curtain-hero *::before,
.curtain-hero *::after {
  box-sizing: inherit;
}

/* ==========================================================================
   4. Photograph
   ========================================================================== */

.curtain-hero__photo {
  position: absolute;
  inset: 0;
  z-index: 0;

  display: block;
  width: 100%;
  height: 100%;
  min-height: 100%;

  object-fit: cover;
  object-position: var(--ch-photo-cadrage);
}

/* ==========================================================================
   5. Bands
   --------------------------------------------------------------------------
   A band is the full width of the stage with its own side margin. It carries
   the query container: the title is sized as a share of the band's inner
   width, which is what makes it fill the line exactly at any size.
   ========================================================================== */

.curtain-hero__bande {
  position: relative;
  z-index: 1;

  container-type: inline-size;
  width: 100%;
  padding-inline: var(--ch-bande-marge);
}

/* ==========================================================================
   6. Caption
   --------------------------------------------------------------------------
   Two lines, each in its own mask, so they can rise one after the other.
   The paragraphs zero their own margins: the component assumes no reset, and
   a default <p> margin is worth 16 px of drift here.
   ========================================================================== */

.curtain-hero__meta {
  max-width: var(--ch-meta-largeur);
}

.curtain-hero__ligne {
  margin: 0;
  overflow: hidden;
}

.curtain-hero__texte {
  display: block;
  font-size: 1em;
  font-weight: var(--ch-meta-graisse);
  line-height: var(--ch-meta-interligne);
  letter-spacing: 0;
}

/* ==========================================================================
   7. Title
   --------------------------------------------------------------------------
   The original ships the title as an SVG outline whose ink touches all four
   edges of its box. Set as real text — selectable, searchable, translatable —
   the same word leaves side bearings, so the mask keeps the original's height
   and the type is nudged inside it by the two measured offsets.

   The mask height is a share of the band, not of the type: it stays what the
   original drew even though the words are not the same.
   ========================================================================== */

.curtain-hero__titre {
  margin: 0;
  height: var(--ch-titre-hauteur);
  overflow: hidden;
}

.curtain-hero__lettrage {
  display: block;
  width: max-content;

  margin-top: var(--ch-titre-decalage-y);
  margin-left: var(--ch-titre-decalage-x);

  font-size: var(--ch-titre-corps);
  font-weight: var(--ch-titre-graisse);
  font-variation-settings: "wght" 700;
  line-height: 1;
  letter-spacing: 0;
  white-space: nowrap;
}

/* ==========================================================================
   8. Curtain
   --------------------------------------------------------------------------
   Decorative, and out of the accessibility tree. It rests off the top of the
   frame; the entrance is what brings it down to cover, then takes it away.
   ========================================================================== */

.curtain-hero__rideau {
  position: absolute;
  inset: 0;
  z-index: 3;

  background-color: var(--ch-rideau);
  transform: translateY(var(--ch-rideau-course));
}

/* ==========================================================================
   9. Entrance motion
   --------------------------------------------------------------------------
   Each keyframe set describes its FROM only. With animation-fill-mode set to
   backwards the arrival state stays whatever the rules above say, so the
   motion never overwrites a transform or an opacity already in place — and
   setting a duration to 0s lands on that same final state.

   The title and the caption rise by exactly the height of their own mask, so
   they start flush under it rather than a percentage of their own box: the
   two are not the same once the type is real text.
   ========================================================================== */

@keyframes ch-rideau {
  from {
    transform: translateY(0);
  }
}

@keyframes ch-zoom {
  from {
    transform: scale(var(--ch-photo-zoom));
  }
}

@keyframes ch-monter-titre {
  from {
    transform: translateY(var(--ch-titre-hauteur));
  }
}

@keyframes ch-monter-ligne {
  from {
    transform: translateY(100%);
  }
}

.curtain-hero__rideau {
  animation: ch-rideau var(--ch-rideau-duree) var(--ch-courbe)
    var(--ch-rideau-retard) backwards;
}

.curtain-hero__photo {
  animation: ch-zoom var(--ch-photo-duree) var(--ch-courbe)
    var(--ch-photo-retard) backwards;
}

.curtain-hero__lettrage {
  animation: ch-monter-titre var(--ch-titre-duree) var(--ch-courbe)
    var(--ch-titre-retard) backwards;
}

.curtain-hero__ligne:nth-of-type(1) .curtain-hero__texte {
  animation: ch-monter-ligne var(--ch-ligne-duree) var(--ch-courbe)
    var(--ch-ligne-1-retard) backwards;
}

.curtain-hero__ligne:nth-of-type(2) .curtain-hero__texte {
  animation: ch-monter-ligne var(--ch-ligne-duree) var(--ch-courbe)
    var(--ch-ligne-2-retard) backwards;
}

/* Held back until the stage comes into view. The script sets this attribute
   and takes it away; paused with backwards fill, every part shows its start
   state, so nothing flashes before the entrance runs. */
.curtain-hero[data-etat="attente"] .curtain-hero__rideau,
.curtain-hero[data-etat="attente"] .curtain-hero__photo,
.curtain-hero[data-etat="attente"] .curtain-hero__lettrage,
.curtain-hero[data-etat="attente"] .curtain-hero__texte {
  animation-play-state: paused;
}

/* Replay. A finished animation with backwards fill no longer affects anything,
   so the engine drops it and the Web Animations API has nothing left to
   restart. Taking the animation-name away for one frame — which is what this
   attribute does — is what builds it again. The script sets it and removes it
   inside the same task, so this state is never painted. */
.curtain-hero[data-etat="rejeu"] .curtain-hero__rideau,
.curtain-hero[data-etat="rejeu"] .curtain-hero__photo,
.curtain-hero[data-etat="rejeu"] .curtain-hero__lettrage,
.curtain-hero[data-etat="rejeu"] .curtain-hero__texte {
  animation: none;
}

/* ==========================================================================
   10. Responsive steps
   --------------------------------------------------------------------------
   The original changes four things as the window narrows, and nothing else:
   the height of the stage, the mount, the gutter and the size of the caption.
   They are all tokens, so a step is four declarations — and a page that wants
   one constant layout, like the embedded preview, only has to set the tokens
   back.
   ========================================================================== */

@media (max-width: 991px) {
  .curtain-hero {
    --ch-hauteur: calc(65 * var(--ch-unit));
    --ch-gouttiere: calc(2.5 * var(--ch-unit));
    --ch-passe-partout: 1.5rem;
    --ch-marge: 1.5rem;
    --ch-bande-marge: 1.5rem;
    /* Below this width the original slides the framing to the right of the
       photograph, where its subject sits. */
    --ch-photo-cadrage: 100% 50%;
  }
}

@media (max-width: 767px) {
  .curtain-hero {
    --ch-hauteur: calc(50 * var(--ch-unit));
    --ch-gouttiere: calc(2 * var(--ch-unit));
    --ch-font-size: 1rem;
    --ch-meta-largeur: 15rem;
  }
}

@media (max-width: 479px) {
  .curtain-hero {
    --ch-hauteur: calc(40 * var(--ch-unit));
    --ch-gouttiere: calc(1.5 * var(--ch-unit));
    --ch-passe-partout: 0px;
    /* The original goes back UP one step here, from 1 rem to 1.15 rem. It
       looks like a slip, and it is kept because it is what the reference
       does; --ch-font-size takes it back to 1rem in one line. */
    --ch-font-size: 1.15rem;
  }
}

/* ==========================================================================
   11. Fallbacks
   ========================================================================== */

/* Reduced motion. Delays go too: a delay is movement spread over time, and a
   title that appears out of nowhere three quarters of a second late is still
   motion as far as the reader is concerned. The mount, the mask and the
   framing are drawing, not movement, and they stay.

   The two caption lines are matched by their full selector and not by
   .curtain-hero__texte alone. A media query buys no specificity: the rule that
   sets the animation is worth three, a lone class is worth one, and the lone
   class loses. Caught by the bench — both lines went on animating. */
@media (prefers-reduced-motion: reduce) {
  .curtain-hero__rideau,
  .curtain-hero__photo,
  .curtain-hero__lettrage,
  .curtain-hero__ligne:nth-of-type(1) .curtain-hero__texte,
  .curtain-hero__ligne:nth-of-type(2) .curtain-hero__texte {
    animation: none;
  }
}

/* Forced colours. The photograph carries no meaning on its own and the system
   palette cannot be applied to it, so it steps aside and the stage becomes a
   plain readable block. */
@media (forced-colors: active) {
  .curtain-hero {
    background-color: Canvas;
    color: CanvasText;
    border-color: CanvasText;
  }

  .curtain-hero__photo,
  .curtain-hero__rideau {
    display: none;
  }
}
