/* ============================================================================
   LES BRIEFS REPRIS — styles

   Il en restait deux blocs autonomes jusqu'au 30/08/2026. Le bloc « Prisma »
   portait la section accessibilité et a été réécrit dans la charte du site,
   avec tous ses textes : ses 440 lignes de styles sont parties avec lui. Ce
   fichier ne porte donc plus qu'un bloc, « Jack », plus l'accueil du site, qui
   vient du troisième brief et n'a jamais été un bloc à part.

   Traduction des classes Tailwind des briefs en CSS ordinaire, avec les
   MÊMES valeurs. Quelques équivalences pour relire :

     text-[17.5vw]     → font-size: 17.5vw
     md:px-10          → à partir de 768 px, padding gauche/droite 40 px
     rounded-t-[60px]  → border-radius: 60px 60px 0 0
     gap-3             → gap: 12px
     max-w-5xl         → max-width: 64rem

   Les breakpoints sont ceux de Tailwind : sm 640, md 768, lg 1024.

   POURQUOI CES COULEURS SONT ÉCRITES EN DUR ICI, alors que la règle du site
   l'interdit : ce sont les palettes des briefs, pas celle de Paul. Les faire
   passer par le thème du site les rendrait solidaires de lui, donc modifier
   l'accent de Paul repeindrait deux maquettes qui ne lui appartiennent pas.
   Elles sont déclarées en variables locales à chaque bloc, ce qui garde
   l'avantage sans la confusion.
   ========================================================================= */

/* --- Ce que les deux blocs partagent -------------------------------------- */

.brief {
  position: relative;
  z-index: 4;
}

/* Un bandeau qui dit d'où vient le bloc. Sans lui, la page présente d'abord
   Paul puis « Jack » sans que rien n'explique pourquoi, et le visiteur en
   conclut que le site parle de deux personnes. Il y en avait trois avant le
   30/08/2026, « Marcus Chen » étant parti avec le bloc Prisma. */
.brief__etiquette {
  margin: 0;
  padding: 10px var(--marge-page);
  background: var(--accent-soft);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--font-texte);
  font-size: var(--t-petit);
  text-align: center;
}

/* Le dégradé de titre commun aux deux briefs (`.hero-heading` chez Jack).
   Valeur du brief : linear-gradient(180deg, #646973 0%, #BBCCD7 100%).

   Le brief le donne dans ce sens, du sombre vers le clair. Il est appliqué tel
   quel. À savoir : le haut des lettres tombe alors à 4,03 de contraste sur
   #0C0C0C, sous le seuil de 4,5. Ce sont des titres énormes, donc du « texte
   de grande taille » au sens du RGAA, où le seuil est de 3 et non de 4,5 :
   c'est conforme, mais ça ne le serait pas sur un texte courant. */
.hero-heading {
  background: linear-gradient(180deg, #646973 0%, #BBCCD7 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Repli si le navigateur ne sait pas découper un fond à la forme du texte :
   sans lui, le titre serait purement et simplement invisible. */
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .hero-heading {
    background: none;
    color: #BBCCD7;
  }
}

/* ==========================================================================
   01 · ACCUEIL — hero plein écran sur vidéo

   Traduction du troisième brief. Ses valeurs Tailwind sont reprises telles
   quelles : text-8xl vaut 6rem, tracking-[-2.46px], leading-[0.95], py-[90px].

   LA PALETTE EST CELLE DU BRIEF, en variables locales comme pour les deux
   autres blocs. Elle est donnée en HSL dans le brief, convertie ici en
   hexadécimal parce que c'est ce qui se relit :

     --background        201 100% 13%   →  #002B42
     --foreground        0 0% 100%      →  #FFFFFF
     --muted-foreground  240 4% 66%     →  #A5A5AC
     --border            0 0% 18%       →  #2E2E2E

   Le fond navy ne se voit presque pas, la vidéo le recouvre. Il tient deux
   rôles quand même : la fraction de seconde avant que l'affiche n'arrive, et
   les bords si le format de l'écran est plus extrême que celui de la vidéo.
   ========================================================================== */

.accueil {
  --ac-fond: #002B42;
  --ac-encre: #ffffff;
  --ac-gris: #A5A5AC;

  position: relative;
  z-index: 5;
  /* LE BANDEAU DE MAQUETTE EST RETIRÉ DE LA HAUTEUR, et sans lui le hero
     dépassait du pli de sa hauteur exacte, 45 px : la commande de pause,
     posée à 24 px du bas de la SECTION, tombait 21 px sous le bord de
     l'écran et n'était pas atteignable à la souris.

     Le bandeau est mesuré par le script et écrit dans `--haut-bandeau`. Le
     repli à 0 px n'est pas de la prudence, c'est le cas normal de demain :
     ce bandeau part le jour où le site est publié, et la règle se corrige
     alors toute seule. */
  min-height: calc(100svh - var(--haut-bandeau, 0px));
  /* LE HERO REMONTE SOUS L'EN-TÊTE, en permanence et pas au moment où
     celui-ci s'efface : une marge qui apparaît et disparaît au défilement
     ferait sauter la page de 66 px, et le saut se produirait pile au moment
     du basculement, donc en boucle.

     L'en-tête est `sticky`, il garde sa place dans le flux : sans cette
     marge, l'effacer laisse une bande vide de 66 px au-dessus de la vidéo,
     et le « plein écran » n'en est plus un. Avec elle, le hero commence
     juste sous le bandeau de maquette.

     Sans JavaScript, l'en-tête ne s'efface pas et recouvre les 66 px du haut
     de la vidéo. C'est le recouvrement normal d'un en-tête collant, il est
     sombre et translucide, et la barre du brief est de toute façon masquée
     dans ce cas. */
  margin-top: calc(-1 * var(--haut-entete));
  display: flex;
  flex-direction: column;
  background: var(--ac-fond);
  /* La vidéo est en `absolute` et déborde volontairement de sa boîte pour
     couvrir : sans ce `hidden`, elle fait défiler le document en largeur. */
  overflow: hidden;
  font-family: var(--font-texte);
}

/* --- La vidéo ------------------------------------------------------------ */

.accueil__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  /* La vidéo commence par une image quasi noire. Sans ce fond, on voit le
     navy du bloc pendant deux images au démarrage de la boucle. */
  background: #000;
}

