/*
Theme Name: SAFIR
Template: twentytwentyfour
Theme URI: https://safir-v2.local
Author: SAFIR
Description: Thème enfant de Twenty Twenty-Four : palette et typographie de la plateforme SAFIR (theme.json), polices auto-hébergées, styles du parcours de formation.
Version: 1.0.0
Requires at least: 6.5
Requires PHP: 8.1
Text Domain: safir
*/

/* Avantages / Limitations lists on the Méthodes pages (RBS, ERDA, NRA/NRP,
   MEIS) — replaces the ✅/⚠️ emoji bullets the client didn't like with a
   plain list plus a coloured left border: primary for advantages, accent
   for limitations. The heading above each list ("Avantages"/"Limitations")
   already carries the meaning, so no icon is needed. */
.safir-pros-list,
.safir-cons-list {
    list-style: none;
    margin: 0;
    padding: 0.15rem 0 0.15rem 1.1rem;
    border-left: 3px solid var(--wp--preset--color--primary);
}

.safir-cons-list {
    border-left-color: var(--wp--preset--color--accent);
}

.safir-pros-list li,
.safir-cons-list li {
    margin: 0 0 0.5rem;
}

.safir-pros-list li:last-child,
.safir-cons-list li:last-child {
    margin-bottom: 0;
}

/* Informational callouts ("en cours de montage", "photos à venir"...) —
   same tinted background as before, just without a leading emoji; the
   bold lead-in word does the same job. */
.safir-callout {
    background-color: #fbf2e6 !important;
    border-left: 3px solid var(--wp--preset--color--accent);
    padding: 0.75rem 1rem !important;
}

/* Small pill badge sitting next to an H2, e.g. "Chambres d'analyse" on the
   Équipements page — reuses the site's existing category-badge pill
   language (primary-tint fill, primary text, pill radius) so it reads as
   "the site's badge style" rather than a one-off, and gives that section a
   visual identity distinct from the "Accélérateur" H2 above it without
   needing an icon. */
.safir-heading-badge {
    display: inline-block;
    vertical-align: middle;
    background: var(--wp--preset--color--primary-tint);
    color: var(--wp--preset--color--primary);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 0.2rem 0.7rem;
    border-radius: 999px;
    margin-left: 0.75rem;
}

/* "Back to top" button — injected by assets/js/back-to-top.js, hidden by
   default and only shown past SHOW_AFTER_PX so it doesn't sit uselessly
   over the hero on short pages. Fixed bottom-right, same teal/amber
   language as the rest of the UI so it reads as part of the site rather
   than a bolted-on widget. */
.safir-back-to-top {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 100;
    width: 46px;
    height: 46px;
    border: none;
    border-radius: 50%;
    background: var(--wp--preset--color--primary);
    color: #fafaf8;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
    opacity: 0;
    transform: translateY(12px);
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease, background-color 0.15s ease;
}

.safir-back-to-top.is-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.safir-back-to-top:hover,
.safir-back-to-top:focus-visible {
    background: var(--wp--preset--color--accent);
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .safir-back-to-top {
        transition: none;
    }
}

@media (max-width: 600px) {
    .safir-back-to-top {
        right: 1rem;
        bottom: 1rem;
        width: 42px;
        height: 42px;
    }
}

/* "Scroll to bottom" button -- same fixed slot and look as "back to top"
   above, since the two are mutually exclusive (JS only shows one at a
   time depending on scroll position) and sharing the corner reads as one
   consistent control rather than two competing ones. */
.safir-scroll-to-bottom {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 100;
    width: 46px;
    height: 46px;
    border: none;
    border-radius: 50%;
    background: var(--wp--preset--color--primary);
    color: #fafaf8;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
    opacity: 0;
    transform: translateY(-12px);
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease, background-color 0.15s ease;
}

.safir-scroll-to-bottom.is-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.safir-scroll-to-bottom:hover,
.safir-scroll-to-bottom:focus-visible {
    background: var(--wp--preset--color--accent);
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .safir-scroll-to-bottom {
        transition: none;
    }
}

@media (max-width: 600px) {
    .safir-scroll-to-bottom {
        right: 1rem;
        bottom: 1rem;
        width: 42px;
        height: 42px;
    }
}

/* Rank Math breadcrumb shortcode, added to the generic page template so
   nested pages (Méthodes > RBS/ERDA/NRA/MEIS) show their place in the
   hierarchy — unstyled by default (plain black text, underlined links). */
.rank-math-breadcrumb {
    font-size: 0.85rem;
    color: var(--wp--preset--color--foreground);
    opacity: 0.7;
    margin: 0 0 0.5rem;
}

.rank-math-breadcrumb a {
    color: var(--wp--preset--color--primary);
    text-decoration: none;
}

.rank-math-breadcrumb a:hover,
.rank-math-breadcrumb a:focus-visible {
    text-decoration: underline;
}

.rank-math-breadcrumb .separator {
    margin: 0 0.4em;
    opacity: 0.6;
}

.rank-math-breadcrumb .last {
    font-weight: 600;
}

