/* ─────────────────────────────────────────────────────────────
   Epitech Africa — visual overrides
   Loaded after wp-inline-styles.css and theme style.css.
   Goals:
     0) Restore missing WP preset tokens (palette, fonts, spacing)
     1) Kill border-radius everywhere (sharp, editorial look)
     2) Add pronounced frosted-glass blur on glassy panels
     3) Support the new `hero_custom` section block
     4) Remove gap between header and content
   ───────────────────────────────────────────────────────────── */

/* First-block top spacing — header gap matches epitech.eu.
   Layout containers (main, .entry-content) always reset to 0 so they
   don't carry their own spacing.
   First visible block (skipping Astro <link> hydration nodes via
   :first-of-type per tag): default uses --wp--preset--spacing--xxl
   (clamp 80→128px fluid) so internal pages get visible breathing room
   below the navbar at every breakpoint, matching epitech.eu's
   internal-page gap.
   Home hero overrides this to 0 — the home page is full-bleed and
   intentionally hugs the header. Detection via `body:has(.hero-home)`,
   which captures the WP ACF home-hero block; any page without one is
   treated as an internal page. */
.wp-site-blocks > main,
.wp-site-blocks > main > .entry-content {
  margin-top: 0 !important;
  padding-top: 0 !important;
}
.wp-site-blocks > main > .entry-content > div:first-of-type,
.wp-site-blocks > main > .entry-content > section:first-of-type,
.wp-site-blocks > main > .entry-content > header:first-of-type,
.wp-site-blocks > main > .entry-content > article:first-of-type {
  margin-top: var(--wp--preset--spacing--xxl) !important;
  padding-top: 0 !important;
}
body:has(.hero-home, .wp-block-acf-hero-home)
  .wp-site-blocks
  > main
  > .entry-content
  > div:first-of-type,
body:has(.hero-home, .wp-block-acf-hero-home)
  .wp-site-blocks
  > main
  > .entry-content
  > section:first-of-type,
body:has(.hero-home, .wp-block-acf-hero-home)
  .wp-site-blocks
  > main
  > .entry-content
  > header:first-of-type,
body:has(.hero-home, .wp-block-acf-hero-home)
  .wp-site-blocks
  > main
  > .entry-content
  > article:first-of-type {
  margin-top: 0 !important;
}
/* Same opt-out, content-derived: a full-bleed primary-colored hero as the
   first block (e.g. /prendre-rdv, /jpo) is meant to hug the navbar, just
   like home — no body-level marker needed. The first-of-type rule above
   forces padding-top:0 (assuming spacing comes from the outer margin); for
   these heroes the spacing instead lives *inside* the blue, so we restore
   it here using the same fluid token (80→128px). */
.wp-site-blocks > main > .entry-content > .alignfull.has-primary-background-color:first-of-type {
  margin-top: 0 !important;
  padding-top: var(--wp--preset--spacing--xxl) !important;
  padding-bottom: var(--wp--preset--spacing--xxl) !important;
}

/* Hero home — halve the top gap (navbar → punchline). EDMS-89.
 * Two padding layers stack on the home hero:
 *   - External: the .alignfull.has-primary-background-color:first-of-type rule
 *     above pads the blue block with --xxl (≈107px at 1280).
 *   - Internal: style.css .hero-home_inner adds --xl on top (≈64px at 1280).
 * Total ≈170px; PO wants ≈85px. We halve both, scoped to .hero-home so the
 * blue heroes on /prendre-rdv and /jpo (which share :first-of-type) keep
 * their gap unchanged. */
.wp-site-blocks > main > .entry-content > .hero-home.alignfull.has-primary-background-color:first-of-type {
  padding-top: calc(var(--wp--preset--spacing--xxl) / 2) !important;
}
.hero-home .hero-home_inner {
  padding-top: calc(var(--wp--preset--spacing--xl) / 2);
}

/* 0) Missing WordPress preset tokens.
 *
 * The vendored wp-inline-styles.css snapshot does not include the
 * `global-styles-inline-css` block that WP normally emits, so tokens
 * like --wp--preset--color--primary were empty on :root. That made
 * every .has-*-background-color / .has-*-color class resolve to
 * transparent — hero had no background, text lost its palette, fonts
 * fell back to sans-serif, spacing collapsed. Defining them here
 * restores the whole Epitech palette without touching the snapshot.
 *
 * Values mirror the Epitech brandbook (see memory/brandbook_epitech.md).
 */
:root {
  /* Palette */
  --wp--preset--color--primary: #013afb;
  --wp--preset--color--secondary: #0a1033;
  --wp--preset--color--tertiary: #f2f4f8;
  --wp--preset--color--tech: #2dffa8;
  --wp--preset--color--together: #ff3377;
  --wp--preset--color--tomorrow: #ff5f3a;
  --wp--preset--color--white: #ffffff;
  --wp--preset--color--black: #000000;
  --wp--preset--color--background: #ffffff;
  --wp--preset--color--foreground: #0a1033;

  /* Font families */
  --wp--preset--font-family--primary:
    "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --wp--preset--font-family--secondary: "Anton", Impact, sans-serif;
  --wp--preset--font-family--body:
    "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --wp--preset--font-family--heading: "Anton", Impact, sans-serif;
  --secondary: "Anton", Impact, sans-serif;

  /* Font sizes (fluid). Heading values mirror epitech.eu's theme.json
     (epitech-refonte) — canonical clamp() ramps from 20px → 96px so titles
     scale visibly between mobile/desktop and match the reference brand. */
  --wp--preset--font-size--xs: 0.75rem;
  --wp--preset--font-size--s: 0.875rem;
  --wp--preset--font-size--m: 1rem;
  --wp--preset--font-size--l: 1.25rem;
  --wp--preset--font-size--xl: 1.75rem;
  --wp--preset--font-size--xxl: clamp(2rem, 4vw, 3rem);
  --wp--preset--font-size--heading-xxs: clamp(
    1.25rem,
    1.1667rem + 0.2778vw,
    1.5rem
  );
  --wp--preset--font-size--heading-xs: clamp(
    1.5rem,
    1.3333rem + 0.5556vw,
    2rem
  );
  --wp--preset--font-size--heading-s: clamp(2rem, 1.8333rem + 0.5556vw, 2.5rem);
  --wp--preset--font-size--heading-m: clamp(2.5rem, 2rem + 1.6667vw, 4rem);
  --wp--preset--font-size--heading-l: clamp(
    2.5rem,
    1.8333rem + 2.2222vw,
    4.5rem
  );
  --wp--preset--font-size--heading-xl: clamp(4.5rem, 4rem + 1.6667vw, 6rem);
  --wp--preset--font-size--body-s: 0.875rem;
  --wp--preset--font-size--body-m: 1rem;
  --wp--preset--font-size--body-l: 1.125rem;
  --wp--preset--font-size--body-xl: 1.25rem;

  /* Spacing scale. --xxl mirrors epitech.eu's theme.json fluid clamp
     (80→128px) so the gap below the navbar on internal pages and other
     spacing-xxl-keyed paddings scale across viewports. The smaller tokens
     stay fixed; bumping them too could shift card/grid layouts unrelated
     to the title parity work. */
  --wp--preset--spacing--xs: 0.5rem;
  --wp--preset--spacing--s: 1rem;
  --wp--preset--spacing--m: 1.5rem;
  --wp--preset--spacing--l: 2.5rem;
  --wp--preset--spacing--xl: 4rem;
  --wp--preset--spacing--xxl: clamp(5rem, 4rem + 3.3333vw, 8rem);
}