/* --- Le voile de contraste ------------------------------------------------

   Le brief dit « no overlays. The video provides all visual depth ». Refusé,
   et la mesure explique pourquoi mieux qu'un principe.

   CE QUE DIT LA VIDÉO, relevée bande par bande sur toute la boucle : elle est
   noire jusqu'à 60 % de sa hauteur, luminance moyenne 0,03, et au-delà elle
   explose. Trois écrans d'ordinateur allumés et un massif de fleurs orangées
   montent à 0,98, c'est-à-dire du blanc. Il n'existe aucun voile qui rende un
   gris #A5A5AC lisible sur du blanc sans noyer l'image : la seule réponse
   possible est de ne pas y mettre de texte.

   D'où DEUX gestes, et le premier fait le gros du travail :

     1. Le bloc de texte est remonté au-dessus de la ligne des 60 %, par le
        `padding-bottom` de `.accueil__hero`. C'est du placement, pas de la
        peinture, et ça ne coûte rien à l'image.
     2. Ce qui reste à traiter est plus discret : les ÉTOILES. Elles sont à
        0,25-0,50 de luminance, soit très exactement celle du gris du brief,
        et une étoile derrière une lettre grise donne 1,2 de contraste. Une
        ellipse douce centrée sur le texte les éteint là où il y en a, et
        nulle part ailleurs. Le ciel reste étoilé sur tout le pourtour.

   Les valeurs ne sont pas choisies, elles sont cherchées : cinq réglages
   croisés avec cinq largeurs et sept moments de la boucle, en mesurant le
   pire pixel derrière chaque texte. Celui retenu est le PLUS CLAIR de ceux
   qui passent partout, pas le plus sûr. Marge la plus faible, 4,75 pour un
   seuil de 4,5, sur le sous-titre à 768 px.

   Le fond du pire pixel est mesuré après un flou de l'épaisseur du trait des
   lettres, et non pixel à pixel. Ce n'est pas une facilité : une étoile d'un
   pixel derrière une capitale de 96 px ne la rend pas illisible, et la
   compter comme telle ferait condamner un texte parfaitement lisible, donc
   assombrir l'image pour rien. Le flou vaut la taille de police divisée par
   douze, minimum 1,5 px.
   ------------------------------------------------------------------------ */
.accueil__voile {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    /* L'ellipse sur le texte. Elle est posée EN PREMIER, donc au-dessus. */
    radial-gradient(circle max(62vw, 62svh) at 50% 52%,
      rgba(0, 16, 26, 0.50) 0%,
      rgba(0, 16, 26, 0.31) 55%,
      rgba(0, 16, 26, 0) 100%),
    /* Le dégradé du haut vers le bas. Il a été ALLÉGÉ après coup, et c'est le
       bloc de texte remonté qui l'a permis : ses deux dernières valeurs
       étaient 0,72 et 0,86, dimensionnées pour un sous-titre qui tombait
       dans les écrans allumés. Plus aucun texte n'y est posé, la partie la
       plus vivante de l'image n'a donc plus à être éteinte, et les écrans et
       les fleurs retrouvent leur lumière. */
    linear-gradient(180deg,
      rgba(0, 22, 34, 0.42) 0%,
      rgba(0, 22, 34, 0.22) 26%,
      rgba(0, 22, 34, 0.30) 55%,
      rgba(0, 22, 34, 0.36) 78%,
      rgba(0, 22, 34, 0.48) 100%);
}

/* --- Verre dépoli --------------------------------------------------------

   `.liquid-glass` du brief, renommé `.verre` pour rester dans la langue du
   reste du fichier. Repris à la valeur près.

   Le `::before` dessine un liseré qui n'existe que sur les bords haut et bas :
   c'est un dégradé opaque aux extrémités et transparent au milieu, découpé en
   anneau par la composition de deux masques. `padding` fixe l'épaisseur du
   liseré, ici 1,4 px.
   ------------------------------------------------------------------------ */
.verre {
  position: relative;
  overflow: hidden;
  border: none;
  background: rgba(255, 255, 255, 0.01);
  background-blend-mode: luminosity;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.1);
}

.verre::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.4px;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.45) 0%,
    rgba(255, 255, 255, 0.15) 20%,
    rgba(255, 255, 255, 0) 40%,
    rgba(255, 255, 255, 0) 60%,
    rgba(255, 255, 255, 0.15) 80%,
    rgba(255, 255, 255, 0.45) 100%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
}

/* Repli quand la composition de masques n'est pas comprise : sans lui, le
   `::before` est un rectangle plein qui recouvre le libellé du bouton. */
@supports not ((mask-composite: exclude) or (-webkit-mask-composite: xor)) {
  .verre::before {
    background: none;
    border: 1.4px solid rgba(255, 255, 255, 0.22);
  }
}

/* --- La navigation du brief ---------------------------------------------- */

/* Sans JavaScript, l'en-tête collant du site ne s'efface jamais. C'est donc
   celle-ci qui disparaît, et il n'y a jamais deux barres empilées. Le hero
   cesse alors d'être remonté sous l'en-tête, pour la même raison qu'en
   dessous de 900 px : sinon la barre lui mange son titre. */
html:not(.js) .accueil__nav { display: none; }

html:not(.js) .accueil {
  margin-top: 0;
  min-height: calc(100svh - var(--haut-entete));
}

.accueil__nav {
  position: relative;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
  max-width: 80rem;          /* max-w-7xl */
  margin: 0 auto;
  padding: 24px 32px;        /* py-6 px-8 */
}