/* Quote block: the "signature" violet accent (theme.json's ion-glow color,
   drawn from the facility's own ion-source photos) as a border rather than
   a fill — set here instead of theme.json's styles.blocks.core/quote,
   which silently produced no CSS at all: border customization on a block
   only generates output if that block declares support for it, and core/
   quote's own block.json doesn't. */
.wp-block-quote {
    font-family: var(--wp--preset--font-family--heading);
    font-style: italic;
    border-left: 3px solid var(--wp--preset--color--ion-glow);
    padding-left: 1.25rem;
    margin-left: 0;
}

/* Same accent, second and (deliberately) only other use: a small underline
   marking the homepage's "Publications récentes" section, so the new
   color has more than one token appearance on the site. */
.safir-recent-publications h2::after {
    content: "";
    display: block;
    width: 96px;
    height: 2px;
    background: var(--wp--preset--color--ion-glow);
    margin-top: 0.6rem;
}

/* Title/credit line under any native image, video or embed — the site-wide
   convention: WordPress's own "Caption" field on an attachment is where a
   photo's title and/or credit ("Photo : ...") belongs; this just makes that
   field look considered instead of like a leftover default. */
.wp-block-image figcaption,
.wp-block-video figcaption,
.wp-block-embed figcaption {
    font-family: var(--wp--preset--font-family--body);
    font-size: 0.85rem;
    color: var(--wp--preset--color--contrast);
    opacity: 0.7;
    text-align: center;
    margin-top: 0.5rem;
    line-height: 1.4;
}

/*
 * Header chrome: INSP (left) / SAFIR brand (true center) / CNRS+Sorbonne+
 * EMIR&A (right), with the nav on its own row below. Lives here rather
 * than in the safir-training plugin's stylesheet because it's theme chrome
 * (site identity/navigation), not plugin-rendered UI (dashboard/quiz).
 */

/* Header title block next to the logo mark: the acronym spelled out in
   full, since the logo itself already spells out "SAFIR" — repeating the
   short name in text right next to it read as redundant. Slightly reduced
   opacity keeps it reading as a caption to the logo rather than a second
   competing headline. Capped width so the long name wraps to two lines
   instead of stretching the header row on narrower desktop widths. */
.safir-header-title p {
    margin: 0 !important;
    opacity: 0.85;
    max-width: 220px;
}

/* True centering for the brand block regardless of how wide the left/right
   logo groups are — plain flex space-between only looks centered when both
   sides are equal width, which INSP-alone vs CNRS+Sorbonne+EMIR&A isn't.
   Falls back to normal flow (and normal wrapping) below the breakpoint
   where the three groups start stacking, so it can never overlap on
   narrow screens. */
@media (min-width: 783px) {
    .safir-header-row {
        position: relative;
        min-height: 50px;
    }

    .safir-header-brand {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }
}

/* Partner logos (INSP / SAFIR / CNRS / Sorbonne / EMIR&A) are fixed-pixel
   images (40-141px wide, set via HTML width/height attributes) with no
   responsive sizing at all -- harmless on desktop where they're a small
   fraction of a 1280px+ header, but on a narrow PORTRAIT phone
   (~360-430px wide) the exact same fixed size is a huge fraction of the
   screen: each one wraps to its own full-width row (safir-header-row is
   flex-wrap), stacking into a header block that ate most of the visible
   viewport before any page content appeared. Landscape phones (~650px+)
   don't hit this — confirmed live at 390x844. Scoped tight to real phone
   portrait widths so tablets and landscape phones, which the user
   confirmed already look fine, are untouched. */
@media (max-width: 600px) {
    .safir-partner-logo--header img {
        height: 24px !important;
        width: auto !important;
    }

    .safir-header-brand img {
        height: 32px !important;
        width: auto !important;
    }

    .safir-header-row {
        row-gap: 0.5rem;
    }
}

/* The Navigation block's link color is normally set globally in
   theme.json (teal, for use on the light page background) — on this
   header's teal bar that would be invisible, so force it back to white
   here specifically. !important is deliberate: the block editor emits its
   own color rule for wp:navigation with ordinary specificity, and source
   order between that and this stylesheet isn't something to rely on. */
.safir-header-nav .wp-block-navigation-item__label,
.safir-header-nav a {
    color: #fafaf8 !important;
}

/* Same problem, missed by the rule above because it only covers links: the
   mobile hamburger open/close buttons and the submenu-toggle chevron are
   <button> elements whose SVG icon uses fill:currentColor, so they inherit
   the theme's default dark text color too -- on the header's dark bar that
   rendered as a near-invisible dark-grey-on-navy icon (reported by a real
   phone tester; confirmed via computed style: icon fill exactly matched the
   default text color, not the white used everywhere else in this header). */
.safir-header-nav button {
    color: #fafaf8 !important;
}

/* The open-menu button's own tap target was a bare 24x24px icon with no
   padding -- too small for a comfortable touch target (WCAG 2.5.5 wants at
   least 44x44px). Pad it out without growing the visible icon itself. */
.safir-header-nav .wp-block-navigation__responsive-container-open {
    padding: 10px !important;
    margin: -10px !important;
}

