/* AI Hub — a single, simple page aggregating LinkedIn posts (see
   docs/linkedin-integration.md). Every card is a plain, instant local
   element built from data already cached by the sync job — no LinkedIn
   iframe, no cookie banner, no loading state of any kind lives on this
   page; a card's own href just opens the real post on LinkedIn in a new
   tab.

   Design: "dark glass" — the exact frosted-panel recipe already used
   elsewhere on this site (background/opacity/blur/border ratios lifted
   directly from .blog-sidebar-card and .blog-search in blog.css, and the
   blur(24px) figure used consistently in header.css/goal.css/
   contact-hero.css/portfolio-hero.css/testimonials.css/download-cv.css) —
   deliberately NOT a second, independent visual language. The page
   background itself is untouched (inherited from body in css/base.css);
   only individual surfaces (cards, the identity block, the sort control)
   get the glass treatment, floating on the site's own violet/black
   gradient rather than sitting inside a separate light or solid zone.
   LinkedIn blue is reserved for links/buttons/focus states only — every
   other surface reuses this site's own dark palette. */

/* Design tokens — used by the identity block (in the hero) and everything
   in the feed section below it, so defined on both. */
.ai-hub-hero,
.ai-hub-feed {
  /* Anthracite with a slight violet/blue undertone, not neutral grey —
     tinted using this site's own accent hue (the same violet family as
     body's radial-gradient patches in base.css and the .btn gradient
     below), not an invented new color. */
  --li-card-bg: rgba(38, 32, 56, 0.55);
  --li-card-blur: blur(24px);
  --li-border: rgba(255, 255, 255, 0.08);
  --li-border-hover: rgba(88, 130, 246, 0.4);
  --li-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
  --li-shadow-hover: 0 10px 32px rgba(63, 123, 248, 0.16), 0 4px 16px rgba(184, 107, 248, 0.12);
  --li-radius: 16px;
  /* Off-white / light grey, not pure black-on-white the way the first
     light-theme pass had it. */
  --li-text: rgba(255, 255, 255, 0.92);
  --li-text-secondary: rgba(255, 255, 255, 0.6);
  /* Reserved for links/buttons/focus only — never a surface fill. */
  --li-blue: #0a66c2;
  --li-blue-hover: #4a9eff;
  --li-heart: #e0245e;
  /* The dark, neutral zone behind a letterboxed image — replaces the old
     light --li-bg tint entirely (requirement: no white around images). */
  --li-media-bg: rgba(10, 8, 16, 0.6);
}

/* Same overall page width as the homepage (.hero__grid: 1560px) and
   Contact/FAQ, not the page-split default's 1180px — the first pass left
   large unused margins on wider screens while the post grid felt cramped
   in the middle; this uses that side space instead. Scoped to this page
   only via .page-split--ai-hub / .ai-hub-hero (set in pages/ai-hub.njk) —
   doesn't touch any other page-split page's own width or ratio.

   The intro column stays a fixed width (not the page-split default's
   fr-based ratio) — this page's left block is a compact, self-contained
   profile card, not a wide intro paragraph, so it only ever needs enough
   room for that card (.ai-hub-identity tops out at 320px); a proportional
   ~40% share of a 1560px row would leave a few hundred px of dead space
   next to the card instead of handing it to the grid, which needs it. */
.ai-hub-hero .section__inner {
  max-width: 1560px;
}

@media (min-width: 1024px) {
  .page-split--ai-hub {
    max-width: 1560px;
    --page-split-intro: 390px;
    --page-split-gap: 56px;
  }
}