.accueil__marque {
  font-family: var(--font-accent);
  font-size: 1.875rem;       /* text-3xl */
  line-height: 1.15;
  letter-spacing: -0.025em;  /* tracking-tight */
  color: var(--ac-encre);
  text-decoration: none;
  white-space: nowrap;
}

.accueil__liens {
  display: flex;
  align-items: center;
  gap: 32px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.accueil__liens a {
  font-size: 0.875rem;       /* text-sm */
  color: var(--ac-gris);
  text-decoration: none;
  transition: color var(--transition);
}

.accueil__liens a:hover,
.accueil__liens a:focus-visible,
.accueil__liens a[aria-current] {
  color: var(--ac-encre);
}

/* --- Les deux boutons ----------------------------------------------------- */

.accueil__cta {
  display: inline-block;
  border-radius: 999px;
  color: var(--ac-encre);
  text-decoration: none;
  text-align: center;
  padding: 20px 56px;        /* py-5 px-14 */
  font-size: 1rem;           /* text-base */
  transition: transform var(--transition);
}

/* Celui de la barre est le petit : px-6 py-2.5 text-sm. */
.accueil__cta--barre {
  padding: 10px 24px;
  font-size: 0.875rem;
  white-space: nowrap;
}

.accueil__cta:hover,
.accueil__cta:focus-visible {
  transform: scale(1.03);
}

/* --- Le bloc de titre ----------------------------------------------------- */

/* LE BLOC EST CENTRÉ DANS LE HAUT DE L'ÉCRAN, PAS DANS L'ÉCRAN.

   Le brief donne `pt-32 pb-40 py-[90px]`, donc 90 px en haut comme en bas. Sa
   vidéo à lui devait être sombre partout. Celle-ci ne l'est que sur ses trois
   premiers cinquièmes, et un bloc centré posait le sous-titre et le bouton en
   plein dans les écrans allumés : 1,86 de contraste mesuré à 320 px, pour un
   seuil de 4,5.

   Le `padding-bottom` de 36svh décale le centrage vers le haut d'un peu moins
   d'un cinquième d'écran. Mesuré après coup, le bas du bouton passe de 78 % à
   64 % à 320 px, et de 73 % à 56 % à 1440 px. Tout le texte est alors dans la
   partie noire, et le voile n'a plus à faire que la moitié du chemin.

   `flex: 1 0 auto` et non `flex: 1` : sur une fenêtre courte, le bloc ne tient
   pas dans ce qui reste et doit pouvoir POUSSER la section au-delà de
   100svh. Avec `flex: 1`, il serait comprimé et sortirait par le haut, où
   l'`overflow: hidden` de la section le couperait. */
.accueil__hero {
  position: relative;
  z-index: 3;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px 24px 36svh;  /* px-6, et le décalage vers le haut */
}

/* Sur une fenêtre courte, remonter le bloc d'un tiers d'écran le ferait sortir
   par le haut avant que la hauteur ne serve à quoi que ce soit. Le décalage
   est réduit, et la partie éclairée de la vidéo est de toute façon repoussée
   hors cadre par le recadrage en `cover`. */
@media (max-height: 720px) and (min-width: 901px) {
  .accueil__hero { padding-bottom: 16svh; }
}


.accueil__titre {
  max-width: 80rem;          /* max-w-7xl */
  margin: 0;
  font-family: var(--font-accent);
  font-weight: 400;
  font-size: clamp(3rem, 8.5vw, 6rem);   /* text-5xl → text-8xl */
  line-height: 0.95;
  letter-spacing: -2.46px;
  color: var(--ac-encre);
  /* Le titre est le seul texte posé sur la partie la plus claire de l'image
     quand l'écran est court. Blanc pur, il tient partout, mais son ombre
     portée est ce qui le décolle des étoiles derrière lui. */
  text-shadow: 0 2px 24px rgba(0, 14, 22, 0.45);
}

/* Le brief teinte une partie du titre en gris pour le contraste
   typographique. `not-italic` chez lui, `font-style: normal` ici : la balise
   est gardée pour son sens, pas pour son rendu par défaut. */
.accueil__titre em {
  font-style: normal;
  color: var(--ac-gris);
}

.accueil__accroche {
  max-width: 42rem;          /* max-w-2xl */
  margin: 32px 0 0;          /* mt-8 */
  font-size: clamp(1rem, 1.2vw, 1.125rem);
  line-height: 1.625;        /* leading-relaxed */
  color: var(--ac-gris);
  text-shadow: 0 1px 16px rgba(0, 14, 22, 0.55);
}

.accueil__hero .accueil__cta {
  margin-top: 48px;          /* mt-12 */
}

/* --- La commande de pause -------------------------------------------------

   Masquée sans JavaScript : rien ne pourrait l'actionner, et un bouton mort
   vaut moins qu'un bouton absent.
   ------------------------------------------------------------------------ */
.accueil__pause {
  display: none;
}

.js .accueil__pause {
  position: absolute;
  right: 24px;
  bottom: 24px;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px 10px 14px;
  border-radius: 999px;
  font: inherit;
  font-size: 0.8125rem;
  color: var(--ac-encre);
  cursor: pointer;
  transition: transform var(--transition);
}

.js .accueil__pause:hover,
.js .accueil__pause:focus-visible {
  transform: scale(1.03);
}

/* Deux barres verticales, qui deviennent un triangle une fois en pause. Le
   dessin tient en `clip-path`, donc aucune image et aucun caractère. */
.accueil__pause-icone {
  width: 11px;
  height: 12px;
  background: currentColor;
  clip-path: polygon(0 0, 4px 0, 4px 12px, 0 12px, 0 0,
                     7px 0, 11px 0, 11px 12px, 7px 12px);
}

.accueil__pause[aria-pressed="true"] .accueil__pause-icone {
  clip-path: polygon(0 0, 11px 6px, 0 12px);
}

/* --- Apparitions ----------------------------------------------------------

   `fade-rise` du brief, à la durée et aux retards près : 0,8 s, puis 0,2 s et
   0,4 s de décalage. Elles ne sont posées que sous `.js`, comme toutes les
   apparitions du site : sans script, la classe n'arrive jamais et rien ne
   commence à l'état invisible.
   ------------------------------------------------------------------------ */
@keyframes accueil-monte {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}

.js .accueil__titre    { animation: accueil-monte 0.8s ease-out both; }
.js .accueil__accroche { animation: accueil-monte 0.8s ease-out 0.2s both; }
.js .accueil__hero .accueil__cta { animation: accueil-monte 0.8s ease-out 0.4s both; }

/* --- Écrans étroits -------------------------------------------------------

   Sous 900 px, la barre du brief sort ENTIÈREMENT, et pas seulement ses cinq
   liens. Le brief les masque à partir de `md:` et ne laisse que la marque et
   le bouton, ce qui marche pour lui : il n'a pas d'autre navigation.

   Ici, si. Ne garder que la marque reviendrait à laisser un visiteur de
   téléphone devant un écran entier sans un seul moyen d'aller quelque part,
   puisque l'en-tête du site s'efface sous le hero. C'est donc l'inverse qui
   est fait : la barre du brief disparaît, l'en-tête du site reste, avec son
   menu déroulant et ses neuf destinations. L'échange est gouverné d'ici, le
   script se contente de poser la classe.
   ------------------------------------------------------------------------ */
@media (max-width: 900px) {
  .accueil__nav { display: none; }

  /* LA REMONTÉE SOUS L'EN-TÊTE EST ANNULÉE ICI, et c'est un défaut corrigé,
     pas une précaution. La marge négative n'a de sens que si l'en-tête
     s'efface ; ici il reste, et il recouvrait les 66 px du haut du hero, donc
     le premier mot du titre. Vu à la capture : « Là où » à moitié sous la
     barre.

     La hauteur suit : le hero ne fait plus un écran mais un écran moins
     l'en-tête, c'est-à-dire exactement ce qui reste visible sous lui. */
  .accueil {
    margin-top: 0;
    min-height: calc(100svh - var(--haut-bandeau, 0px) - var(--haut-entete));
  }

  .accueil__hero { padding: 24px 20px 36svh; }

  .accueil__titre { letter-spacing: -1px; }

  /* Le bouton de pause passe au centre : dans le coin, il tombait sous le
     pouce qui tient le téléphone. */
  .js .accueil__pause {
    right: 50%;
    transform: translateX(50%);
    bottom: 18px;
  }

  .js .accueil__pause:hover,
  .js .accueil__pause:focus-visible {
    transform: translateX(50%) scale(1.03);
  }
}

/* LE MÊME CAS SUR UN PETIT TÉLÉPHONE, traité le 11/09/2026.

   ELLE EST POSÉE ICI, APRÈS LE BLOC 900 px, ET PAS À CÔTÉ DE SA JUMELLE.
   Écrite plus haut, elle ne servait à rien : le bloc `@media (max-width: 900px)`
   ci-dessus réécrit le `padding` EN RACCOURCI, donc il repose un
   `padding-bottom` de 36svh, à spécificité égale et plus bas dans la cascade.
   La mesure l'a dit tout de suite, le bouton restait à 109,2 %. Une règle
   correcte au mauvais endroit d'une feuille de style ne fait rien du tout. La règle ci-dessus
   ne s'appliquait qu'au-delà de 901 px de large, donc jamais sur un téléphone,
   et le bas du bouton tombait à 109,2 % de la hauteur d'écran en 320 × 568 :
   sous le pli, invisible sans défiler, sur le seul appel à l'action du haut de
   page. Mesuré, pas déduit, et le défaut est antérieur à la réécriture du
   texte du 30/08 — la version d'avant le mettait à 113 %.

   Le motif est exactement celui de la règle du dessus, et il vaut aussi ici :
   le recadrage en `cover` repousse hors cadre la partie éclairée de la vidéo
   sur un écran étroit, donc le décalage d'un tiers d'écran ne protège plus
   rien qu'il faille protéger. Il n'y a pas de contrepartie de contraste à
   payer, seulement de la place à rendre.

   CE QUE LA MESURE A CORRIGÉ DANS LE DIAGNOSTIC, et c'est l'essentiel. Le
   dossier attribuait ces 109 % au contenu du hero, « simplement plus haut que
   la place disponible ». C'est faux à 90 % : en retirant le bandeau de
   maquette, qui prend trois lignes à cette largeur et qui ne part pas en
   ligne, le bouton tombe à 93,3 %, donc déjà au-dessus du pli. Le défaut réel
   est donc un artefact de développement, pas un défaut de composition.

   Ce qui est fait ici n'est donc pas une réparation mais une marge : le
   `padding-bottom` de 36svh était de la place morte SOUS le bouton, sans
   effet sur le pli, et les deux écarts internes passent de 32 et 48 px à 20 et
   28. Le bouton descend à 87,7 % dans l'état où le site est mis en ligne, ce
   qui laisse de quoi respirer au lieu de tenir de justesse.

   Aucune de ces valeurs ne touche le contraste : le texte ne bouge pas
   au-dessus de la ligne des 60 % de luminance de la vidéo, qui est ce que le
   placement du 29/08 protège. */
@media (max-height: 700px) and (max-width: 900px) {
  .accueil__hero { padding-bottom: 12svh; }
  .accueil__accroche { margin-top: 20px; }
  .accueil__hero .accueil__cta { margin-top: 28px; }
}

/* ==========================================================================
   BLOC 1 — « JACK — 3D CREATOR »
   ========================================================================== */

.brief--jack {
  --jk-fond: #0c0c0c;
  --jk-texte: #d7e2ea;
  --jk-encre: #0c0c0c;
  --jk-blanc: #ffffff;

  background: var(--jk-fond);
  color: var(--jk-texte);
  font-family: 'Kanit', 'Trebuchet MS', sans-serif;
  overflow-x: clip;
}

.brief--jack h3,
.brief--jack h4 {
  font-family: 'Kanit', 'Trebuchet MS', sans-serif;
  color: inherit;
  margin: 0;
  letter-spacing: normal;
}

/* `:where()` met cette règle à zéro de spécificité. Sans ça, elle bat les
   classes du brief : `.brief--jack p` l'emportait sur `.jk-hero__accroche`,
   donc la largeur maximale de 260 px demandée par le brief était ignorée et
   l'accroche s'étalait sur toute la page. Ce reset n'est là que pour annuler
   le `max-width` que `base.css` pose sur tous les paragraphes du site. */
.brief--jack :where(p) {
  max-width: none;
}

/* --- 1. Hero -------------------------------------------------------------- */

.jk-hero {
  position: relative;
  height: 100svh;
  display: flex;
  flex-direction: column;
  overflow-x: clip;
  /* Ce retrait remplace celui que donnait la navigation du brief, retirée
     parce qu'elle doublait l'en-tête du site. Sans lui, le `margin-top`
     négatif du titre le fait passer SOUS la barre collante, et on lit le haut
     des lettres au travers de son fond translucide. */
  padding-top: 34px;
}


/* overflow-hidden autour du titre : c'est lui qui permet à la ligne de monter
   depuis le bas sans qu'on la voie déborder avant d'arriver.

   La marge latérale est un écart assumé avec le brief, qui pose le titre à
   ras bord. À ras bord, il ne déborde pas — mesuré, le texte fait exactement
   la largeur du cadre — mais il TOUCHE les deux côtés de l'écran, et un titre
   collé aux bords se lit comme un titre sur le point d'être coupé.

   La valeur reprend celle de la barre du bas, pour que le titre s'aligne avec
   l'accroche qui est dessous plutôt que de flotter au-dessus d'elle. */
.jk-hero__titre-cadre {
  overflow: hidden;
  width: 100%;
  padding: 0 24px;
}

.jk-hero__titre {
  width: 100%;
  margin-top: 24px;
  font-size: 14vw;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  line-height: 1;
  white-space: nowrap;
}

/* Le portrait est centré en absolu. Sur mobile il est au milieu de l'écran,
   à partir de 640 px il est posé sur le bas. */
.jk-portrait {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 10;
  width: 280px;
}

/* L'aimant est un `inline-block` : à l'intérieur d'une boîte en largeur fixe,
   il se rétracte au contenu, et le SVG en `width: 100%` calcule alors sa
   largeur contre un conteneur indéfini. Résultat, un portrait plus petit que
   prévu et décalé du centre. Le rendre `block` et pleine largeur rétablit la
   chaîne de mesure. */
.jk-portrait__entree,
.jk-portrait .aimant {
  display: block;
  width: 100%;
}

.jk-portrait__forme {
  display: block;
  width: 100%;
  height: auto;
}

/* La tête 3D. Le rapport hauteur/largeur est fixé ici et pas laissé au
   visualiseur : sans lui, `model-viewer` prend 300 × 150 px par défaut le
   temps du chargement, et le bas de la page saute quand le modèle arrive. */
.jk-tete {
  display: block;
  width: 100%;
  /* `height: auto` est OBLIGATOIRE, et c'est le piège de ce composant :
     `model-viewer` pose lui-même `height: 150px` dans sa feuille interne, et
     une hauteur explicite bat toujours un `aspect-ratio`. Sans cette ligne, le
     cadre restait à 280 × 150 px et la tête s'affichait minuscule au fond
     d'une bande. Mesuré, pas deviné : `getBoundingClientRect` renvoyait bien
     150 de haut. */
  height: auto;
  aspect-ratio: 3 / 4;
  background: transparent;
  /* La barre de chargement reprend l'accent du site plutôt que le bleu par
     défaut du visualiseur. */
  --progress-bar-color: var(--jk-texte);
  --progress-bar-height: 2px;
}

/* Pas de règle sur `::part(default-progress-bar)` : lui poser une opacité fixe
   l'EMPÊCHE de disparaître une fois le modèle chargé, et la barre restait en
   travers du titre. Le visualiseur la masque très bien tout seul. */

/* justify-between items-end, pb-7 */
.jk-hero__bas {
  margin-top: auto;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  padding: 0 24px 28px;
  position: relative;
  z-index: 20;
}

.jk-hero__accroche {
  margin: 0;
  max-width: 160px;
  font-size: clamp(0.75rem, 1.4vw, 1.5rem);
  font-weight: 300;
  text-transform: uppercase;
  letter-spacing: 0.025em;
  line-height: 1.375;
}

/* --- Bouton de contact, valeurs exactes du brief -------------------------- */

.jk-bouton-contact {
  display: inline-block;
  padding: 12px 32px;
  border-radius: 9999px;
  background: linear-gradient(123deg, #18011f 7%, #b600a8 37%, #7621b0 72%, #be4c00 100%);
  box-shadow:
    0 4px 4px rgba(181, 1, 167, 0.25),
    4px 4px 12px #7721b1 inset;
  outline: 2px solid var(--jk-blanc);
  outline-offset: -3px;
  color: var(--jk-blanc);
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-decoration: none;
  white-space: nowrap;
}

/* L'anneau de focus doit rester visible : le bouton porte déjà un `outline`
   décoratif, donc sans cette règle, la mise au point au clavier ne se
   distinguerait pas de l'état normal. */
.jk-bouton-contact:focus-visible {
  outline: 3px solid var(--jk-blanc);
  outline-offset: 3px;
}

/* --- 2. Marquee ----------------------------------------------------------- */

/* pt-24 pb-10 */
.jk-marquee {
  background: var(--jk-fond);
  padding: 96px 0 40px;
  display: grid;
  gap: 12px;
}

.jk-marquee__rangee { overflow: hidden; }

.jk-marquee__piste {
  display: flex;
  width: max-content;
  will-change: transform;
}

.jk-marquee__jeu {
  display: flex;
  gap: 12px;
  padding-right: 12px;
}

/* 420 × 270 dans le brief, ramené à la largeur disponible sur petit écran. */
.jk-tuile {
  flex: 0 0 auto;
  width: min(420px, 74vw);
  aspect-ratio: 420 / 270;
  border-radius: 16px;
  box-shadow: inset 0 1px 0 rgba(215, 226, 234, 0.18);
  background:
    radial-gradient(120% 100% at 20% 10%, #3a4b7a 0%, transparent 60%),
    linear-gradient(150deg, #1a1d24 0%, #0e0f12 100%);
}

.jk-tuile:nth-child(7n + 2) {
  background:
    radial-gradient(110% 120% at 80% 15%, #7a3a5e 0%, transparent 62%),
    linear-gradient(30deg, #14161b 0%, #0c0c0c 100%);
}
.jk-tuile:nth-child(7n + 3) {
  background:
    radial-gradient(130% 90% at 50% 108%, #2f6b6a 0%, transparent 64%),
    linear-gradient(200deg, #101318 0%, #0c0c0c 100%);
}
.jk-tuile:nth-child(7n + 4) {
  background:
    conic-gradient(from 200deg at 32% 40%, #57407e 0deg, transparent 150deg,
      #2c3a58 300deg, transparent 360deg),
    linear-gradient(140deg, #16181e 0%, #0c0c0c 100%);
}
.jk-tuile:nth-child(7n + 5) {
  background:
    radial-gradient(70% 130% at 10% 90%, #8a5228 0%, transparent 58%),
    linear-gradient(180deg, #15171c 0%, #0c0c0c 100%);
}
.jk-tuile:nth-child(7n + 6) {
  background:
    linear-gradient(118deg, #46557f 0%, transparent 46%),
    linear-gradient(300deg, #131519 0%, #0c0c0c 100%);
}
.jk-tuile:nth-child(7n + 7) {
  background:
    radial-gradient(90% 110% at 62% 8%, #6b3f7a 0%, transparent 60%),
    linear-gradient(220deg, #17191f 0%, #0c0c0c 100%);
}

/* --- 3. About ------------------------------------------------------------- */

.jk-about {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 40px;
  padding: 80px 20px;
  text-align: center;
  overflow: hidden;
}

.jk-about__titre {
  font-size: clamp(3rem, 12vw, 160px);
  font-weight: 900;
  text-transform: uppercase;
  line-height: 1;
  letter-spacing: -0.02em;
}

.jk-about__texte {
  margin: 0;
  max-width: 560px;
  font-size: clamp(1rem, 2vw, 1.35rem);
  font-weight: 500;
  line-height: 1.625;
  color: var(--jk-texte);
}

.jk-about__action { margin-top: 24px; }

/* Les quatre objets décoratifs. `pointer-events: none` : ils passent
   par-dessus le texte sur les écrans étroits, et sans ça ils voleraient les
   clics du bouton. */
.jk-objet {
  position: absolute;
  display: block;
  pointer-events: none;
  opacity: 0.85;
}

.jk-objet svg { width: 100%; height: auto; display: block; }

.jk-objet--hg { width: 120px; top: 4%; left: 1%; }
.jk-objet--hd { width: 120px; top: 4%; right: 1%; }
.jk-objet--bg { width: 100px; bottom: 8%; left: 3%; }
.jk-objet--bd { width: 130px; bottom: 8%; right: 3%; }

/* --- 4. Services ---------------------------------------------------------- */

/* La seule section claire du bloc, rounded-t-[40px] puis 60px. */
.jk-services {
  background: #ffffff;
  color: var(--jk-encre);
  border-radius: 40px 40px 0 0;
  padding: 80px 20px;
}

.jk-services__titre {
  margin: 0 0 64px;
  color: var(--jk-encre);
  font-size: clamp(3rem, 12vw, 160px);
  font-weight: 900;
  text-transform: uppercase;
  text-align: center;
  line-height: 1;
  letter-spacing: -0.02em;
}

.jk-services__liste {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: 64rem;
}

.jk-service {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  padding: 32px 0;
  border-bottom: 1px solid rgba(12, 12, 12, 0.15);
}

.jk-service:first-child {
  border-top: 1px solid rgba(12, 12, 12, 0.15);
}

.jk-service__num {
  flex: 0 0 auto;
  font-size: clamp(3rem, 10vw, 140px);
  font-weight: 900;
  line-height: 0.9;
  color: var(--jk-encre);
}

.jk-service__nom {
  margin: 0 0 8px;
  color: var(--jk-encre);
  font-size: clamp(1rem, 2.2vw, 2.1rem);
  font-weight: 500;
  text-transform: uppercase;
}

.jk-service__texte {
  margin: 0;
  max-width: 42rem;
  font-size: clamp(0.85rem, 1.6vw, 1.25rem);
  font-weight: 300;
  line-height: 1.625;
  /* Le brief demande opacity 0.6. Sur du noir #0C0C0C posé sur du blanc, ça
     donne 7,4 de contraste : au-dessus du seuil, donc gardé tel quel. */
  opacity: 0.6;
}

/* --- 5. Projects ---------------------------------------------------------- */

.jk-projets {
  position: relative;
  z-index: 10;
  background: var(--jk-fond);
  border-radius: 40px 40px 0 0;
  margin-top: -40px;
  padding: 80px 20px;
}

.jk-projets__titre {
  margin: 0 0 48px;
  font-size: clamp(3rem, 12vw, 160px);
  font-weight: 900;
  text-transform: uppercase;
  text-align: center;
  line-height: 1;
  letter-spacing: -0.02em;
}

.jk-projets__liste {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Chaque carte se colle en haut et rétrécit quand la suivante la recouvre.
   Le décalage de 28 px par carte laisse voir le bord de celle du dessous. */
.jk-projet-case {
  position: sticky;
  top: calc(96px + var(--carte-decalage, 0px));
  padding-bottom: 24px;
}

.jk-projet-case:nth-child(1) { --carte-decalage: 0px; }
.jk-projet-case:nth-child(2) { --carte-decalage: 28px; }
.jk-projet-case:nth-child(3) { --carte-decalage: 56px; }

.jk-projet {
  border: 2px solid var(--jk-texte);
  border-radius: 40px;
  background: var(--jk-fond);
  padding: 16px;
  transform: scale(calc(1 - var(--recul-projet, 0) * 0.03));
  transform-origin: center top;
  transition: transform 0.15s linear;
}

.jk-projet__haut {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}

.jk-projet__num {
  font-size: clamp(2.4rem, 8vw, 110px);
  font-weight: 900;
  line-height: 0.9;
}

.jk-projet__titres { margin-right: auto; }

.jk-projet__categorie {
  display: block;
  font-size: 0.75rem;
  font-weight: 300;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  opacity: 0.7;
}

.jk-projet__nom {
  margin: 0;
  font-size: clamp(1rem, 2.2vw, 2rem);
  font-weight: 500;
  text-transform: uppercase;
}

.jk-bouton-fantome {
  display: inline-block;
  padding: 12px 32px;
  border: 2px solid var(--jk-texte);
  border-radius: 9999px;
  color: var(--jk-texte);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.2s;
}

.jk-bouton-fantome:hover {
  background: rgba(215, 226, 234, 0.1);
}

/* 40 % à gauche en deux images, 60 % à droite en une seule. */
.jk-projet__grille {
  display: grid;
  grid-template-columns: 40fr 60fr;
  gap: 12px;
}

.jk-projet__colonne {
  display: grid;
  gap: 12px;
  align-content: start;
}

.jk-visuel {
  border-radius: 40px;
  box-shadow: inset 0 1px 0 rgba(215, 226, 234, 0.18);
  background:
    radial-gradient(110% 110% at 25% 10%, #3c4c78 0%, transparent 62%),
    linear-gradient(155deg, #191c23 0%, #0d0e11 100%);
}

.jk-visuel--haut { height: clamp(130px, 16vw, 230px); }
.jk-visuel--bas  { height: clamp(160px, 22vw, 340px); }

/* Quand l'emplacement est une vraie image et non un aplat, elle doit remplir
   le cadre en se recadrant. Sans `object-fit`, l'image impose sa propre
   proportion, la hauteur fixée ci-dessus est ignorée et la colonne se
   déforme. `object-position: top` garde le haut de la capture, qui est la
   partie qui montre quelque chose. */
img.jk-visuel {
  width: 100%;
  object-fit: cover;
  object-position: top center;
}

img.jk-visuel--grand { height: 100%; }

.jk-visuel--grand {
  height: 100%;
  min-height: clamp(302px, 38vw, 582px);
  background:
    radial-gradient(90% 120% at 70% 15%, #7a4a2c 0%, transparent 60%),
    radial-gradient(80% 80% at 20% 95%, #2f5a6b 0%, transparent 58%),
    linear-gradient(200deg, #16181e 0%, #0c0c0c 100%);
}

/* ==========================================================================
   ANIMATIONS COMMUNES AUX DEUX BLOCS
   ========================================================================== */

/* FadeIn du brief : whileInView, y 30 par défaut, easing [0.25,0.1,0.25,1]. */
.js [data-apparition] {
  opacity: 0;
  transform: translate3d(var(--dx, 0px), var(--dy, 30px), 0);
  transition:
    opacity var(--duree, 0.7s) cubic-bezier(0.25, 0.1, 0.25, 1),
    transform var(--duree, 0.7s) cubic-bezier(0.25, 0.1, 0.25, 1);
  transition-delay: var(--retard, 0s);
}

.js [data-apparition].est-entre {
  opacity: 1;
  transform: none;
}

/* Entrée par l'échelle, 0,95 vers 1. PLUS EMPLOYÉE DANS LA PAGE depuis le
   30/08/2026 : c'était l'effet des cartes du bloc Prisma, et ce bloc a laissé
   la place à une section écrite dans la charte du site. La règle et son script
   sont gardés parce qu'ils ne coûtent rien et qu'un brief suivant les
   redemandera. Le jour où ce n'est plus vrai, ils se retirent d'un bloc. */
.js [data-apparition-echelle] {
  opacity: 0;
  transform: scale(0.95);
  transition:
    opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: var(--retard, 0s);
}

.js [data-apparition-echelle].est-entre {
  opacity: 1;
  transform: none;
}

/* WordsPullUp : chaque mot monte de 20 px, décalé de 0,08 s. */
.mot-monte {
  display: inline-block;
  position: relative;
}

.js [data-mots-montent] .mot-monte {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 0.6s cubic-bezier(0.25, 0.1, 0.25, 1),
    transform 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
  transition-delay: calc(var(--i, 0) * 0.08s);
}

.js [data-mots-montent].est-entre .mot-monte {
  opacity: 1;
  transform: none;
}

/* AnimatedText : chaque caractère passe de 0,2 à 1 d'opacité selon la position
   du défilement.

   Le brief le fait par l'opacité. Ici c'est la COULEUR qui varie, pour la même
   raison que sur le manifeste du site : un texte à 0,2 d'opacité tombe à 1,3 de
   contraste, donc illisible, et c'est l'état de la majorité des caractères à
   tout instant. L'effet visuel est le même, l'écart de lisibilité non. */
.lettre-scroll {
  transition: color 0.2s linear;
}

.brief--jack .lettre-scroll { color: #6b7278; }
.brief--jack .lettre-scroll.est-lue { color: #d7e2ea; }


/* ==========================================================================
   RESPONSIVE — les breakpoints de Tailwind
   ========================================================================== */

@media (min-width: 640px) {
  .jk-hero__titre { font-size: 15vw; margin-top: 16px; }
  .jk-hero__bas { padding-bottom: 32px; }
  .jk-hero__accroche { max-width: 220px; }
  .jk-portrait {
    width: 360px;
    top: auto;
    bottom: 0;
    transform: translate(-50%, 0);
  }
  .jk-bouton-contact { padding: 14px 40px; font-size: 0.875rem; }
  .jk-marquee { padding-top: 128px; }
  .jk-about { padding: 80px 32px; gap: 56px; }
  .jk-objet--hg, .jk-objet--hd { width: 160px; }
  .jk-objet--bg { width: 140px; }
  .jk-objet--bd { width: 170px; }
  .jk-objet--hg { left: 2%; } .jk-objet--hd { right: 2%; }
  .jk-objet--bg { left: 6%; } .jk-objet--bd { right: 6%; }
  .jk-services { border-radius: 50px 50px 0 0; padding: 96px 32px; }
  .jk-services__titre { margin-bottom: 80px; }
  .jk-service { padding: 40px 0; }
  .jk-projets { border-radius: 50px 50px 0 0; margin-top: -48px; padding: 96px 32px; }
  .jk-projet { border-radius: 50px; padding: 24px; }
  .jk-visuel { border-radius: 50px; }

}

@media (min-width: 768px) {
  .jk-hero { padding-top: 60px; }
  .jk-hero__titre-cadre { padding: 0 40px; }
  .jk-hero__titre { font-size: 16vw; margin-top: -20px; }
  .jk-hero__bas { padding: 0 40px 40px; }
  .jk-hero__accroche { max-width: 260px; }
  .jk-portrait { width: 440px; }
  .jk-bouton-contact { padding: 16px 48px; font-size: 1rem; }
  .jk-marquee { padding-top: 160px; }
  .jk-about { padding: 80px 40px; gap: 64px; }
  .jk-objet--hg, .jk-objet--hd { width: 210px; }
  .jk-objet--bg { width: 180px; }
  .jk-objet--bd { width: 220px; }
  .jk-objet--hg { left: 4%; } .jk-objet--hd { right: 4%; }
  .jk-objet--bg { left: 10%; } .jk-objet--bd { right: 10%; }
  .jk-services { border-radius: 60px 60px 0 0; padding: 128px 40px; }
  .jk-services__titre { margin-bottom: 112px; }
  .jk-service { gap: 32px; padding: 48px 0; }
  .jk-projets { border-radius: 60px 60px 0 0; margin-top: -56px; padding: 128px 40px; }
  .jk-projet { border-radius: 60px; padding: 32px; }
  .jk-projet-case { top: calc(128px + var(--carte-decalage, 0px)); }
  .jk-visuel { border-radius: 60px; }

}

@media (min-width: 1024px) {
  .jk-hero__titre { font-size: 17.5vw; }
  .jk-portrait { width: 520px; }

}

/* ==========================================================================
   RÉDUIRE LES ANIMATIONS

   Les états de départ sont annulés, pas seulement les transitions : un élément
   qui part de `opacity: 0` resterait invisible pour toujours.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .js [data-apparition],
  .js [data-apparition-echelle],
  .js [data-mots-montent] .mot-monte {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  .jk-marquee__piste { transform: none !important; }

  /* Le hero est le seul endroit du site où quelque chose bouge tout seul et
     sans fin. La vidéo est arrêtée par le script, ce qui l'arrête vraiment :
     une vidéo masquée en CSS continue à décoder image par image. Ici on ne
     traite donc que ce que le script ne peut pas faire, les trois apparitions.

     Le bouton de pause, lui, RESTE. Le réglage système dit ce qu'on préfère
     par défaut, il ne dit pas qu'on refuse de voir la vidéo : le bouton
     bascule alors dans l'autre sens et la lance. */
  .js .accueil__titre,
  .js .accueil__accroche,
  .js .accueil__hero .accueil__cta {
    animation: none;
    opacity: 1;
    transform: none;
  }

  /* La rotation continue du modèle est exactement le mouvement ample que ce
     réglage demande d'éviter. L'attribut est retiré par le script, ce qui
     l'arrête vraiment au lieu de le masquer. */
  .jk-projet { transform: none !important; }

  .brief--jack .lettre-scroll { color: #d7e2ea; }
}

/* ==========================================================================
   IMPRESSION
   ========================================================================== */

@media print {
  /* Sur papier, la vidéo ne peut pas exister et l'affiche sortirait en aplat
     sombre sur une page entière. Le hero redevient donc un titre et un
     paragraphe, à l'encre du reste du document. Le voile et le bouton de
     pause n'ont plus d'objet. */
  .accueil {
    min-height: 0;
    background: none;
    overflow: visible;
  }

  .accueil__video,
  .accueil__voile,
  .accueil__pause,
  .accueil__nav { display: none !important; }

  .accueil__hero { padding: 0 0 24px; }

  .accueil__titre,
  .accueil__accroche {
    color: #000 !important;
    text-shadow: none;
    animation: none !important;
    opacity: 1 !important;
  }

  .accueil__titre em { color: #444 !important; }

  .jk-marquee { display: none !important; }

  /* `transition: none` est INDISPENSABLE, et pas seulement l'opacité finale.
     Sans lui, l'élément commence à peine son fondu au moment où le navigateur
     fige la page, et il sort du papier à moitié transparent. Mesuré ici :
     0,0001 d'opacité sur le cinquième service. C'est le piège que le socle
     documente depuis Terra Rossa, et je l'ai refait. */
  .js [data-apparition],
  .js [data-apparition-echelle],
  .js [data-mots-montent] .mot-monte {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  .lettre-scroll { transition: none !important; }

  .jk-hero, .jk-about { min-height: 0; height: auto; }
  .jk-projet-case { position: static; }
  .jk-projet { transform: none !important; }
  .lettre-scroll { color: #000 !important; }
}