/* Mobile Chrome/Android paints a default translucent grey/white flash
   ("tap highlight") over the whole tapped link on every touch, since none
   of the header links reset it — visible on a solid teal bar as a stray
   pale rectangle that outlives the tap for a moment. Purely cosmetic,
   :active/:focus styling above already gives real feedback. */
.safir-header-nav a,
.safir-header-nav button {
    -webkit-tap-highlight-color: transparent;
}

/* The actual "white rectangle on click" bug: core's global styles draw a
   focus ring on every link (:where(.wp-site-blocks :focus) { outline-width:
   2px; outline-style: solid }) but deliberately leave outline-color unset,
   so it resolves to currentColor — which here is the #fafaf8 we force above
   for legibility on the teal bar. Clicking (not just tabbing to) a link
   focuses it in most browsers, so every menu item flashed a solid
   near-white box on click, and the mobile overlay auto-focuses its first
   link on open, showing the same box immediately. No outline box at all —
   the amber color/weight switch added to the :hover rule above doubles as
   the focus indicator, so keyboard users still get a visible cue without a
   rectangle appearing on ordinary clicks. */
.safir-header-nav a:focus,
.safir-header-nav button:focus {
    outline: none !important;
}

/* The plain accent amber (#BA7517) reads fine on the light background it
   was designed for, but barely stands out against the teal header — a
   hue-only shift between two mid-dark tones is hard to spot at a glance.
   A brighter, more luminant gold plus bold weight makes the hover state
   actually noticeable here. */
.safir-header-nav .wp-block-navigation-item__label:hover,
.safir-header-nav a:focus,
.safir-header-nav a:focus .wp-block-navigation-item__label {
    color: #F5B342 !important;
    font-weight: 700;
}

/* Real bug, not a hypothetical: core's block-library CSS gives a submenu
   dropdown a WHITE background whenever the parent nav block lacks the
   has-background class (wp-includes/css/dist/block-library/style.css,
   ".wp-block-navigation:not(.has-background) .wp-block-navigation__
   submenu-container"). This nav only sets a text color, not a background,
   so it never gets that class — the result was white submenu text (from
   the rule above) on that default white background, i.e. invisible on
   hover. Give the dropdown an explicit background instead of relying on
   the class core expects. */
.safir-header-nav .wp-block-navigation__submenu-container {
    background-color: var(--wp--preset--color--primary) !important;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}

/* Same bug again, third spot: inside the open mobile overlay specifically,
   core has a MORE specific rule than the one above
   (".is-menu-open:where(...) .wp-block-navigation-item .submenu-container
   { background: transparent !important }") that wins on specificity alone
   regardless of source order. Net effect: opening "Méthodes" on mobile left
   the submenu-container background-less while still carrying the box-shadow
   from the rule above, rendering as a floating pale smudge with no fill
   behind it. Out-specify core's selector to restore the fill. */
.safir-header-nav .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item .wp-block-navigation__submenu-container {
    background-color: var(--wp--preset--color--primary) !important;
}

.safir-header-nav .wp-block-navigation__submenu-container .wp-block-navigation-item__label,
.safir-header-nav .wp-block-navigation__submenu-container a {
    color: #fafaf8 !important;
}

/* Same bug, mobile version: the hamburger overlay defaults to a WHITE
   background whenever the nav block lacks has-background (same core rule,
   block-library/style.css ~line 3022) — combined with our own forced
   near-white link color above, the whole mobile menu was rendering
   white-on-white, effectively invisible. */
.safir-header-nav .wp-block-navigation__responsive-container.is-menu-open,
.safir-header-nav .wp-block-navigation__responsive-dialog {
    background-color: var(--wp--preset--color--primary) !important;
}

/* page.html was widened to 1600px (matching the wideSize already used by
   Équipe/Actualités' own align:wide blocks) so that ordinary pages stop
   looking cramped next to them. That's the right call for pages built from
   tables, cards or short paragraphs, but it's wrong for genuine long-form
   prose, where a 1600px line length becomes hard to read. The privacy
   policy is the one page on the site that's pure running text, so it alone
   gets its comfortable reading width back. */
.page-id-3 .entry-content {
    max-width: 720px;
    margin-left: auto;
    margin-right: auto;
}

/* Sticky footer: on short pages (the placeholder "à compléter" pages, in
   particular) the footer used to end wherever the content ended, well
   above the bottom of the viewport, leaving bare background below it that
   reads as "blank space under the footer" even though the footer itself
   is correctly the last element in the document. Covers both page shapes
   in this theme: standard block templates wrap everything in
   .wp-site-blocks, but the safir-training plugin's own module/lesson
   templates put <main> directly under <body> (see single-safir_module.php)
   — body is made the flex column in both cases so one rule covers both. */
body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.wp-site-blocks {
    display: flex;
    flex-direction: column;
    flex: 1 0 auto;
    width: 100%;
}

.wp-site-blocks > main,
body > main {
    flex: 1 0 auto;
}