/* .ai-hub-hero__inner is now .page-split__intro (page-split.css handles
   its width) — .page-title/.page-subtitle (page-heading.css) supply the
   H1/subtitle typography, shared with every other page. .ai-hub-subtitle
   only tweaks the width slightly.

   Centred, not left-aligned like the shared page-split default (see the
   !important-free override below) — with the card as the visual anchor of
   this column, a centred title/subtitle/card stack reads as one composed
   group instead of a paragraph with a card awkwardly placed under it.
   Scoped to .ai-hub-hero__inner specifically (doubled up with
   .page-split__intro for specificity) so no other .page-split__intro
   page (Compétences, Tarifs, FAQ) is affected. */
.ai-hub-hero__inner.page-split__intro {
  text-align: center;
}

@media (min-width: 1024px) {
  /* Left-aligned on desktop (explicit direction — mobile keeps the
     centered treatment from the base rule above, untouched). The card
     below (.ai-hub-identity) already got this same desktop-only
     left-align treatment in an earlier round — this brings the title/
     subtitle above it in line with it, rather than the two reading as a
     centered pair sitting above a left-aligned card. */
  .ai-hub-hero__inner.page-split__intro {
    text-align: left;
  }

  /* text-align alone isn't enough here -- .ai-hub-hero__sticky (the flex
     column wrapping title/subtitle/card) sets align-items: center, which
     centers each item's own (shrink-to-fit) box on the cross axis
     regardless of the text-align inside it, unless it opts out with its
     own align-self -- same fix already applied to .ai-hub-identity. */
  .ai-hub-hero__inner.page-split__intro .page-title,
  .ai-hub-hero__inner.page-split__intro .page-subtitle {
    align-self: flex-start;
    text-align: left;
    margin-left: 0;
    margin-right: 0;
  }

  /* Stretched to the full row height (matches the much taller post grid
     next to it) so .ai-hub-hero__sticky below has real room to remain
     stuck while scrolling — an un-stretched grid item (the page-split
     default, align-items: start) is only as tall as its own content,
     which leaves a sticky child almost no room before it un-sticks again. */
  .ai-hub-hero__inner {
    align-self: stretch;
    /* Cancels this page's own `.section { padding-top: 160px }` so the
       stretched box is already flush with the viewport top at scroll 0
       — not just once scrolled 160px down (same fix as the FAQ page's
       .faq-intro, see faq.css). */
    margin-top: -160px;
  }
}

.ai-hub-subtitle {
  max-width: 46ch;
}

/* Sticks at the viewport top and centres its content vertically within
   exactly one viewport height — the actual "profile card" look, always
   vertically centred while the post grid scrolls past beside it (same
   two-layer stretch-parent/sticky-child pattern as the FAQ page's
   .faq-intro / .faq-intro__sticky). */