/* Background / text utility classes that reference the tokens.
 * WP normally emits these; our snapshot is missing the ones that map
 * `.has-primary-background-color` → `var(--wp--preset--color--primary)`. */
.has-primary-background-color {
  background-color: var(--wp--preset--color--primary) !important;
}
.has-secondary-background-color {
  background-color: var(--wp--preset--color--secondary) !important;
}
.has-tertiary-background-color {
  background-color: var(--wp--preset--color--tertiary) !important;
}
.has-tech-background-color {
  background-color: var(--wp--preset--color--tech) !important;
}
.has-together-background-color {
  background-color: var(--wp--preset--color--together) !important;
}
.has-tomorrow-background-color {
  background-color: var(--wp--preset--color--tomorrow) !important;
}
.has-white-background-color {
  background-color: var(--wp--preset--color--white) !important;
}

.has-terciary-background-color {
  background-color: var(--wp--preset--color--tertiary) !important;
}
.has-terciary-color {
  color: var(--wp--preset--color--tertiary) !important;
}

.has-primary-color {
  color: var(--wp--preset--color--primary) !important;
}
.has-secondary-color {
  color: var(--wp--preset--color--secondary) !important;
}
.has-tech-color {
  color: var(--wp--preset--color--tech) !important;
}
.has-together-color {
  color: var(--wp--preset--color--together) !important;
}
.has-tomorrow-color {
  color: var(--wp--preset--color--tomorrow) !important;
}
.has-white-color {
  color: var(--wp--preset--color--white) !important;
}

/* 1) No border radius, anywhere inside the portal shell */
.wp-site-blocks *,
.wp-site-blocks *::before,
.wp-site-blocks *::after,
.edms-landing-header *,
.edms-landing-footer * {
  border-radius: 0 !important;
}

/* 1a) Editor escape hatch — when an EditableImage sets an explicit
 * border-radius via the `--edms-img-radius` custom property (EDMS-115
 * Phase 1 toolbar slider), let that value win over the global rule
 * above. Same `!important` weight, higher specificity via the attribute
 * selector on `style*=--edms-img-radius`. The variable is only set when
 * the editor opts in, so untouched images keep the sharp brand look. */
.wp-site-blocks [style*="--edms-img-radius"],
.app-portal-preview [style*="--edms-img-radius"] {
  border-radius: var(--edms-img-radius, 0) !important;
}

/* 1b) AlumniBlock: restore circular alumni photos defeated by the global
 * radius:0 !important rule above (EDMS-30). The component's inline border
 * color (--wp--preset--color--tech) and width (2px) survive the global rule;
 * only border-radius needs an !important escape hatch. */
.wp-site-blocks .edms-alumni-carousel figure.has-custom-border,
.wp-site-blocks .edms-alumni-carousel figure.has-custom-border > img {
  border-radius: 999px !important;
}

/* 2) Stronger frosted blur on glassy surfaces */
.wp-site-blocks .hero-v2 .wp-block-group.has-global-padding,
.wp-site-blocks .hero-home_card,
.wp-site-blocks .hero-custom_card,
.wp-site-blocks .is-glass,
.wp-site-blocks .wp-block-group.is-style-hero-v2 > .wp-block-group {
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
}

/* Keep CTA buttons square (override wp pill default) */
.wp-site-blocks .wp-block-button__link,
.wp-site-blocks .wp-element-button {
  border-radius: 0 !important;
}