/* Same bug class as the submenu fix above, different spot: theme.json's
   global link color (teal, styles.elements.link.color.text) targets <a>
   elements directly via a :where() selector. Zero specificity or not, a
   rule that names the element beats plain inheritance — so the Liens
   rapides links, which only had color:#fafaf8 on their *parent* <ul>, were
   rendering in teal-on-teal instead of inheriting the white. Setting color
   directly on the links fixes it for good, in the same footer-wide rule
   any future link here will need. */
.safir-footer a {
    color: #fafaf8 !important;
}

.safir-footer a:hover {
    color: #F5B342 !important;
}

/* Équipe page: a plain, always-visible card (photo, name, role, socials
   stacked in normal document flow) — deliberately NOT a hover-reveal
   overlay anymore. That design relied on position:absolute over a
   position:relative ancestor with no defined height, which real-world
   testing (screenshot from the block editor and the front end both) showed
   silently losing the cascade fight against theme.json's own generated
   group-block styles, and it also meant the editor could never show what
   the card actually looks like (you can't hover a static preview). A plain
   stacked card can't have that class of bug, and is what the block editor
   shows is exactly the real result — true WYSIWYG, which is the point
   after rebuilding this page with real native blocks. Every rule here
   carries !important on purpose: core's own block-library / theme.json
   global styles reliably inject *after* this stylesheet, so anything that
   isn't forced this way is one theme.json regeneration away from silently
   losing again. */
.safir-team-card {
    display: block !important;
    border: 1px solid #dadad6 !important;
    border-radius: 8px !important;
    overflow: hidden !important;
    background: #fff !important;
}

.safir-team-card__media {
    aspect-ratio: 3 / 4 !important;
    overflow: hidden !important;
    margin: 0 !important;
}

.safir-team-card__media img {
    width: 100% !important;
    height: 100% !important;
    display: block !important;
    object-fit: cover !important;
}

.safir-team-card__placeholder {
    width: 100%;
    aspect-ratio: 3 / 4;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--wp--preset--color--primary-tint);
    color: var(--wp--preset--color--primary);
    font-family: var(--wp--preset--font-family--heading);
    font-size: 4.5rem;
    font-weight: 700;
}

.safir-team-card__body {
    padding: 1rem 1.1rem 1.25rem !important;
}

.safir-team-card__name {
    display: block !important;
    color: var(--wp--preset--color--primary) !important;
    font-weight: 700 !important;
    font-size: 1.1rem !important;
    line-height: 1.3 !important;
    margin: 0 0 0.2rem !important;
    white-space: normal;
    overflow-wrap: break-word;
}

/* Statutory research grade (Ingénieur de recherche, Maître de conférences...)
   is administrative status, distinct from what a person actually does day
   to day on SAFIR (already covered by __role below) — small caps + accent
   color reads as an official label without competing with the name for
   attention. */
.safir-team-card__grade {
    display: block !important;
    color: var(--wp--preset--color--accent) !important;
    font-weight: 600 !important;
    font-size: 0.78rem !important;
    text-transform: uppercase !important;
    letter-spacing: 0.04em;
    margin: 0 0 0.3rem !important;
}

.safir-team-card__role {
    display: block !important;
    color: var(--wp--preset--color--foreground) !important;
    opacity: 0.75;
    font-size: 0.9rem !important;
    margin: 0 !important;
}

/* Optional social-link badges (LinkedIn/YouTube/website) in the overlay —
   rendered only for members who actually have a URL set. Also happens to
   fix a real keyboard-accessibility gap in the hover-reveal pattern: with
   no focusable element inside the card, :focus-within could never fire
   for keyboard users. Whichever member gets a link first is the first one
   where Tab actually reveals the overlay. */
.safir-team-card__socials {
    display: flex !important;
    gap: 0.5rem !important;
    margin: 0.7rem 0 0 !important;
    padding: 0 !important;
    list-style: none !important;
}

.safir-team-card__social {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    background: var(--wp--preset--color--primary-tint) !important;
    color: var(--wp--preset--color--primary) !important;
}

.safir-team-card__social:hover,
.safir-team-card__social:focus-visible {
    background: var(--wp--preset--color--primary) !important;
    color: #fff !important;
    outline: none;
}

/* Équipements page: the "Accélérateur" and "Chambres d'analyse" sections
   each get their own card treatment (tinted background + coloured top
   border) so the eye immediately knows which half of the page it's in —
   navy for the accelerator, amber for the chambers, matching the same
   navy/amber pairing already used for pros/cons lists and section badges
   elsewhere on the site. */
.safir-equip-section {
    border-radius: 10px;
    padding: 0.5rem 2rem 2rem;
    margin: 2.5rem 0;
}

.safir-equip-section > :first-child {
    margin-top: 1.5rem;
}

.safir-equip-section--accelerator {
    background: var(--wp--preset--color--primary-tint);
    border-top: 4px solid var(--wp--preset--color--primary);
}

.safir-equip-section--chambers {
    background: #fbf2e6;
    border-top: 4px solid var(--wp--preset--color--accent);
}