@media (min-width: 1024px) {
  .ai-hub-hero__sticky {
    position: sticky;
    top: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
}

/* Identity block — the ONE place name/photo/headline appear on this page
   (not repeated per card). Compact vertical card now (photo+name+headline
   on top, follower stat, then the follow button, each stacked) — it lives
   in the narrow left column next to the title/subtitle, not a full-width
   strip spanning the whole page like the previous centered-hero version.

   No colored top hairline and no gradient ring around the avatar anymore
   (both removed per explicit direction) — depth now comes only from the
   soft two-tone background wash, the border and the ambient shadow below,
   plus a hover lift (see .ai-hub-identity:hover). Still the exact same
   brand gradient elsewhere on the card (stat number, button) — richer
   treatment, not a new palette. */
/* Was overflow:hidden (only ever needed to clip the old top hairline bar
   to the card's own rounded corners) — dropped now that it's gone, so
   the animated border ring and hover halo below (both sit slightly
   outside the card's own box) aren't cut off. isolation:isolate gives
   the ring pseudo-element (negative z-index) a stacking context scoped
   to just this card, so it can paint behind the card's own content
   without also having to worry about the page behind it. */
.ai-hub-identity {
  position: relative;
  isolation: isolate;
  width: 100%;
  max-width: 320px;
  /* Centered on mobile/tablet (explicit direction — stays as-is there);
     left-aligned on desktop only, see the >=1024px override below. */
  margin: 28px auto 0;
  padding: 24px 24px 26px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(63, 123, 248, 0.14), transparent 60%),
    radial-gradient(120% 100% at 100% 100%, rgba(184, 107, 248, 0.12), transparent 60%),
    var(--li-card-bg);
  backdrop-filter: var(--li-card-blur);
  -webkit-backdrop-filter: var(--li-card-blur);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 22px;
  box-shadow: 0 20px 44px rgba(0, 0, 0, 0.4), 0 2px 0 rgba(255, 255, 255, 0.04) inset;
  text-align: left;
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

/* Desktop only (explicit direction — "juste pour la version PC", mobile
   stays centered per the base rule above): the card itself moves to the
   left edge of the intro column instead of centering within it. Scoped
   to just .ai-hub-identity — the title/subtitle above it keep their own
   existing centered treatment, untouched.

   margin-left/right: 0 alone isn't enough -- .ai-hub-hero__sticky (the
   flex column wrapping title/subtitle/card) sets align-items: center,
   which centers this card on the cross axis regardless of its own
   margins unless it opts out with its own align-self. */
@media (min-width: 1024px) {
  .ai-hub-identity {
    align-self: flex-start;
    /* More breathing room under the subtitle than the base 28px
       (explicit direction — "descend légèrement la carte"), desktop
       only. */
    margin-top: 48px;
    margin-left: 0;
    margin-right: 0;
  }
}

/* A thin light continuously travels around the card's edge — a rotating
   conic gradient masked down to just the border band (padding-box vs
   border-box via mask-composite), independent of the hover state below. */
@property --ai-hub-border-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.ai-hub-identity::after {
  content: "";
  position: absolute;
  inset: -1.5px;
  z-index: -1;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(
    from var(--ai-hub-border-angle),
    transparent 0deg,
    rgba(63, 123, 248, 0.9) 35deg,
    rgba(184, 107, 248, 0.95) 80deg,
    transparent 130deg,
    transparent 360deg
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
  animation: ai-hub-border-travel 5s linear infinite;
}

@keyframes ai-hub-border-travel {
  to {
    --ai-hub-border-angle: 360deg;
  }
}

/* Small hover lift plus a soft colored halo bloom around the card (on top
   of the existing dark ambient shadow) — the card itself isn't a link
   (the CTA inside it is), so this is purely an ambient "alive" cue, not
   an affordance for a click target. */
.ai-hub-identity:hover {
  transform: translateY(-4px);
  border-color: rgba(255, 255, 255, 0.18);
  box-shadow:
    0 26px 52px rgba(0, 0, 0, 0.48),
    0 0 36px rgba(63, 123, 248, 0.28),
    0 0 64px rgba(184, 107, 248, 0.18),
    0 0 0 1px rgba(63, 123, 248, 0.18),
    0 2px 0 rgba(255, 255, 255, 0.04) inset;
}

/* Text-wrap-around-image, via a real CSS float — NOT a fixed two-column
   flex/grid ([photo][text column]), which would keep the text column's
   narrower width all the way to its very last line and leave a dead
   empty column under the photo once the text runs taller than it (the
   exact bug a fixed-column layout produced here before). The photo
   floats; .ai-hub-identity__name and .ai-hub-identity__headline are
   plain block-level content after it in normal flow, so their line
   boxes narrow only while they still overlap the float's own height,
   then automatically reclaim the FULL block width the moment they clear
   it — same mechanism as a photo dropped into a paragraph of body text.
   This wrapper's only job is to clear that float afterward so it can't
   bleed into the follower-stat/button rows below. */
.ai-hub-identity__top::after {
  content: "";
  display: table;
  clear: both;
}

/* Plain circular frame — was a gradient ring + a blurred glow behind it;
   removed per explicit direction, kept just as a sizing wrapper with a
   thin neutral border matching every other card edge on this page
   (var(--li-border)), not a colored accent.

   Responsive size (not one fixed px value for every viewport, per
   explicit direction): a touch smaller on tablet, smaller again on
   phones — see the media queries further down. width/height always
   match (plus aspect-ratio as a belt-and-braces guard against a stray
   width-only override anywhere) so it's never anything but a perfect
   circle, object-fit:cover so the source photo is never squashed or
   stretched to get there. */
.ai-hub-identity__avatar-ring {
  float: left;
  /* Right/bottom clearance for the text wrap; deliberately nothing on
     top/left — any margin there would just push the photo away from the
     card's own top/left edge for no reason. */
  margin: 0 12px 6px 0;
  width: 58px;
  height: 58px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  overflow: hidden;
}

.ai-hub-identity__avatar {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--li-border);
}

.ai-hub-identity__name {
  margin: 0;
  font-family: var(--font-satoshi);
  font-weight: 700;
  font-size: 16px;
  color: #fff;
}

/* Full headline, no ellipsis, no clamp — set by js/ai-hub.js from the real
   fetched value (never the short static SSR placeholder — see
   data-hub-headline, the single source of truth for this text; nothing
   here duplicates or hardcodes it beyond that first-paint placeholder).
   Natural wrapping only — its early line(s) narrow to sit beside the
   floated photo exactly like the name above it does, then widen back out
   to the block's full width the moment they pass the photo's bottom
   edge, with no leftover indentation or empty space. */
.ai-hub-identity__headline {
  margin: 6px 0 0;
  font-family: var(--font-satoshi);
  font-size: 13px;
  line-height: 1.45;
  color: var(--li-text-secondary);
}

@media (max-width: 1023px) {
  .ai-hub-identity__avatar-ring {
    width: 54px;
    height: 54px;
  }
}

@media (max-width: 640px) {
  .ai-hub-identity__avatar-ring {
    width: 50px;
    height: 50px;
  }
}

/* `[hidden]` alone loses to this rule's own `display: flex` (an author
   `display` value always overrides the UA's `[hidden]` default, regardless
   of specificity — the same bug already hit once on .ai-hub-loadmore and
   .ai-hub-consent earlier in this project), hence the explicit override
   placed first. */
.ai-hub-identity__followers[hidden] {
  display: none;
}

/* Centered per explicit direction — the rest of the card (name,
   headline) stays left-aligned, only this stat row is centered as its
   own accent, not tied to .ai-hub-identity's overall text-align. */
.ai-hub-identity__followers {
  display: flex;
  justify-content: center;
  padding-top: 2px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

/* The stat itself — deliberately minimalist per explicit direction: no
   background box, no border, no badge. Just the number (real IAcine Tech
   gradient, the same stops as .btn and the identity block's own accent
   bar — never an invented color) and a small label beside it. Row layout
   (not stacked) now that it sits on its own line above the button, rather
   than needing to share a row with it. */
.ai-hub-identity__stat {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding-top: 12px;
}

.ai-hub-identity__stat-number {
  font-family: var(--font-satoshi);
  font-weight: 750;
  font-size: 20px;
  background: linear-gradient(270.04deg, #3f7bf8 0.04%, #b86bf8 99.96%);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  white-space: nowrap;
}

.ai-hub-identity__stat-label {
  font-family: var(--font-satoshi);
  font-weight: 500;
  font-size: 12.5px;
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.55);
  white-space: nowrap;
}

/* Full-width within the compact card, LinkedIn-blue-flavored (not the
   site's generic purple/blue .btn gradient — this CTA's whole job is
   "go to LinkedIn", so it keeps that brand association) but with real
   depth instead of the old flat single-tone fill: a subtle diagonal
   gradient, an ambient blue glow, and a lift + icon nudge on hover. */
.ai-hub-identity__cta {
  width: 100%;
  gap: 8px;
  background: linear-gradient(135deg, #0a66c2 0%, #0f7fe0 60%, #2f97f5 100%);
  box-shadow: 0 10px 24px rgba(10, 102, 194, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.06) inset;
  transition: transform 0.22s ease, box-shadow 0.22s ease, background 0.22s ease;
}

.ai-hub-identity__cta:hover {
  opacity: 1;
  transform: translateY(-2px);
  background: linear-gradient(135deg, #0f7fe0 0%, #2f97f5 60%, #5cb3ff 100%);
  box-shadow: 0 14px 30px rgba(15, 127, 224, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.12) inset;
}

.ai-hub-identity__cta:active {
  transform: translateY(0);
}

/* A plain white "in" glyph on the blue gradient read as barely-there
   (same real complaint as LinkedIn's own icon needing its usual white
   badge to stay legible on colored surfaces) — given a small white
   backing chip instead, same idea as LinkedIn's own real badge. */
.ai-hub-identity__cta svg {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  padding: 3px;
  border-radius: 7px;
  background: #fff;
  color: var(--li-blue);
}

@media (max-width: 640px) {
  /* Was max-width:420px with no margin/width change from the desktop
     base rule (width:100%, margin:28px 0 0) — on a real phone that let
     it stretch to the full column width instead of reading as a compact
     card ("elle prend de la place en largeur" — confirmed directly on a
     narrow viewport). Capped to a real fixed width and centered instead
     of just capped. Widened slightly from a first pass at 260px so
     "2024 abonnés" (see .ai-hub-identity__stat below) has enough room to
     stay on one line. */
  .ai-hub-identity {
    width: 288px;
    max-width: 100%;
    margin: 28px auto 0;
    text-align: center;
  }

  /* The card's own text-align:center above is meant for the follower
     stat row only (see .ai-hub-identity__followers) — the name/headline
     stay fully left-aligned at every width per explicit direction, so
     this overrides it back for just this block. */
  .ai-hub-identity__top {
    text-align: left;
  }

  /* Was flex-direction:column here, forcing the number onto its own line
     above the label no matter how much width was actually available —
     that, not the card's own width, is why "2024 abonnés" split across
     two lines. Falls back to the base rule's row layout (same as
     desktop) now that the card above is wide enough for it. */
}

/* Toolbar (sort control) --------------------------------------------------- */

.ai-hub-toolbar {
  display: flex;
  justify-content: flex-end;
  margin: 0 0 28px;
}

.ai-hub-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Segmented sort control — replaces a native <select> entirely (its
   browser-drawn dropdown can't be restyled and always broke the page's
   dark design with a system grey/blue menu). Same dark-glass recipe as
   every other surface on this page, a real ARIA radiogroup underneath
   (see pages/ai-hub.njk / js/ai-hub.js for roving-tabindex keyboard nav). */
.ai-hub-sortgroup {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  background: var(--li-card-bg);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--li-border);
  border-radius: 14px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 4px 16px rgba(0, 0, 0, 0.25);
}

.ai-hub-sortgroup__btn {
  appearance: none;
  border: none;
  background: transparent;
  font-family: var(--font-satoshi);
  font-size: 14px;
  font-weight: 600;
  color: var(--li-text-secondary);
  padding: 10px 18px;
  min-height: 44px;
  border-radius: 10px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}

.ai-hub-sortgroup__btn:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--li-text);
}

/* Active option — the site's own IAcine Tech gradient (identical stops to
   .btn in button.css, see .ai-hub-identity::before above), never LinkedIn
   blue or a browser default. */
.ai-hub-sortgroup__btn[aria-checked="true"] {
  background: linear-gradient(270.04deg, #3f7bf8 0.04%, #b86bf8 99.96%);
  color: #fff;
  box-shadow: 0 4px 14px rgba(63, 123, 248, 0.35), 0 2px 8px rgba(184, 107, 248, 0.25);
}

.ai-hub-sortgroup__btn[aria-checked="true"]:hover {
  background: linear-gradient(270.04deg, #3f7bf8 0.04%, #b86bf8 99.96%);
  color: #fff;
}

.ai-hub-sortgroup__btn:focus-visible {
  outline: 2px solid var(--li-border-hover);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(88, 130, 246, 0.25);
}

@media (max-width: 640px) {
  .ai-hub-toolbar {
    justify-content: stretch;
  }

  .ai-hub-sortgroup {
    display: flex;
    width: 100%;
  }

  .ai-hub-sortgroup__btn {
    flex: 1;
    padding: 10px 8px;
    font-size: 13px;
  }
}

/* Grid ---------------------------------------------------------------- */

.ai-hub-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  transition: opacity 0.18s ease, transform 0.18s ease;
}

/* Set briefly by js/ai-hub.js right when the sort mode changes (recent /
   popular / oldest) — a short fade + a few pixels of vertical motion so
   the re-sorted list doesn't just snap, without ever scrolling the page
   itself (the toolbar and scroll position both stay put). */
.ai-hub-grid.is-resorting {
  opacity: 0;
  transform: translateY(6px);
}

@media (prefers-reduced-motion: reduce) {
  .ai-hub-grid,
  .ai-hub-sortgroup__btn,
  .ai-hub-identity,
  .ai-hub-identity__cta {
    transition: none;
  }

  .ai-hub-identity::after {
    animation: none;
  }

  .ai-hub-identity:hover,
  .ai-hub-identity__cta:hover {
    transform: none;
  }
}

/* 2 columns from tablet width up, 3 from desktop (1024px) where the intro
   column narrows to 360px (see .page-split--ai-hub above) and hands the
   post grid enough room for a 3rd column without cramping each card. Below
   1024px the intro is still the page-split default width, so 3 columns
   would be too narrow there. PAGE_SIZE in js/ai-hub.js is 6, so this is
   also what makes the initial "page" read as 2 full rows of 3 before
   "Afficher plus", instead of 3 rows of 2. */
@media (min-width: 640px) {
  .ai-hub-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .ai-hub-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Card ------------------------------------------------------------------
   No per-card identity block (removed — see .ai-hub-identity above). Order:
   media (if any) first, full width, straight from the top of the card, then
   date, excerpt, stats, link — see pages/ai-hub.njk / js/ai-hub.js. Same
   dark-glass recipe as the identity block above. */

.ai-hub-card {
  display: flex;
  flex-direction: column;
  background: var(--li-card-bg);
  backdrop-filter: var(--li-card-blur);
  -webkit-backdrop-filter: var(--li-card-blur);
  border: 1px solid var(--li-border);
  border-radius: var(--li-radius);
  box-shadow: var(--li-shadow);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

.ai-hub-card:hover {
  transform: translateY(-2px);
  border-color: var(--li-border-hover);
  box-shadow: var(--li-shadow-hover);
}

.ai-hub-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 14px 18px 18px;
}

/* Media — full card width, straight from the top edge (no padding above
   it), a consistent height across every card regardless of the source
   image's own aspect ratio (object-fit: contain keeps a vertical image or a
   carousel page uncropped and undistorted, letterboxed on a dark, neutral
   tint — never white — rather than stretched or cropped). Only ever
   rendered when a real, already-cached image exists — see js/ai-hub.js /
   pages/ai-hub.njk, no empty placeholder container is ever created. */
.ai-hub-card__media {
  width: 100%;
  height: 240px;
  overflow: hidden;
  background: var(--li-media-bg);
  flex-shrink: 0;
}

.ai-hub-card__media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.ai-hub-card__date {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-satoshi);
  font-weight: 500;
  font-size: 12px;
  color: var(--li-text-secondary);
  margin: 0;
}

.ai-hub-card__public-icon {
  opacity: 0.7;
}

.ai-hub-card__doc-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-satoshi);
  font-weight: 600;
  font-size: 13px;
  color: var(--li-text);
  margin: 8px 0 0;
}

