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