/* The accelerator's own hero photo (id 163) is a standalone wp:image, not
   part of the chamber grid's own equal-height treatment below -- at plain
   "large" size with no cap it rendered near its native (portrait, floor-to-
   ceiling) height, much taller than any other photo on the page. Same
   aspect-ratio + object-fit:cover treatment as the Actualités cards
   (.safir-news-card__image) so it reads as "the site's photo size", not an
   outlier. */
.safir-equip-photo {
    aspect-ratio: 4 / 3;
    max-width: 600px;
    margin: 1rem auto;
}

.safir-equip-photo img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

@media (max-width: 600px) {
    .safir-equip-section {
        padding: 0.5rem 1.25rem 1.5rem;
    }
}

/* Chamber cards: a 2-column grid of plain white cards (same border/radius
   language as the Actualités cards below) sitting inside the amber
   "Chambres d'analyse" section, each with room for a photo up top — added
   directly in the editor as the real photos become available. */
.safir-chamber-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1.75rem;
    margin-top: 1.5rem;
}

/* Gutenberg's own block-gap support CSS (generated per-instance for this
   group's default "flow" layout) adds margin-block-start to every child
   except the first, regardless of the display:grid override above -- so it
   stacked on top of this grid's own `gap`, pushing every card except
   Polyvalente down by an extra ~19px and, since CSS Grid's implicit row
   sizing follows each item's own effective box, made the two rows' heights
   diverge instead of aligning. This is what "la chambre polyvalente va
   plus haut que la goniométrique" was actually seeing -- the photo bands
   were already equal (fixed above); the *cards* were offset by this
   leftover margin. Two classes here outweighs Gutenberg's one-class
   generated selector, so this wins regardless of source order. */
.safir-chamber-grid > .safir-chamber-card {
    margin-top: 0;
    margin-block-start: 0;
}

@media (max-width: 782px) {
    .safir-chamber-grid {
        grid-template-columns: 1fr;
    }
}

.safir-chamber-card {
    background: #fff;
    border: 1px solid #dadad6;
    border-radius: 8px;
    overflow: hidden;
}

/* Every card's photo band is forced to the same 4:3 height regardless of
   what fills it -- a single full-width photo (Polyvalente, Goniométrique),
   a 2-up gallery (MEIS), or a placeholder (ERDA, no photo yet). Without
   this, aspect-ratio applied per-<img> made the MEIS gallery's two
   half-width photos render at half the height of a full-width one, and the
   photo-less ERDA card had no photo band at all -- three different frame
   heights in one grid, which is what "les cadres n'ont pas la même taille"
   was reporting. Applying aspect-ratio to the *container* (this rule) and
   stretching its contents to fill (below) keeps the band height identical
   no matter how many photos share it. Scoped with the child combinator so
   it targets only each card's own lead photo block, not the individual
   images nested one level deeper inside the MEIS gallery. */
.safir-chamber-card > .wp-block-image,
.safir-chamber-card > .wp-block-gallery,
.safir-chamber-card__placeholder {
    margin: 0;
    aspect-ratio: 4 / 3;
    overflow: hidden;
}

.safir-chamber-card > .wp-block-image img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The MEIS card's photo is a 2-up gallery rather than a single image.
   Deliberately NOT touching the gallery's own display/flex context here
   (an earlier version forced display:flex + flex-basis on the nested
   images) -- that broke WordPress's own lightbox "Agrandir" button, whose
   position core computes assuming its normal layout: the button ended up
   positioned off-screen (found live: bounding rect x -35px, outside the
   card entirely, unclickable). Only sizing the <img> itself is safe since
   it doesn't change the containing block core's button math relies on.
   The selector repeats core's own class/type combination (needs to beat
   wp-includes/blocks/gallery/style.css's ".wp-block-gallery.has-nested-
   images figure.wp-block-image img { height: auto; width: auto; }") so
   this actually takes effect rather than being silently outweighed. */
.safir-chamber-card > .wp-block-gallery.has-nested-images figure.wp-block-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.safir-chamber-card__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f4f3ef;
    border-bottom: 1px dashed #cbcac4;
    color: #767568;
    font-size: 0.9rem;
    font-style: italic;
    text-align: center;
    padding: 0 1rem;
}

.safir-chamber-card__body {
    padding: 1.1rem 1.4rem 1.4rem;
}

.safir-chamber-card__body h3 {
    margin-top: 0;
}

.safir-chamber-card__body p:last-child {
    margin-bottom: 0;
}

/* Actualités: article cards (image, date, title, excerpt) replacing the
   plain "Derniers articles" link list — every post gets a real featured
   image, per the site's "systematic photo" requirement for this page. */
.safir-news-grid .wp-block-post-template {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2.5rem;
    list-style: none;
    margin: 2rem 0;
    padding: 0;
}

/* This is the actual root cause of "the first card always looks
   different": core injects `:root :where(.is-layout-flow) > * {
   margin-block-start: 1.2rem }` (inline stylesheet, global-styles),
   intended for a plain top-to-bottom "flow" list where every item but the
   first needs space above it. The <ul> keeps its is-layout-flow class even
   after we turn it into a real grid via `display:grid` above, so that rule
   still fires -- every card except the very first grid item (whichever
   post currently sorts first) gets an extra 19.2px pushed inside its own
   grid cell, on top of the `gap` already handling spacing between rows.
   Confirmed live via getComputedStyle (marginTop: 19.2px on item 2/3,
   0px on item 1) before finding this. Our own `gap` already does 100% of
   the real spacing job in a grid; this leftover flow-layout margin has no
   role here and must be zeroed out. */