/* Exactly 3 lines, hard-cropped — NOT `-webkit-line-clamp`, which always
   renders its own automatic "…" at the cut point with no property to turn
   that off while keeping the clamp itself. A plain height cap (3 lines'
   worth, via line-height) + overflow:hidden crops at the same spot without
   that trailing mark. NOT flex:1 on this box — that stretches it to fill
   the remaining column height instead of its own 3-line cap, which would
   either show more/less than 3 lines depending on content;
   .ai-hub-card__footer's own margin-top:auto pushes the footer down
   instead. */
.ai-hub-card__excerpt {
  font-family: var(--font-satoshi);
  font-weight: 400;
  font-size: 14px;
  line-height: 1.4;
  color: var(--li-text);
  margin: 8px 0 0;
  white-space: pre-line;
  max-height: calc(1.4em * 3);
  overflow: hidden;
}

.ai-hub-card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: auto;
  padding-top: 14px;
}

/* Une seule ligne compacte, mais qui ne pousse plus « Voir sur LinkedIn »
   hors de la carte : le passage de deux à quatre compteurs (l'ajout des
   impressions par la Community Management API) tronquait le lien sur
   TOUTES les cartes — constaté en capture, pas en relecture.

   min-width: 0 autorise la piste flex à se réduire sous la largeur de son
   contenu ; flex-wrap laisse les compteurs passer à la ligne sur une carte
   étroite plutôt que déborder. Le lien, lui, ne rétrécit jamais. */