/* ─── 3) hero_custom block ─────────────────────────────────── */
.hero-custom {
  position: relative;
  overflow: hidden;
  padding: clamp(80px, 12vw, 160px) clamp(24px, 5vw, 96px)
    clamp(64px, 10vw, 128px);
  color: #fff;
  isolation: isolate;
}
.hero-custom__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.hero-custom__overlay {
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--edms-color-primary, #013afb);
  /* opacity is set inline via style prop on the element */
  backdrop-filter: blur(6px) saturate(120%);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
}
.hero-custom__inner {
  position: relative;
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 28px;
}
.hero-custom__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.hero-custom__tag {
  display: inline-flex;
  align-items: center;
  padding: 8px 18px;
  border: 1.5px solid rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: #fff;
  text-transform: none;
}
.hero-custom__title {
  font-family: "Anton", Impact, sans-serif;
  font-size: clamp(2.5rem, 6.5vw, 5.25rem);
  line-height: 0.95;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  margin: 0;
  max-width: 12ch;
}
.hero-custom__title::after {
  content: "_";
  color: var(--edms-color-accent, #2dffa8);
  margin-left: 0.05em;
}
.hero-custom__desc {
  max-width: 640px;
  font-size: clamp(1rem, 1.4vw, 1.15rem);
  line-height: 1.6;
  margin: 0;
  opacity: 0.96;
}
.hero-custom__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 8px;
}
.hero-custom__cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 14px 28px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-decoration: none;
  border: 2px solid var(--edms-color-accent, #2dffa8);
  transition:
    transform 0.2s ease,
    background 0.2s ease;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-size: 0.95rem;
}
.hero-custom__cta--primary {
  background: var(--edms-color-accent, #2dffa8);
  color: #0a1033;
}
.hero-custom__cta--secondary {
  background: transparent;
  color: #fff;
  border-color: rgba(255, 255, 255, 0.7);
}
.hero-custom__cta:hover {
  transform: translateY(-2px);
}
/* ─── Scroll reveal for portal sections ───────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .wp-site-blocks .wp-block-group.is-style-section {
    opacity: 0;
    transform: translateY(32px);
    transition:
      opacity 0.7s ease,
      transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
    will-change: opacity, transform;
  }
  .wp-site-blocks .wp-block-group.is-style-section.is-visible {
    opacity: 1;
    transform: none;
  }
  /* Admin canvas: the scroll-reveal IntersectionObserver lives in the
   * public-site chrome JS and observes `window` scroll. The admin preview
   * scrolls inside `.ppe-preview` and never loads that JS, so sections
   * would stay at opacity:0 forever (only Hero — `is-style-hero-v2` — would
   * show). Force them to the settled state so the editor matches the
   * published page's after-scroll appearance. */
  .app-portal-preview .wp-block-group.is-style-section {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Stagger KPI cards & salary rows inside a visible section */
  .wp-site-blocks .is-visible .salary-chart__row,
  .wp-site-blocks .is-visible .edms-countup {
    animation: edms-rise 0.7s both;
  }
  .wp-site-blocks .is-visible .salary-chart__row:nth-child(2) {
    animation-delay: 0.05s;
  }
  .wp-site-blocks .is-visible .salary-chart__row:nth-child(3) {
    animation-delay: 0.1s;
  }
  .wp-site-blocks .is-visible .salary-chart__row:nth-child(4) {
    animation-delay: 0.15s;
  }
  .wp-site-blocks .is-visible .salary-chart__row:nth-child(5) {
    animation-delay: 0.2s;
  }
  .wp-site-blocks .is-visible .salary-chart__row:nth-child(6) {
    animation-delay: 0.25s;
  }
  .wp-site-blocks .is-visible .salary-chart__row:nth-child(7) {
    animation-delay: 0.3s;
  }
}
@keyframes edms-rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.hero-custom__cta:focus-visible {
  outline: 2px solid var(--edms-color-accent, #2dffa8);
  outline-offset: 3px;
}

/* ─── Hero (Home) typography force ─────────────────────────────────────
 * wp-inline-styles.css has a blanket rule:
 *   p, li, span, a:not(.wp-block-heading), ... { font-family: ... body font }
 * which has higher specificity than the .hero-home_punchline font-family
 * set on the parent div — so every span child of the punchline falls back
 * to the body font. The live portal shell used to patch this with an
 * inline <style> block, but that override never reached the admin
 * preview. Putting the rules here (loaded via editorCssUrls) applies them
 * everywhere the portal blocks render. */
.hero-home_punchline,
.hero-home_punchline span {
  font-family: "Anton", sans-serif !important;
  text-transform: uppercase !important;
  font-weight: 400 !important;
}
.hero-home_signature .tech,
.hero-home_signature .together,
.hero-home_signature .tomorrow,
.hero-home_signature_inner span {
  font-family: "Anton", sans-serif !important;
  text-transform: uppercase !important;
  font-weight: 400 !important;
}

/* ── TableBlock — fee schedules + comparison grids ────────────────────
   The WP theme's default table styles look generic. Pin an Epitech-brand
   header (primary blue), a highlighted tfoot total row, and comfortable
   line rhythm so fee tables read at a glance. */
.edms-table {
  margin: 0 !important;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: 12px;
  border: 1px solid rgba(15, 23, 42, 0.08);
}
.edms-table > table {
  width: 100%;
  min-width: 480px;
  border-collapse: collapse;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  table-layout: fixed;
}
.edms-table thead th {
  background: var(--wp--preset--color--primary, #013afb);
  color: #fff !important;
  font-family: "Anton", sans-serif;
  font-weight: 400;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
  padding: 14px 18px;
  text-align: center;
  border: none !important;
}
.edms-table thead th.edms-table__col-label {
  text-align: left;
}
.edms-table tbody td {
  padding: 14px 18px;
  border-top: 1px solid rgba(15, 23, 42, 0.08) !important;
  border-bottom: none !important;
  border-left: none !important;
  border-right: none !important;
  text-align: center;
  font-size: 0.95rem;
  color: #0f172a;
}
.edms-table tbody td.edms-table__row-label {
  text-align: left;
  font-weight: 700;
}
.edms-table tfoot td {
  padding: 16px 18px;
  background: rgba(1, 58, 251, 0.04);
  border-top: 2px solid var(--wp--preset--color--primary, #013afb) !important;
  font-weight: 800;
  font-size: 1rem;
  text-align: center;
  color: var(--wp--preset--color--primary, #013afb);
}
.edms-table tfoot td.edms-table__row-label {
  text-align: left;
}
.edms-table__caption {
  display: block;
  margin-top: 12px;
  font-size: 0.85rem;
  color: rgba(15, 23, 42, 0.6);
  font-style: italic;
  text-align: center;
}

/* ── Formations section: IBM Plex Sans on large headings per feedback ── */
.wp-block-group.is-style-section h2.has-text-align-center {
  font-size: clamp(2rem, 4vw, 3rem);
  line-height: 1.15;
}

/* ── Stats section: numbers in Anton bold ────────────────────────────── */
.edms-countup {
  font-family: "Anton", Impact, sans-serif !important;
  font-weight: 400 !important;
}

/* Cards-2 heading highlight: theme CSS ships with a hardcoded 153×64 px
   rectangle behind the first heading, which crops longer labels like
   "Organisations internationales" or "Écoles et universités". Let it
   hug the actual text box instead of a fixed size. */
.wp-block-group.is-style-cards-2
  > .wp-block-group
  > .wp-block-heading:first-of-type {
  display: inline-block;
  width: auto;
}
.wp-block-group.is-style-cards-2
  > .wp-block-group
  > .wp-block-heading:first-of-type::before {
  width: 100% !important;
  height: 100% !important;
}

/* ── Responsive table: comfortable on mobile ─────────────────────────── */
@media (max-width: 640px) {
  .edms-table thead th,
  .edms-table tbody td,
  .edms-table tfoot td {
    padding: 10px 10px;
    font-size: 0.82rem;
  }
}

/* ── Admission photos: prefer WebP, ensure consistent aspect ─────── */
.wp-block-group.is-style-section img,
.entry-content .wp-block-image img {
  image-rendering: auto;
  object-fit: cover;
}

/* ── Per-section "Apparence" knobs: title + chevron size overrides ───
   Server (edms/server.ts buildSectionStyleDecls) emits CSS custom
   properties on `.edms-section-wrap` when an admin sets the matching
   knob in `sectionStyleFields`. Empty knob = no inline prop = default
   heading preset wins. `!important` here so the override beats the
   utility classes (.has-heading-*-font-size) which themselves use
   !important. Selector via [style*="--edms-..."] limits the rule to
   wrappers that actually carry the prop. */
.edms-section-wrap[style*="--edms-title-size"] :is(h1, h2, h3) {
  font-size: var(--edms-title-size) !important;
}
.edms-section-wrap[style*="--edms-decoration-size"] p.is-style-balises::before,
.edms-section-wrap[style*="--edms-decoration-size"]
  p.is-style-lead-balises::before {
  font-size: var(--edms-decoration-size) !important;
}

/* ── Section-level chevron toggle ────────────────────────────────────────
   Admin can tick "Masquer les chevrons décoratifs" on any section in the
   "Apparence" group. The server (`buildSectionWrapperClasses` in
   edms/server.ts) then adds `edms-no-chevrons` to the `.edms-section-wrap`
   div, and these rules wipe every chevron pseudo-element scoped under it
   — across every chevron-bearing class found in the codebase audit
   (.is-style-balises, .is-style-lead-balises, .hero-home_*, .is-style-hero-v2,
   .campusSlider_card_excerpt, .video-circle-text, .is-style-picture-text-box,
   .is-style-text-box-picture, .is-style-hero-form). Wrapper-scoped so other
   sections on the same page keep their decoration. `!important` to beat the
   pseudo-element content rules in style.css. */
.edms-section-wrap.edms-no-chevrons :is(h1, h2, h3, h4, h5, h6, p).is-style-balises::before,
.edms-section-wrap.edms-no-chevrons :is(h1, h2, h3, h4, h5, h6, p).is-style-balises::after,
.edms-section-wrap.edms-no-chevrons :is(h1, h2, h3, h4, h5, h6, p).is-style-lead-balises::before,
.edms-section-wrap.edms-no-chevrons :is(h1, h2, h3, h4, h5, h6, p).is-style-lead-balises::after,
.edms-section-wrap.edms-no-chevrons div.is-style-balises > p:first-of-type::before,
.edms-section-wrap.edms-no-chevrons div.is-style-balises > p:last-of-type::after,
.edms-section-wrap.edms-no-chevrons div.is-style-lead-balises > p:first-of-type::before,
.edms-section-wrap.edms-no-chevrons div.is-style-lead-balises > p:last-of-type::after,
.edms-section-wrap.edms-no-chevrons .hero-home_description::before,
.edms-section-wrap.edms-no-chevrons .hero-home_description::after,
.edms-section-wrap.edms-no-chevrons .hero-home_signature .hero-home_signature_inner::before,
.edms-section-wrap.edms-no-chevrons .hero-home_signature .hero-home_signature_inner::after,
.edms-section-wrap.edms-no-chevrons .wp-block-group.is-style-hero-v2 > .wp-block-group:first-child p:first-of-type::before,
.edms-section-wrap.edms-no-chevrons .wp-block-group.is-style-hero-v2 > .wp-block-group:first-child p:last-of-type::after,
.edms-section-wrap.edms-no-chevrons .wp-block-group.is-style-hero-form > .wp-block-group:first-child p:first-of-type::before,
.edms-section-wrap.edms-no-chevrons .wp-block-group.is-style-hero-form > .wp-block-group:first-child p:last-of-type::after,
.edms-section-wrap.edms-no-chevrons .wp-block-columns.is-style-picture-text-box > .wp-block-column:last-child > p:first-of-type::before,
.edms-section-wrap.edms-no-chevrons .wp-block-columns.is-style-picture-text-box > .wp-block-column:last-child > p:last-of-type::after,
.edms-section-wrap.edms-no-chevrons .wp-block-columns.is-style-text-box-picture > .wp-block-column:first-child > p:first-of-type::before,
.edms-section-wrap.edms-no-chevrons .wp-block-columns.is-style-text-box-picture > .wp-block-column:first-child > p:last-of-type::after,
.edms-section-wrap.edms-no-chevrons .campusSlider_card_excerpt p:first-child::before,
.edms-section-wrap.edms-no-chevrons .campusSlider_card_excerpt p:last-child::after,
.edms-section-wrap.edms-no-chevrons .video-circle-text::before,
.edms-section-wrap.edms-no-chevrons .video-circle-text::after {
  content: none !important;
}

/* Single-arrow variant of .is-style-arrow-link.
   Overrides only the SVG mask: paths 1+2+3 of the original (chevron head
   + horizontal shaft); path 4 (the dimmed overshoot tip that creates the
   "double arrow" look) is dropped. Inherits size, color, margin, and
   transition from the parent .is-style-arrow-link rule in style.css. */
.is-style-arrow-link.is-style-arrow-link-single .wp-block-button__link:after {
  mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.9291 2.00002L10.7305 3.19861L19.6088 12.0769L20.8073 10.8783L11.9291 2.00002Z" fill="currentColor"/><path d="M19.5815 12.0515L10.8315 20.8014L12.0301 22L20.7801 13.2501L19.5815 12.0515Z" fill="currentColor"/><path d="M19.6101 11.1884H2.021V12.8835H19.6101V11.1884Z" fill="currentColor"/></svg>');
}

/* EDMS-22: Language toggle should reflect the `is_active` flag from the
 * navigation singleton's `languages[]`, not assume the active language is
 * always :first-child. The legacy rule in style.css:11940 bolds whichever
 * <a> is first in DOM order, which silently mismatches when the admin
 * marks a non-first language as active. These two rules tie on specificity
 * (0,3,1) with the legacy :first-child rule but win via load order plus
 * !important — and target every <a> by class regardless of position. */
.site-header .site-header-langs a.is-active {
  border-bottom-color: #013AFB !important;
  font-weight: 700 !important;
}
.site-header .site-header-langs a.is-inactive {
  border-bottom-color: transparent !important;
  font-weight: 300 !important;
}

/* ──────────────────────────────────────────────────────────────────────────
 * Homepage agenda block — empty-state line.
 * Class-driven (no inline color in JSX, per designer-SOUL.md rule 1). Inherits
 * the legacy light surface, so we use a token-aligned muted gray with thin
 * top/bottom rules to make an empty campus tab feel intentional rather than
 * broken. Default surface targets the `white` / unset background EventCardsBlock
 * uses on the homepage; if the section is ever themed dark again, the
 * `.has-secondary-background-color` override below kicks in.
 * Future token: `--edms-color-text-muted` (light) / `--edms-color-text-muted-on-dark`. */
.onglets.onglets__campus .event-empty {
  margin: 0;
  padding: 24px 0;
  color: #6b7280;
  text-align: center;
  font-style: italic;
  border-top: 1px solid rgba(15, 23, 42, 0.08);
  border-bottom: 1px solid rgba(15, 23, 42, 0.08);
}
.has-secondary-background-color .onglets.onglets__campus .event-empty {
  color: rgba(255, 255, 255, 0.65);
  border-top-color: rgba(255, 255, 255, 0.12);
  border-bottom-color: rgba(255, 255, 255, 0.12);
}

/* ──────────────────────────────────────────────────────────────────────────
 * Homepage event card — title length safety.
 *
 * The legacy chrome rule `.event .event-container` is `display:flex` with
 * `flex-wrap:nowrap` above 1024px and gives the label `font-size:32px`. When
 * Zeno feeds a title longer than ~30 characters two things break:
 *   1) the label keeps growing horizontally and squeezes the "Je m'inscris"
 *      CTA into a single-character column (one letter per line),
 *   2) at multi-line wrap the card stretches vertically and pushes the
 *      "Voir tous les événements" button away from the meta pill.
 *
 * Fix: allow the label to shrink, clamp it to 3 lines, anchor the CTA at
 * its natural width so it never collapses. Keep these rules generic to
 * `.event .event-container` so every campus tab benefits.
 * Knob, not hardcode — bump `-webkit-line-clamp` or the CTA min-width
 * here if the design ever needs more headroom. */
.event .event-container {
  align-items: flex-start;
  min-width: 0;
}
.event .event-container .label {
  flex: 1 1 auto;
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: normal;
  overflow-wrap: anywhere;
  line-height: 1.15;
}
.event .event-container .wp-block-button {
  flex: 0 0 auto;
  min-width: 160px;
}
.event .event-container .wp-block-button .wp-block-button__link {
  white-space: nowrap;
  display: inline-block;
}
@media screen and (max-width: 600px) {
  .event .event-container .wp-block-button {
    min-width: 0;
    width: 100%;
  }
  .event .event-container .wp-block-button .wp-block-button__link {
    width: 100%;
    text-align: center;
  }
}

/* ───────────────────────────────────────────────────────────────────────────
 * EDMS-104: metiers template chrome alignment
 *
 * Metiers pages emit body.metiers-template (set by metiersSchema.portal.bodyClass).
 * The site-header is alignfull; without the inner container being constrained
 * to the same width as `.is-layout-constrained` content, the logo + nav sit at
 * the very left edge while content is indented — looking visibly misaligned.
 *
 * Same trick the Epitech reference uses with `site-header__inner-page` but
 * scoped to our body class so it doesn't bleed into homepage chrome.
 * ─────────────────────────────────────────────────────────────────────────── */
body.metiers-template .site-header > .site-header-main,
body.metiers-template .site-header .site-header-campus-container {
  padding-left: var(--wp--style--root--padding-left, clamp(20px, 5vw, 80px));
  padding-right: var(--wp--style--root--padding-right, clamp(20px, 5vw, 80px));
  box-sizing: border-box;
}

/* The site-header campus selector wraps the EPITECH logo + langs + campus
 * picker. On metiers pages, align its inner with the entry-content gutter. */
body.metiers-template .wp-block-acf-site-header {
  max-width: none;
}

/* ─── Métiers hero `page-interne` left-column alignment ───────────────────────
 * Companion to the .site-header padding fix above. The hero variant uses
 * `wp-block-columns.is-style-hero-page-interne.alignfull` which spans
 * edge-to-edge, so the inner group's `has-global-padding` only adds the small
 * root-padding (clamp 20-80px) on either side. On a wide viewport (> chrome
 * content-size), the header logo sits at `(100vw - 1120) / 2 + root-padding`
 * from the left while the hero <h1> sits at just `root-padding` — visibly
 * misaligned (200-300px drift at 1920px).
 *
 * Align the first column's left padding to match the centered chrome by
 * adding the half-margin when the viewport exceeds the global content-size.
 * The right column (image) keeps its full-bleed reach.
 * ─────────────────────────────────────────────────────────────────────────── */
body.metiers-template
  .wp-block-columns.is-style-hero-page-interne
  > .wp-block-column:first-child {
  /* Match the header logo's left edge: the chrome wraps its logo in a
   * `.site-header-main-container { max-width: 1120px; margin: 0 auto }`
   * inside an outer `.has-global-padding`. The logo's left therefore lands
   * at `(viewport - 1120) / 2` once the viewport exceeds the content size.
   * Below 1120px viewport, the chrome's outer padding (root-padding-left,
   * default `clamp(20, 5vw, 80)`) takes over — we mirror that floor here.
   * Verified empirically at 1440px viewport: logo at 152.5px, h1 at ~160px
   * (scrollbar accounts for the 8px residual). */
  padding-left: max(
    var(--wp--style--root--padding-left, clamp(20px, 5vw, 80px)),
    calc((100vw - var(--wp--style--global--content-size, 1120px)) / 2)
  );
  box-sizing: border-box;
}

/* Inner group inside the first column already has `has-global-padding`; with
 * the outer column now padded to align with the chrome, the inner padding
 * would double up. Zero it out specifically inside the hero. */
body.metiers-template
  .wp-block-columns.is-style-hero-page-interne
  > .wp-block-column:first-child
  > .wp-block-group.has-global-padding {
  padding-left: 0;
  padding-right: 0;
}

/* ─── Hero sizing on metier pages ──────────────────────────────────────────
 * The theme stylesheet (`themes/epitech-refonte/dist/css/style.css:8694+`)
 * sets `min-height: 720px / 880px / 1080px` on `.is-style-hero-page-interne`
 * at desktop breakpoints, assuming a tall hero photo. Two problems on our
 * metier pages:
 *   1. Without an image, that floor leaves ~500px of empty space below the
 *      intro paragraph.
 *   2. WITH a 16:9 image, the text column (title + 1-2 short paragraphs)
 *      doesn't fill 720px either — col0 ends up with ~400px empty at the
 *      bottom while col1 (image) is tall.
 *
 * Fix: scope to body.metiers-template, vertical-center the columns so the
 * text sits middle-of-hero (less obvious gap), and cap the min-height at
 * 480px (still tall enough for a confident hero, short enough that real
 * content fills it). `:has()` is supported across all modern browsers
 * (Chrome 105+, Safari 15.4+, Firefox 121+).
 * ─────────────────────────────────────────────────────────────────────── */
body.metiers-template .wp-block-columns.is-style-hero-page-interne {
  min-height: 480px;
  align-items: center;
}
body.metiers-template
  .wp-block-columns.is-style-hero-page-interne:not(:has(.wp-block-image img, figure img)) {
  min-height: 0;
  align-items: stretch;
}

/* Tighten the gap between the hero and the next section. The theme leaves
 * ~86px of margin-top on `.wp-block-group.alignfull.is-style-section` plus
 * the hero's own 54px margin-bottom — total visible gap ~110-120px on a
 * desktop viewport. Bring the hero margin to 0 and reduce the next section's
 * top margin so transitions feel intentional, not orphaned. */
body.metiers-template .wp-block-columns.is-style-hero-page-interne {
  margin-bottom: 0;
}
body.metiers-template
  .wp-block-columns.is-style-hero-page-interne
  + .wp-block-group.is-style-section,
body.metiers-template
  .wp-block-columns.is-style-hero-page-interne
  + * {
  margin-top: 32px;
}

/* ─── Métiers info grid: perspectives / salaire / secteurs as 3 cards ─────
 * Custom HTML structure emitted by renderMetiersInfoGrid() in edms/server.ts:
 *
 *   <section class="metiers-info-section">
 *     <div class="metiers-info-grid">
 *       <article class="mig-card mig-card--{key}">
 *         <figure class="mig-card__icon"><img …/></figure>   ← floats right
 *         <h2 class="mig-card__title">…</h2>
 *         <div class="mig-card__body">…</div>
 *       </article> ×3
 *     </div>
 *   </section>
 *
 * Reference: epitech.eu/metiers/devops + user target screenshot
 * (Screenshot from 2026-05-16 22-49-12.png).
 *
 * Knobs (override per-page or per-brand at body / :root level):
 *   --epitech-metiers-section-bg   warm cream surface (also paints cards)
 *   --epitech-metiers-card-h       capped card height (overflow hidden)
 *   --epitech-metiers-card-pad     internal padding
 *   --epitech-metiers-title-size   uppercase title clamp
 *   --epitech-metiers-icon-size    floated corner icon clamp
 * ─────────────────────────────────────────────────────────────────────── */
body.metiers-template .metiers-info-section {
  background: var(--epitech-metiers-section-bg, transparent);
  padding-block: clamp(32px, 4vh, 56px);
}

body.metiers-template .metiers-info-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--epitech-metiers-gap, clamp(4px, 0.4vw, 6px));
  align-items: stretch;
  max-width: var(--wp--style--global--content-size, 1180px);
  margin: 0 auto;
  padding: 0 var(--wp--style--root--padding-left, clamp(20px, 5vw, 64px));
  box-sizing: border-box;
}

/* Outlined card matching the live Epitech reference markup
 * (has-border-color has-primary-border-color, border-width:2px).
 * Background inherits from the parent so the card blends with the page;
 * the 2 px primary border is what separates the columns.
 *
 * Card height is content-driven. The grid's `align-items: stretch`
 * (see .metiers-info-grid above) aligns the three cards in a row to the
 * tallest card's content per métier page — no globally-fixed height,
 * no artificial empty space. */
body.metiers-template .mig-card {
  position: relative;
  display: block; /* allow figure.float */
  background: var(--epitech-metiers-card-bg, transparent);
  border: var(--epitech-metiers-card-border-width, 2px) solid
          var(--epitech-metiers-card-border-color,
                var(--wp--preset--color--primary, #013afb));
  border-radius: 0;
  padding: var(--epitech-metiers-card-pad, clamp(14px, 1.2vw, 22px));
  box-sizing: border-box;
}
body.metiers-template .mig-card::after {
  content: "";
  display: block;
  clear: both;
}

/* No fade gradient: the user wants actual content trimming, not
 * visual hiding. Content trimming happens server-side via
 * summarizeForCard() in edms/server.ts (see METIERS_INFO_CARD_MAX_CHARS),
 * which caps very long métier bodies at a reasonable budget. With the
 * card height now content-driven, the trim is purely a content budget
 * — no overflow clipping. */

body.metiers-template .mig-card__icon {
  float: right;
  margin: 0 0 6px 14px;
  width: var(--epitech-metiers-icon-size, clamp(36px, 3vw, 48px));
}
body.metiers-template .mig-card__icon img {
  width: 100%;
  height: auto;
  display: block;
}

body.metiers-template .mig-card__title {
  margin: 0 0 14px;
  font-family: "Anton", system-ui, sans-serif;
  font-weight: 700;
  font-size: var(--epitech-metiers-title-size, clamp(24px, 2.2vw, 30px));
  line-height: 1.18;
  letter-spacing: 0;
  color: #181818;
  text-transform: uppercase;
}

body.metiers-template .mig-card__body p {
  margin: 0 0 6px;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-size: 13.5px;
  font-weight: 400;
  line-height: 1.45;
  color: #1a1a1a;
  text-align: justify;
  text-justify: inter-word;
  hyphens: auto;
}
body.metiers-template .mig-card__body p:last-child {
  margin-bottom: 0;
}

body.metiers-template .mig-card__body ul {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}
body.metiers-template .mig-card__body li {
  position: relative;
  padding: 0 0 3px 12px;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  color: #1a1a1a;
}
body.metiers-template .mig-card__body li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  width: 3px;
  height: 3px;
  background: #1a1a1a;
}

/* Responsive: 2 cols at tablet, 1 col at mobile.
 * On mobile we drop the icon float so the icon stacks above the heading.
 * Card heights are content-driven at every breakpoint (handled by the base
 * rule above). */
@media (max-width: 980px) {
  body.metiers-template .metiers-info-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 640px) {
  body.metiers-template .metiers-info-grid {
    grid-template-columns: 1fr;
    gap: 8px;
  }
  body.metiers-template .mig-card__icon {
    float: none;
    margin: 0 0 10px;
  }
}

/* ─── "Le métier de X" — revolving planet visual ─────────────────────────
 * Replaces the static image in the second section of every metier page
 * with the Epitech-planete asset (wireframe globe + orbital rings +
 * 3 satellite capsules), and animates it with CSS @keyframes so it
 * subtly rotates and bobs — mimicking the GSAP-driven `gsap-image-3d`
 * treatment on epitech.eu/metiers/devops without a JS dependency.
 *
 * Scope: `body.metiers-template figure.wp-block-image img[src*="Epitech-planete"]`
 *        — narrow enough to leave every other image on the page alone,
 *        wide enough to catch the asset wherever the React block renders.
 *
 * Knobs:
 *   --epitech-planete-rotate-duration  default 90s
 *   --epitech-planete-float-duration   default 6s
 *   --epitech-planete-float-amp        default 10px
 *   --epitech-planete-halo-color       default rgba(1,58,251,0.06) (Epitech blue at 6% alpha)
 * ───────────────────────────────────────────────────────────────────────── */
body.metiers-template figure.wp-block-image:has(img[src*="Epitech-planete"]) {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding-block: clamp(24px, 4vh, 56px);
  isolation: isolate;
}
/* Soft halo behind the planet — gives the wireframe room to breathe and
 * adds the "immersive background" feel without competing with the line art. */
body.metiers-template figure.wp-block-image:has(img[src*="Epitech-planete"])::before {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 90%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    circle at 50% 50%,
    var(--epitech-planete-halo-color, rgba(1, 58, 251, 0.08)) 0%,
    transparent 70%);
  z-index: -1;
  pointer-events: none;
}

/* Split the two transforms across elements so they compose cleanly:
 *   - the <figure> bobs vertically (translateY)
 *   - the <img> rotates around its center
 * If both ran on the same element, the later `animation` would override
 * the earlier `transform` on every frame and they'd fight. */
body.metiers-template figure.wp-block-image:has(img[src*="Epitech-planete"]) {
  animation: metiers-planete-float var(--epitech-planete-float-duration, 6s)
             ease-in-out infinite;
}
body.metiers-template figure.wp-block-image img[src*="Epitech-planete"] {
  width: clamp(260px, 30vw, 420px);
  height: auto;
  aspect-ratio: auto !important;
  object-fit: contain !important;
  display: block;
  will-change: transform;
  animation: metiers-planete-spin var(--epitech-planete-rotate-duration, 90s)
             linear infinite;
}

@keyframes metiers-planete-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
@keyframes metiers-planete-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(calc(-1 * var(--epitech-planete-float-amp, 10px))); }
}

/* Accessibility: honor reduced-motion preferences. */
@media (prefers-reduced-motion: reduce) {
  body.metiers-template figure.wp-block-image:has(img[src*="Epitech-planete"]),
  body.metiers-template figure.wp-block-image img[src*="Epitech-planete"] {
    animation: none !important;
  }
}

/* ─── Compétences cover_text: clean blue + bullseye SVG illustration ──────
 * Reference: Screenshot from 2026-05-16 21-34-31.png — the competences
 * section on epitech.eu shows a target/bullseye SVG in the right column
 * over a clean blue background. The CoverTextBlock SSR otherwise renders
 * a dark code photo overlaid on blue (from image_competences). Strip the
 * photo overlay + the top-right thumbnail and inject a target SVG via a
 * `::after` pseudo-element on the right.
 *
 * Selector uses `:is()` so both the public site and the admin canvas
 * match: public roots at `body.metiers-template .entry-content`; admin
 * roots at `.app-portal-preview.preview-metiers-template` (no entry-
 * content wrapper, plus admin chrome divs sit between the wrapper and
 * .wp-block-cover, so descendant ` ` is required — not direct child).
 * The secteurs cover_text used to live elsewhere; after the metiers-info-
 * grid refactor it's now a `<article class="mig-card mig-card--secteurs">`
 * with no `.wp-block-cover` at all, so the descendant selector can't
 * accidentally style it.
 * ─────────────────────────────────────────────────────────────────────── */
:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond
  .wp-block-cover__background,
:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond
  .wp-block-cover__image-background {
  display: none !important;
}

/* Hide every direct-child <span> that the CoverTextBlock SSR emits as
 * an absolute overlay. The first is the dimmed background-image span;
 * the second is a small <img> badge thumbnailing the same asset in the
 * top-right corner. Reference layout shows only the cover bg + bullseye,
 * so both go. */
:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond
  > span {
  display: none !important;
}

:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond {
  background-color: var(--wp--preset--color--primary, #013afb) !important;
  position: relative;
  overflow: hidden;
  /* Default min-height tuned so the 360px-wide bullseye (aspect 345/358 →
   * ~373px tall) clears top/bottom with breathing room AND the 6-bullet
   * content column always fits without overflow. Overridable per-record
   * via the section's styleOverrides setting `--epitech-competences-min-height`
   * (admin can dial it up/down without code edits — knob, not hardcode). */
  min-height: var(--epitech-competences-min-height, 440px) !important;
}

/* Heading: match live Epitech reference (heading-l preset at this width).
 * Reference computed: 36.44 px / weight 400 / line-height 1.25 / white / left.
 * Our default was 57.78 px because the SSR puts `has-heading-l-font-size`
 * on the h2 — a WP preset that carries `!important`. We override with
 * the same `!important` and a selector specific enough to outrank it. */
:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond
  h2.wp-block-heading,
:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond
  h2.has-heading-l-font-size {
  font-family: "Anton", Impact, system-ui, sans-serif;
  font-size: clamp(28px, 2.85vw, 38px) !important;
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: 0;
  color: #fff;
  text-align: left;
  margin: 0 0 28px;
  text-transform: uppercase;
}

/* Constrain inner content to the left half so the bullseye has room.
 * Vertical padding gives the title + bullets breathing space against the
 * section frame — tuned so 6 bullets + 2-line title sit comfortably with
 * ~48px clearance top and bottom at 440px section height. Admins can
 * override the vertical padding per-record via the section's
 * styleOverrides setting `--epitech-competences-padding-y`. */
:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond
  > .wp-block-cover__inner-container {
  max-width: min(640px, 60%);
  margin-left: max(
    var(--wp--style--root--padding-left, clamp(20px, 5vw, 80px)),
    calc((100vw - var(--wp--style--global--content-size, 1120px)) / 2)
  );
  padding-top: var(--epitech-competences-padding-y, 48px);
  padding-bottom: var(--epitech-competences-padding-y, 48px);
}

/* The bullseye decoration — mirrored Epitech asset (cible-1.png, 345×358).
 * Positioned on the right column at the same proportional offset as the
 * live reference. */
:is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
  .wp-block-cover.is-style-image-devant-fond::after {
  content: "";
  position: absolute;
  right: max(
    var(--wp--style--root--padding-left, clamp(20px, 5vw, 80px)),
    calc((100vw - var(--wp--style--global--content-size, 1120px)) / 2)
  );
  top: 50%;
  transform: translateY(-50%);
  width: clamp(220px, 27vw, 360px);
  aspect-ratio: 345 / 358;
  pointer-events: none;
  background-image: url("/wp-content/uploads/2023/11/cible-1.png");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  z-index: 1;
}

/* Hide the decoration on narrow viewports so the bullets stay readable */
@media (max-width: 768px) {
  :is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
    .wp-block-cover.is-style-image-devant-fond
    > .wp-block-cover__inner-container {
    max-width: 100%;
    margin: 0 auto;
  }
  :is(body.metiers-template .entry-content, .app-portal-preview.preview-metiers-template)
    .wp-block-cover.is-style-image-devant-fond::after {
    display: none;
  }
}

/* ───────────────────────────────────────────────────────────────────────────
 * EDMS WYSIWYG — admin-canvas mirror of body.metiers-template metiers-info-grid
 *
 * The public site applies `body.metiers-template` (set by
 * metiersSchema.portal.bodyClass) which scopes every rule above. In the admin
 * canvas, `AdminPortalPreview` also adds those tokens to <body>, but if the
 * effect ever races a route change (we logged it as `bodyClass: ""` during the
 * 2026-05-17 audit), the cards collapsed to a vertical stack. This @layer
 * block re-asserts the trio's layout fundamentals scoped to the preview
 * wrapper (`.app-portal-preview.preview-metiers-template`) so the editor stays
 * WYSIWYG regardless of whether the body class made it on time.
 *
 * Only the LAYOUT essentials are mirrored here (grid, gap, card height,
 * float, sizing) — the typography / color rules are unchanged from the body-
 * class block above and apply once <body> picks up the tokens. The mirror
 * uses a higher-specificity wrapper selector so it never fights the cascade
 * for non-metiers pages.
 * ─────────────────────────────────────────────────────────────────────────── */
.app-portal-preview.preview-metiers-template .metiers-info-section {
  padding-block: clamp(32px, 4vh, 56px);
}
.app-portal-preview.preview-metiers-template .metiers-info-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--epitech-metiers-gap, clamp(4px, 0.4vw, 6px));
  align-items: stretch;
  max-width: var(--wp--style--global--content-size, 1180px);
  margin: 0 auto;
  padding: 0 var(--wp--style--root--padding-left, clamp(20px, 5vw, 64px));
  box-sizing: border-box;
}
.app-portal-preview.preview-metiers-template .mig-card {
  position: relative;
  display: block;
  background: var(--epitech-metiers-card-bg, transparent);
  border: var(--epitech-metiers-card-border-width, 2px) solid
          var(--epitech-metiers-card-border-color,
                var(--wp--preset--color--primary, #013afb));
  border-radius: 0;
  padding: var(--epitech-metiers-card-pad, clamp(14px, 1.2vw, 22px));
  box-sizing: border-box;
}
.app-portal-preview.preview-metiers-template .mig-card::after {
  content: "";
  display: block;
  clear: both;
}
.app-portal-preview.preview-metiers-template .mig-card__icon {
  float: right;
  margin: 0 0 6px 14px;
  width: var(--epitech-metiers-icon-size, clamp(36px, 3vw, 48px));
}
.app-portal-preview.preview-metiers-template .mig-card__icon img {
  width: 100%;
  height: auto;
  display: block;
}
.app-portal-preview.preview-metiers-template .mig-card__title {
  margin: 0 0 14px;
  font-family: "Anton", system-ui, sans-serif;
  font-weight: 700;
  font-size: var(--epitech-metiers-title-size, clamp(24px, 2.2vw, 30px));
  line-height: 1.18;
  letter-spacing: 0;
  color: #181818;
  text-transform: uppercase;
}
.app-portal-preview.preview-metiers-template .mig-card__body p {
  margin: 0 0 6px;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-size: 13.5px;
  font-weight: 400;
  line-height: 1.45;
  color: #1a1a1a;
  text-align: justify;
  hyphens: auto;
}
.app-portal-preview.preview-metiers-template .mig-card__body p:last-child {
  margin-bottom: 0;
}
.app-portal-preview.preview-metiers-template .mig-card__body ul {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}
.app-portal-preview.preview-metiers-template .mig-card__body li {
  position: relative;
  padding: 0 0 3px 12px;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  color: #1a1a1a;
}
.app-portal-preview.preview-metiers-template .mig-card__body li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  width: 3px;
  height: 3px;
  background: #1a1a1a;
}

/* Admin-only chrome around the locked trio. Single insert gutter above; no
 * gutters between cells (the template fixes the order — see
 * AdminPortalPreview's MetiersInfoGrid branch). The wrap div is the click
 * target that walks data-section-index → onSelectSection(cellIdx). */
.app-preview__metiers-grid-wrap {
  position: relative;
}
.app-preview__metiers-grid-wrap .mig-card {
  cursor: pointer;
  transition: outline-color 120ms ease;
  outline: 1px dashed transparent;
  outline-offset: 2px;
}
.app-preview__metiers-grid-wrap .mig-card:hover {
  outline-color: rgba(1, 58, 251, 0.4);
}

/* ─────────────────────────────────────────────────────────────────────────
 * VideoGridBlock — `video_grid` section type
 * Adapted from the inline <style> previously injected by
 * scripts/build-faq-record.mjs (now removed). Single source of truth so the
 * FAQ "Découvrir Epitech en vidéo" tile renders identically on the public
 * site and inside the admin canvas.
 * ────────────────────────────────────────────────────────────────────── */
.edms-video-grid > h2 {
  margin: 0 0 16px;
}
.edms-video-grid__grid {
  display: grid;
  gap: 20px;
  margin: 16px -40px 24px;
  box-sizing: border-box;
}
.edms-video-grid__grid[data-columns="1"] { grid-template-columns: 1fr; }
.edms-video-grid__grid[data-columns="2"] { grid-template-columns: 1fr 1fr; }
.edms-video-grid__grid[data-columns="3"] { grid-template-columns: 1fr 1fr 1fr; }
.edms-video-grid__tile video,
.edms-video-grid__tile iframe {
  width: 100%;
  aspect-ratio: 16/9;
  max-height: 320px;
  object-fit: cover;
  border-radius: 4px;
  background: #0a1033;
  display: block;
  border: 0;
}
.edms-video-grid__placeholder {
  width: 100%;
  aspect-ratio: 16/9;
  max-height: 320px;
  background: #0a1033;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.95rem;
  text-align: center;
  padding: 16px;
}
.is-layout-constrained > .edms-video-grid__grid {
  margin-left: -40px !important;
  margin-right: -40px !important;
  max-width: none !important;
  width: auto !important;
}
@media (max-width: 720px) {
  .edms-video-grid__grid {
    margin: 12px 0 16px;
    gap: 12px;
  }
  .edms-video-grid__grid[data-columns="2"],
  .edms-video-grid__grid[data-columns="3"] {
    grid-template-columns: 1fr;
  }
  .edms-video-grid__tile video,
  .edms-video-grid__tile iframe,
  .edms-video-grid__placeholder {
    max-height: 280px;
  }
  .is-layout-constrained > .edms-video-grid__grid {
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
}

/* iframe_embed section — third-party form / calendar / map embed. Heights are
 * exposed as CSS custom properties by IframeEmbedBlock.tsx (one per viewport),
 * the media queries below pick the right variable. */
.edms-iframe-embed {
  display: block;
  width: 100%;
  padding: 1.5rem 0;
}
.edms-iframe-embed iframe {
  display: block;
  width: 100%;
  border: 0;
  height: var(--edms-iframe-h-desktop, 900px);
}
@media (max-width: 1024px) {
  .edms-iframe-embed iframe {
    height: var(--edms-iframe-h-tablet, 800px);
  }
}
@media (max-width: 640px) {
  .edms-iframe-embed iframe {
    height: var(--edms-iframe-h-mobile, 1400px);
  }
}
.edms-iframe-embed--empty {
  min-height: 120px;
}
.edms-iframe-embed-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 120px;
  padding: 1rem;
  color: #666;
  font-style: italic;
  background: repeating-linear-gradient(
    45deg,
    #f4f4f4,
    #f4f4f4 10px,
    #ececec 10px,
    #ececec 20px
  );
  border: 1px dashed #c0c0c0;
}

/* ── Heading visual size (data-heading-size) ───────────────────────────
   The RichTextEditor size picker writes `data-heading-size` to pin how big a
   heading LOOKS, independent of its semantic tag. In this theme headings differ
   only by font-size (weight/line-height are identical across h1–h6), so pinning
   the size lets an editor change the tag (h1↔h2↔… for SEO) with ZERO visual
   change. This file is loaded by both the public site (cssUrls) and the admin
   preview iframe (portal.editorCssUrls), so WYSIWYG parity holds. The values
   reuse the theme's own size presets so they match exactly.

   Specificity matters: the theme pins h3/h4 with `h3.wp-block-heading,
   h4.wp-block-heading { … !important }` at specificity (0,1,1). A bare
   `[data-heading-size]` is only (0,1,0), so despite `!important` + late source
   order it LOSES to the tag rule and the freeze silently fails for h3/h4
   (source order only breaks ties at equal specificity). Repeating the attribute
   lifts these to (0,2,0) — above the tag rule — without depending on any class
   or ancestor, so the pin wins for every level in both public and editor DOM. */
[data-heading-size='xl'][data-heading-size] {
  font-size: var(--wp--preset--font-size--heading-xl) !important;
}
[data-heading-size='l'][data-heading-size] {
  font-size: var(--wp--preset--font-size--heading-l) !important;
}
[data-heading-size='m'][data-heading-size] {
  font-size: var(--wp--preset--font-size--heading-m) !important;
}
[data-heading-size='s'][data-heading-size] {
  font-size: var(--wp--preset--font-size--heading-s) !important;
}
[data-heading-size='xs'][data-heading-size] {
  font-size: var(--wp--preset--font-size--heading-xs) !important;
}
[data-heading-size='xxs'][data-heading-size] {
  font-size: var(--wp--preset--font-size--heading-xxs) !important;
}