.safir-news-grid .wp-block-post-template > li {
    margin-block-start: 0 !important;
}

@media (max-width: 1024px) {
    .safir-news-grid .wp-block-post-template {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 682px) {
    .safir-news-grid .wp-block-post-template {
        grid-template-columns: 1fr;
    }
}

.safir-news-card {
    border: 1px solid #dadad6;
    border-radius: 8px;
    overflow: hidden;
    background: #fff;
    height: 100%;
}

.safir-news-card__image {
    margin: 0;
}

.safir-news-card__image img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.safir-news-card__body {
    padding: 1.25rem 1.5rem 1.5rem;
}

/* Category badge (core/post-terms) — same pill language as the social-link
   badges on the Équipe cards (primary-tint fill, primary text), so it reads
   as "the site's badge style" rather than a one-off. WordPress's own
   post_class() already stamps a category-{slug} class on each card's <li>,
   which the front-end filter script (news-filter.js) reads directly — this
   badge only needs to LOOK right, no data-attribute wiring required. */
.safir-news-card__categories {
    display: block;
    margin-bottom: 0.5rem;
}

.safir-news-card__categories a {
    display: inline-block;
    background: var(--wp--preset--color--primary-tint);
    color: var(--wp--preset--color--primary);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    text-decoration: none;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
}

.safir-news-card__categories a:hover,
.safir-news-card__categories a:focus-visible {
    background: var(--wp--preset--color--primary);
    color: #fff;
}

.safir-news-card__date {
    display: block;
    font-size: 0.8rem;
    color: var(--wp--preset--color--foreground);
    opacity: 0.65;
    margin-bottom: 0.4rem;
}

/* Locked to a fixed 2-line box (min-height AND max-height both set to the
   same value) so every title occupies identical space regardless of length
   ("IBAF 2026" vs "Un dernier cadeau pour Ian : une pièce unique en bois
   tourné") -- CSS Grid's default align-items:stretch makes every card in a
   row match its tallest sibling, so one long title anywhere in a row was
   enough to visibly stretch every other card in that row.
   Tried -webkit-line-clamp first, but live measurement (real
   getComputedStyle + a forced inline !important override, both still
   resolving to display:flow-root) showed current Chrome no longer treats
   -webkit-box as a real layout mode, silently dropping the clamp -- a
   3-line title rendered at full, unclamped height. Switched to a plain
   max-height + overflow:hidden cap, which needs no special display mode.
   That alone still left a residual few pixels of extra height on long
   titles: core's own CSS makes the <a> inside a post-title `display:
   inline-block` (.wp-block-post-title :where(a)), and that inline-block's
   own unclamped intrinsic size was still leaking into layout/sizing
   despite being visually clipped by the parent's overflow:hidden -- so the
   same max-height + overflow:hidden is applied to the anchor directly,
   not just its H2 parent. */
.safir-news-card__title {
    font-size: 1.15rem;
    line-height: 1.35;
    margin: 0 0 0.6rem;
    overflow: hidden;
    min-height: calc(1.35em * 2);
    max-height: calc(1.35em * 2);
}

.safir-news-card__title a {
    max-height: calc(1.35em * 2);
    overflow: hidden;
    color: var(--wp--preset--color--foreground);
    text-decoration: none;
}

.safir-news-card__title a:hover,
.safir-news-card__title a:focus-visible {
    color: var(--wp--preset--color--primary);
    text-decoration: underline;
}

.safir-news-card__excerpt p {
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.5;
    color: var(--wp--preset--color--foreground);
    opacity: 0.85;
    overflow: hidden;
    min-height: calc(1.5em * 3);
    max-height: calc(1.5em * 3);
}

/* "Partager" button, one per card (news-share.js reads the card's own title
   link at click time for the URL/title, so this markup stays identical
   across every card in the Query Loop template -- see that file's docblock
   for why a per-post value can't just be templated in statically here). */
.safir-news-card__share {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 1rem;
    padding: 0.4rem 0.9rem;
    border: 1px solid #dadad6;
    border-radius: 999px;
    background: #fff;
    color: var(--wp--preset--color--primary);
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.safir-news-card__share svg {
    flex-shrink: 0;
}

.safir-news-card__share:hover,
.safir-news-card__share:focus-visible {
    background: var(--wp--preset--color--primary-tint);
    border-color: var(--wp--preset--color--primary);
}

.safir-news-card__share.is-copied {
    background: var(--wp--preset--color--primary);
    border-color: var(--wp--preset--color--primary);
    color: #fff;
}

/* Same button, reused on the single article page (templates/single.html)
   under the title/date -- that group is a vertical flex column, which would
   otherwise stretch the button to the column's full width. */
.safir-article-share {
    align-self: flex-start;
    margin-top: 0.25rem;
}

/* Filter chips + client-side pager, injected by news-filter.js right before
   and after the grid. The query loop itself now renders every post
   (perPage:100) instead of WordPress's own 6-per-page split -- filtering by
   theme only makes sense against the FULL list (a category with both its
   posts sitting on "page 2" would otherwise look empty on page 1) -- so
   pagination is reproduced here in JS, operating on whichever subset the
   active filter leaves visible. core/query-pagination is left in the markup
   as a no-JS fallback; with only one real WordPress-side page it renders
   nothing when JS is available, and the full unpaginated list is still
   perfectly readable if it isn't. */
.safir-news-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin: 1.5rem 0;
}

.safir-news-filter {
    font-family: var(--wp--preset--font-family--body);
    font-size: 0.9rem;
    font-weight: 600;
    background: #fff;
    color: var(--wp--preset--color--foreground);
    border: 1px solid #dadad6;
    border-radius: 999px;
    padding: 0.4rem 1rem;
    cursor: pointer;
}

.safir-news-filter:hover,
.safir-news-filter:focus-visible {
    border-color: var(--wp--preset--color--primary);
}

.safir-news-filter.is-active {
    background: var(--wp--preset--color--primary);
    border-color: var(--wp--preset--color--primary);
    color: #fff;
}

.safir-news-grid .wp-block-post-template li.safir-news-card-item--hidden {
    display: none !important;
}

.safir-news-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    margin: 2rem 0;
}