.ai-hub-card__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  min-width: 0;
  font-family: var(--font-satoshi);
  font-size: 13px;
  color: var(--li-text-secondary);
}

.ai-hub-card__stat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  /* Un compteur ne se coupe jamais entre son icône et son nombre. */
  white-space: nowrap;
  transition: opacity 0.15s ease;
}

/* Set briefly by js/ai-hub.js right when a background stats refresh
   patches a number in, so the update is visible rather than a silent
   value swap. */
.ai-hub-card__stat.is-updating {
  opacity: 0.4;
}

.ai-hub-card__stat--repost-unknown {
  cursor: help;
}

.ai-hub-card__heart {
  flex-shrink: 0;
}

.ai-hub-card__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Ne rétrécit pas : c'est l'appel à l'action de la carte. Ce sont les
     compteurs qui cèdent la place. */
  flex-shrink: 0;
  font-family: var(--font-satoshi);
  font-size: 13px;
  font-weight: 600;
  color: var(--li-blue);
  white-space: nowrap;
  transition: color 0.15s ease;
}

.ai-hub-card:hover .ai-hub-card__link {
  color: var(--li-blue-hover);
}

/* Load more ---------------------------------------------------------------- */

/* A plain [hidden] selector alone loses to this rule's own `display` (an
   author display: value always overrides the UA's [hidden] default,
   regardless of selector specificity) — confirmed directly: without this,
   the button stayed visible even after js/ai-hub.js set
   loadMoreWrap.hidden = true once every post was shown. Same fix already
   applied once before for .ai-hub-consent earlier in this project. */
.ai-hub-loadmore[hidden] {
  display: none;
}

.ai-hub-loadmore {
  display: flex;
  justify-content: center;
  margin-top: 36px;
}

/* No color override here anymore — reuses .btn's own default IAcine Tech
   gradient (button.css), the same as every other CTA on the site, per
   explicit direction not to introduce a one-off button style just for
   this page. */
.ai-hub-loadmore button {
  width: auto;
  padding: 0 28px;
}

.ai-hub-empty {
  text-align: center;
  font-family: var(--font-satoshi);
  /* On the dark page background, same reasoning as .ai-hub-sort above. */
  color: var(--color-muted-foreground);
  max-width: 520px;
  margin: 0 auto;
}