.safir-news-pager__status {
    font-size: 0.9rem;
    color: var(--wp--preset--color--foreground);
    opacity: 0.75;
    font-variant-numeric: tabular-nums;
}

.safir-news-pager button {
    font-family: var(--wp--preset--font-family--body);
    font-size: 0.9rem;
    font-weight: 600;
    background: #fff;
    color: var(--wp--preset--color--primary);
    border: 1px solid var(--wp--preset--color--primary);
    border-radius: 6px;
    padding: 0.45rem 1rem;
    cursor: pointer;
}

.safir-news-pager button:hover,
.safir-news-pager button:focus-visible {
    background: var(--wp--preset--color--primary);
    color: #fff;
}

.safir-news-pager button:disabled {
    opacity: 0.4;
    cursor: default;
    background: #fff;
    color: var(--wp--preset--color--primary);
}

/* These figure captions exist only as a data source for the lightbox
   caption below (lightbox-captions.js reads their text into the enlarged
   view) -- by design they're not meant to show a second time in the normal
   in-page photo grid. Without this rule they did show there anyway, and
   inconsistently: unconstrained cards (the accelerator photo) rendered
   them plainly below the photo, while cards with the fixed-height 4:3 crop
   (chamber cards) had no room left after the photo filled 100% of the
   box, so the caption was clipped into a sliver of text overlapping the
   photo's bottom edge -- both wrong, and the extra in-flow height on the
   unconstrained card also reopened the "cadres n'ont pas la même taille"
   problem the aspect-ratio rules above were added to fix. */
.wp-block-image > figcaption.wp-element-caption,
.wp-block-gallery figcaption.wp-element-caption {
    display: none;
}

/* Hover zoom (2026-09 request): team photos, chamber photos, and the MEIS
   page's own gallery photos scale up slightly on hover. Team photos are
   zoom-only (no caption there); chamber and MEIS photos also reveal their
   figcaption -- hidden above for the lightbox -- as an overlay while
   hovered, which needs `display` re-enabled locally (opacity/transform
   handle the actual show/hide so it can transition instead of jump-cutting).
   Selector specificity here (3 classes/element) beats the 2-class hide rule
   above regardless of source order. */
.safir-team-card__media {
    position: relative;
}

.safir-team-card__media img {
    transition: transform 0.35s ease;
}

.safir-team-card__media:hover img {
    transform: scale(1.08);
}

.safir-chamber-card > .wp-block-image,
.safir-chamber-card > .wp-block-gallery figure.wp-block-image,
.safir-equip-photo,
.page-id-46 figure.wp-block-image,
.page-id-378 figure.wp-block-image {
    position: relative;
    overflow: hidden;
}

.safir-chamber-card > .wp-block-image img,
.safir-chamber-card > .wp-block-gallery figure.wp-block-image img,
.safir-equip-photo img,
.page-id-46 figure.wp-block-image img,
.page-id-378 figure.wp-block-image img {
    transition: transform 0.35s ease;
}

.safir-chamber-card > .wp-block-image:hover img,
.safir-chamber-card > .wp-block-gallery figure.wp-block-image:hover img,
.safir-equip-photo:hover img,
.page-id-46 figure.wp-block-image:hover img,
.page-id-378 figure.wp-block-image:hover img {
    transform: scale(1.08);
}

.safir-chamber-card > .wp-block-image > figcaption.wp-element-caption,
.safir-chamber-card > .wp-block-gallery figure.wp-block-image > figcaption.wp-element-caption,
.safir-equip-photo > figcaption.wp-element-caption,
.page-id-46 figure.wp-block-image > figcaption.wp-element-caption,
.page-id-378 figure.wp-block-image > figcaption.wp-element-caption {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    padding: 0.6rem 0.8rem;
    background: linear-gradient(to top, rgba(15, 20, 20, 0.85), rgba(15, 20, 20, 0));
    color: #fafaf8;
    font-size: 0.85rem;
    opacity: 0;
    transform: translateY(6px);
    transition: opacity 0.3s ease, transform 0.3s ease;
    pointer-events: none;
}

.safir-chamber-card > .wp-block-image:hover > figcaption.wp-element-caption,
.safir-chamber-card > .wp-block-gallery figure.wp-block-image:hover > figcaption.wp-element-caption,
.safir-equip-photo:hover > figcaption.wp-element-caption,
.page-id-46 figure.wp-block-image:hover > figcaption.wp-element-caption,
.page-id-378 figure.wp-block-image:hover > figcaption.wp-element-caption {
    opacity: 1;
    transform: translateY(0);
}

/* CAD renders (MEIS open + zoom, ERDA line) are exported on a plain white
   background, which melts into the white chamber cards and the off-white
   page alike. A hairline frame in the brand navy (--wp--preset--color--primary,
   #183868, at 45%) gives them an edge, and in the ERDA card separates the
   picture from the text underneath. No hover zoom on these: the frame would
   scale with the image and get clipped by the figure. */
.safir-cad-image img {
    border: 1px solid rgba(24, 56, 104, 0.45);
    box-sizing: border-box;
}

.safir-cad-image:hover img,
.page-id-46 .safir-cad-image:hover img,
.page-id-378 .safir-cad-image:hover img,
.safir-chamber-card > .wp-block-image.safir-cad-image:hover img {
    transform: none;
}

/* In a chamber card the photo band used to run edge to edge, so a frame on
   the image would sit right on the card's own border (a doubled line). Inset
   it instead: same 4:3 band height as the other cards (border-box keeps the
   padding inside the aspect ratio), picture shown whole (contain, no crop).
   No padding at the bottom: the frame's bottom line must sit exactly where
   the neighbouring cards' photos end (asked for, so the row reads as one
   straight edge), and it doubles as the divider above the card's text. */
.safir-chamber-card > .wp-block-image.safir-cad-image {
    box-sizing: border-box;
    padding: 10px 10px 0;
}

.safir-chamber-card > .wp-block-image.safir-cad-image img {
    object-fit: contain;
}

/* The enlarged-photo title (lightbox-captions.js). Deliberately NOT a
   child of the overlay's own reactive ".wp-block-image" figure -- an
   earlier version tried that and a live diagnostic showed it oscillating
   forever, added and silently removed again, because core's own reactive
   rendering doesn't tolerate an unexpected child there and kept undoing
   the change. This is a sibling of that figure instead (a direct child of
   ".wp-lightbox-overlay", which core never touches), positioned via
   JS-computed inline left/top/width from the image's own real rendered
   box rather than relying on core's own --wp--lightbox-container-*
   variables (measured NaN / negative live, on this WordPress version,
   despite the image itself rendering at the correct size). */
.wp-lightbox-overlay .safir-lightbox-caption {
    position: fixed;
    display: none;
    align-items: flex-end;
    justify-content: center;
    height: 60px;
    padding: 0 1.5rem 0.75rem;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.75), transparent);
    color: #fff;
    font-size: 0.95rem;
    text-align: center;
    box-sizing: border-box;
    pointer-events: none;
    z-index: 3000001;
}


/* Language switcher (Polylang navigation block): two flags, current language
   at full strength, the other slightly dimmed until hovered/focused. */
.safir-header-nav .lang-item img {
	/* !important: Polylang writes width/height inline on the <img>. */
	width: 26px !important;
	height: auto !important;
	border-radius: 2px;
	box-shadow: 0 0 0 1px rgba(250, 250, 248, 0.55);
	vertical-align: middle;
}
.safir-header-nav .lang-item:not(.current-lang) img {
	opacity: 0.6;
}
.safir-header-nav .lang-item a:hover img,
.safir-header-nav .lang-item a:focus-visible img {
	opacity: 1;
}
.safir-header-nav .lang-item.current-lang img {
	box-shadow: 0 0 0 2px #fafaf8;
}

/* Mobile: the flags cloned next to the "Ouvrir le menu" button by
   mobile-lang-flags.js (see that file). Same breakpoint core WordPress uses
   to switch the nav block into its hamburger overlay (block-library's
   ".wp-block-navigation__responsive-container-open:not(.always-shown)" is
   hidden from 600px up), so the clone and the hamburger appear/disappear
   together. The originals stay in the DOM inside the opened mobile menu
   (harmless) but are hidden there so the choice isn't shown twice. */
.safir-mobile-lang-switch {
	display: none;
}
@media (max-width: 599px) {
	.safir-mobile-lang-switch {
		display: flex;
		align-items: center;
		gap: 10px;
		list-style: none;
		margin: 0 10px 0 0;
		padding: 0;
	}
	.safir-header-nav.wp-block-navigation .wp-block-navigation__responsive-dialog .lang-item.wp-block-navigation-item {
		display: none !important;
	}
}
