/* =========================================================================
   Feedler — website shared styles
   Palette mirrors the app's built-in Dark theme (PredefinedThemes.swift).
   Reused across every page (cover, FAQ, contact, privacy).
   ========================================================================= */

:root {
  /* The site is dark by design. Declaring the scheme makes the browser render
     UA bits (scrollbars, form controls) dark and signals the dark theme to
     well-behaved tools. */
  color-scheme: dark;

  /* Dark theme tokens (1:1 with the app) */
  --bg:              #202020;
  --bg-secondary:    #363636;
  --bg-tertiary:     #444444;
  --text-primary:    #E1E1E1;
  --text-body:       #C0C0C0;  /* readable body copy (matches the app reader) */
  --text-secondary:  #888888;
  --text-tertiary:   #555555;
  --accent:          #F1745E;
  --separator:       #444444;

  /* Derived */
  --accent-glow:     rgba(241, 116, 94, 0.18);
  --card:            rgba(54, 54, 54, 0.55);
  --border:          rgba(255, 255, 255, 0.08);

  --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
          "Helvetica Neue", Helvetica, Arial, sans-serif;

  --maxw: 760px;
  --radius: 18px;
}

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  background-color: var(--bg); /* avoids a flash during page transitions */
}

body {
  margin: 0;
  min-height: 100vh;   /* fallback */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  font-family: var(--font);
  color: var(--text-primary);
  background-color: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  line-height: 1.6;
}



a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---- Page transitions --------------------------------------------------- */
/* Reliable cross-browser transition (JS-driven, see site.js): the page blurs
   in on load and blurs out before navigating to another internal page. Works
   in every browser, unlike the View Transitions API. */
@media (prefers-reduced-motion: no-preference) {
  body { animation: page-enter 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
  body.is-leaving { animation: page-leave 0.24s ease both; }

  /* Inner pages (those with <main class="page">) don't blur-fade as a whole;
     instead their content slides slightly up and fades in. The homepage
     (<main class="cover">) keeps the page-enter blur-fade above untouched. */
  body:has(main.page) { animation: none; }
  body:has(main.page).is-leaving { animation: page-leave 0.24s ease both; }
  /* Fade only — NO transform. A transform (even a transient/animating one) on
     `.page` makes it the containing block for `position: fixed` descendants (the
     screenshot lightbox), so the lightbox would size/centre against the page
     column + document height instead of the viewport — badly broken when the
     page is scrolled. Opacity doesn't create a containing block, so the entry
     fade is safe. (Was `page-rise` with a translateY that broke this.) */
  body:has(main.page) .page {
    animation: page-fade 0.5s cubic-bezier(0.16, 1, 0.3, 1) backwards;
  }
}
@keyframes page-enter {
  from { opacity: 0; filter: blur(7px); }
  to   { opacity: 1; filter: blur(0); }
}
@keyframes page-leave {
  from { opacity: 1; filter: blur(0); transform: translateY(0); }
  to   { opacity: 0; filter: blur(5px); transform: translateY(-8px); }
}
@keyframes page-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ---- Cover / hero ------------------------------------------------------- */

.cover {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 48px 24px;
  gap: 0;
}

.cover__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.logo {
  width: 140px;
  height: auto;
  animation:
    rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both,
    logo-float 4.5s ease-in-out 0.9s infinite;
}

@keyframes logo-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-8px); }
}

@media (prefers-reduced-motion: reduce) {
  .logo { animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
}

.wordmark {
  margin: 26px 0 0;
  font-size: clamp(2.4rem, 6vw, 3.4rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 0.12s both;
}

.tagline {
  margin: 10px 0 0;
  font-size: clamp(0.85rem, 2.2vw, 1rem);
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-secondary);
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 0.24s both;
}

/* ---- Coming soon + App Store badge ------------------------------------- */

.coming-soon {
  margin-top: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 0.38s both;
}

.coming-soon__eyebrow {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
}
/* Its TestFlight sibling, in the icon's blue — extra air above so the
   TestFlight block reads as its own section, not a caption of the pill
   above. */
.coming-soon__eyebrow--testflight {
  color: #2eaef8;
  margin-top: 18px;
}

/* App Store badge — styled like Apple's pill, marked "Coming soon". */
.appstore-badge {
  display: inline-flex;
  align-items: center;
  /* Left-anchored content (not per-pill centering): with the fixed pill
     size and the shared icon slot below, both pills' icons AND texts start
     at the same x. */
  justify-content: flex-start;
  gap: 12px;
  /* Fixed size so the stacked App Store + TestFlight pills read as one
     aligned block (content-driven sizes differed: ~10px width, and the
     TestFlight fan being taller than the Apple glyph stretched its pill). */
  width: 190px;
  height: 59px;
  padding: 12px 20px;
  border-radius: 14px;
  background: #000;
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #fff;
  cursor: default;
  user-select: none;
}

.appstore-badge__apple { width: 26px; height: 26px; flex: none; fill: #fff; }
/* Shared 33px icon slot: the Apple glyph (26px) centers inside it, the
   TestFlight fan (33px) fills it — so the text column starts at the same x
   in both pills. */
.appstore-badge .appstore-badge__apple { margin-inline: 3.5px; }
.appstore-badge--testflight .appstore-badge__apple { margin-inline: 0; }

.appstore-badge__text { display: flex; flex-direction: column; line-height: 1.1; text-align: left; }
.appstore-badge__small { font-size: 0.62rem; letter-spacing: 0.02em; opacity: 0.85; }
.appstore-badge__big   { font-size: 1.25rem; font-weight: 600; letter-spacing: -0.01em; }

/* A badge that IS a link (the TestFlight pill now; the App Store pill once
   config flips it live): same look, plus the site's lift-on-hover affordance. */
a.appstore-badge {
  cursor: pointer;
  text-decoration: none;
  transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.25, 1), border-color 0.2s ease;
}
a.appstore-badge:hover {
  transform: translateY(-2px);
  border-color: rgba(255, 255, 255, 0.45);
}
/* The CTA can be hidden remotely (admin "Show TestFlight button"): our
   display rules outrank the UA's [hidden] — restate it. */
.appstore-badge[hidden],
.coming-soon__eyebrow[hidden] { display: none; }
/* TestFlight pill — carries the TestFlight icon's identity (cadrage
   Matthieu): its blue gradient AND the icon's subtle white blueprint grid,
   layered as two hairline repeating-gradients over the base gradient,
   offset so no line merges with the pill border. */
.appstore-badge--testflight {
  background:
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.12) 0 1px, transparent 1px 38px),
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.12) 0 1px, transparent 1px 38px),
    linear-gradient(180deg, #2eaef8, #0b63d8);
  background-position: 18px 0, 0 10px, 0 0;
  border-color: rgba(255, 255, 255, 0.28);
}
/* The fan reads too small at the shared 26px — the real icon's fan fills
   most of its tile. */
.appstore-badge--testflight .appstore-badge__apple { width: 33px; height: 33px; }
a.appstore-badge--testflight:hover {
  border-color: rgba(255, 255, 255, 0.5);
  filter: brightness(1.07);
}

/* ---- Header (content pages) -------------------------------------------- */

.site-header {
  display: flex;
  align-items: center;
  padding: 18px 24px;
  border-bottom: 1px solid var(--border);
}

.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--text-primary);
  font-weight: 500;
  font-size: 1.05rem;
}
.site-header__brand:hover { text-decoration: none; opacity: 0.85; }
.site-header__brand img {
  width: 30px;
  height: auto;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.4));
}

/* ---- Content page (FAQ / Contact / Privacy) ---------------------------- */

.page {
  flex: 1 0 auto;
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 48px 24px 32px;
}

.page h1 {
  margin: 0 0 6px;
  font-size: clamp(1.9rem, 5vw, 2.6rem);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.page__updated {
  margin: 0 0 36px;
  font-size: 0.9rem;
  color: var(--text-secondary);
}

.page h2 {
  margin: 38px 0 10px;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--text-primary);
}

.page p,
.page li {
  font-size: 1rem;
  line-height: 1.75;
  color: var(--text-body);
}

.page ul { padding-left: 22px; margin: 10px 0; }
.page li { margin: 6px 0; }
.page strong { color: var(--text-primary); font-weight: 600; }

/* ---- FAQ accordion ------------------------------------------------------ */

/* ── FAQ search — prominent pill that filters questions by the typed words ── */
.faq-search {
  position: relative;
  margin: 26px 0 36px; /* extra breathing room below so it reads as its own block */
}
.faq-search__icon {
  position: absolute;
  left: 20px; top: 50%;
  transform: translateY(-50%);
  width: 19px; height: 19px;
  color: var(--text-secondary);
  pointer-events: none;
  transition: color 0.18s ease;
}
.faq-search:focus-within .faq-search__icon { color: var(--accent); }
.faq-search__input {
  width: 100%;
  box-sizing: border-box;
  padding: 16px 52px;
  font: inherit;
  font-size: 1.05rem;
  color: var(--text-primary);
  /* Lighter fill than the cards + a soft elevation shadow so the bar floats
     above the list and clearly stands out. */
  background: var(--bg-tertiary);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px; /* fully rounded pill */
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.3);
  -webkit-appearance: none;
  appearance: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.faq-search__input::placeholder { color: var(--text-secondary); }
.faq-search__input:focus {
  outline: none;
  border-color: var(--accent);
  background: #4d4d4d;
  box-shadow: 0 0 0 4px var(--accent-glow), 0 12px 30px rgba(0, 0, 0, 0.34);
}
.faq-search__input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.faq-search__clear {
  position: absolute;
  right: 10px; top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border: none; border-radius: 50%;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.faq-search__clear svg { width: 18px; height: 18px; }
.faq-search__clear:hover { color: var(--text-primary); background: rgba(255, 255, 255, 0.08); }
/* `display: inline-flex` above would otherwise override the [hidden] attribute. */
.faq-search__clear[hidden] { display: none; }
/* Hidden state set by JS AFTER the collapse animation finishes. */
.faq__item.is-filtered-out { display: none; }
.faq-noresults {
  text-align: center;
  color: var(--text-secondary);
  margin: 6px 0 0;
  padding: 22px 0;
}
.faq__q mark {
  background: var(--accent-glow);
  color: var(--text-primary);
  border-radius: 3px;
  padding: 0 2px;
}

.faq {
  display: flex;
  flex-direction: column;
  /* No flex `gap` — per-item margin instead, so the JS can animate it to 0 as an
     item collapses out of the filter (gap can't be animated per item). */
  margin-top: 8px;
}
.faq__item { margin-bottom: 10px; }
.faq__item:last-child { margin-bottom: 0; }

/* Section headers between questions — discreet uppercase labels that group
   the FAQ. A small coral dot + a hairline fading out keep it airy (no heavy
   divider). Hidden while a search filters out every question they own. */
.faq__section {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 46px 2px 14px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.faq__section::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
.faq__section::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, rgba(255, 255, 255, 0.12), transparent);
}
.faq__section:first-child { margin-top: 6px; }
.faq__section.is-filtered-out { display: none; }

.faq__item {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--card);
  overflow: hidden;
  transition: border-color 0.35s ease;
}
/* The open card frames itself in faint coral so it stands out from the rest. */
.faq__item--open { border-color: rgba(241, 116, 94, 0.4); }

.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 18px;
  cursor: pointer;
  font-size: 1.02rem;
  font-weight: 500;
  color: var(--text-primary);
  list-style: none;
  /* Don't let a click/double-click select the question text. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.25s ease;
}
.faq__item summary::-webkit-details-marker { display: none; }
/* Subtle hover — the row just lightens (no moving text). Disabled once the item
   is open: the coral title + border are the highlight then. */
.faq__item:not(.faq__item--open) summary:hover { background-color: rgba(255, 255, 255, 0.045); }

.faq__q {
  flex: 1;
  transition: color 0.35s ease;
}
/* Open question fades to the coral accent (animated, not an ON/OFF switch) to
   highlight which answer is showing. */
.faq__item--open .faq__q { color: var(--accent); }

/* Leave breathing room (and clear any future sticky header) when a deep-linked
   question is scrolled to the top via ?q=<slug>. */
.faq__item { scroll-margin-top: 84px; }

/* Discreet "copy link to this question" button, injected by JS into each
   header. Faint by default; on hover-capable devices it only appears when the
   row is hovered, so it never competes with the question. On touch (no hover)
   it stays gently visible so it's still discoverable. */
.faq__copy {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin: -6px 0;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-secondary);
  opacity: 0.55;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 0.2s ease, color 0.2s ease, background-color 0.2s ease,
    transform 0.2s ease;
}
.faq__copy svg { width: 16px; height: 16px; display: block; }
.faq__copy:hover { color: var(--accent); background: rgba(241, 116, 94, 0.12); }
.faq__copy:active { transform: scale(0.9); }
/* Copied confirmation — checkmark flips to coral briefly. */
.faq__copy--done { color: var(--accent); opacity: 1; }
@media (hover: hover) {
  .faq__copy { opacity: 0; }
  .faq__item summary:hover .faq__copy,
  .faq__copy:focus-visible,
  .faq__copy--done { opacity: 0.7; }
  .faq__item summary:hover .faq__copy:hover { opacity: 1; }
}

/* Brief highlight pulse on the question opened via a shared ?q=<slug> link, so
   it's instantly clear which one the link pointed to. */
@keyframes faqFlash {
  0%   { box-shadow: 0 0 0 0 rgba(241, 116, 94, 0); }
  18%  { box-shadow: 0 0 0 4px rgba(241, 116, 94, 0.35); }
  100% { box-shadow: 0 0 0 0 rgba(241, 116, 94, 0); }
}
.faq__item--flash { animation: faqFlash 1.5s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .faq__item,
  .faq__item summary,
  .faq__q { transition: none; }
  .faq__copy { transition: none; }
  .faq__item--flash { animation: none; }
}

.faq__chevron {
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--accent);
  /* Bouncy rotate (ease-out-back overshoot) to match the expand. */
  transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.faq__item--open .faq__chevron { transform: rotate(180deg); }

/* Outer wrapper is what JS animates (height 0 ↔ content), so it clips. */
.faq__answer { overflow: hidden; }

.faq__answer-inner {
  padding: 4px 18px 18px;
  color: var(--text-body);
  line-height: 1.75;
}
.faq__answer-inner a { color: var(--accent); }

/* Inline app-navigation path, e.g. "Settings › Article". Styled like the
   reader's inline <code> token: a neutral tinted box with a hairline
   border and monospace type, so a path reads as a distinct UI string that
   stands clearly apart from the prose — without a loud coloured fill.
   Left on the default `box-decoration-break: slice` (as the reader's
   <code> is) so a long path that wraps reads as one continuous box across
   the break rather than two stacked chips. Never breaks mid-label: every
   space that could break is a real space only *after* each separator,
   while labels and the "label ›" pair are glued with &nbsp; — so the sole
   wrap opportunity is right after a ›. On full-width screens the path
   stays on one line (`nowrap` suppresses those spaces); the mobile block
   below (≤700px) drops the nowrap so a path too wide for the narrow flex
   column breaks cleanly after a › (never inside "Manage Account") instead
   of overflowing. (A real space is used rather than <wbr>, which some
   engines still break at under `nowrap`.) Reusable: wrap the path in
   `.path`, write each separator as `&nbsp;<span>›</span> ` (nbsp before,
   real space after), and join multi-word labels with &nbsp;. */
.path {
  padding: 1px 6px;
  border-radius: 6px;
  background: var(--bg-tertiary);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--text-primary);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
  font-style: normal;
  white-space: nowrap;
}
.path > span {
  color: var(--text-secondary);
}

/* Real, airy lists (e.g. the providers) with coral markers. */
.faq__answer-inner ul {
  margin: 14px 0 4px;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.faq__answer-inner li { margin: 0; }
.faq__answer-inner li::marker { color: var(--accent); }

/* Group labels (e.g. Web-hosted / Self-hosted) above each services list. */
.faq__services-label {
  margin: 18px 0 8px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
}
.faq__services-label + ul { margin-top: 0; }

/* A sub-question heading inside an answer (e.g. "Why isn't Feedly supported?"). */
.faq__subq {
  margin: 22px 0 6px;
  font-weight: 600;
  color: var(--text-primary);
}

/* "Why choose Feedler?" — an icon list (more striking than plain bullets). */
.faq__answer-inner .why {
  list-style: none;
  padding-left: 0;
  margin: 20px 0 8px;
  gap: 16px;
}
.faq__answer-inner .why > li {
  display: flex;
  gap: 14px;
  align-items: center;
  margin: 0;
}
.why__icon {
  flex: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(241, 116, 94, 0.13);
  color: var(--accent);
}
.why__icon svg { width: 21px; height: 21px; display: block; }
.why__body strong { color: var(--text-primary); }
/* Two-way sub-list inside a "why" step (e.g. the OPML import options). The step
   gets taller, so top-align its icon instead of centring it. */
.why__ways { margin: 8px 0 4px; padding-left: 18px; list-style-type: disc; }
.why__ways li { margin: 4px 0; }
.faq__answer-inner .why > li:has(.why__ways) { align-items: flex-start; }
/* Closing line under the list — same breathing room as above the list. */
.faq__answer-inner .why__outro { margin: 20px 0 0; }

/* Provider list — official logo (in brand colours) in front of each name. */
.faq__answer-inner .providers {
  list-style: none;
  padding-left: 14px;
  margin: 12px 0 4px;
  gap: 11px;
}
.faq__answer-inner .providers > li { margin: 0; }
.faq__answer-inner .providers a {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  color: var(--text-primary);   /* white name, not the link accent */
}
.faq__answer-inner .providers a:hover { text-decoration: none; }
.provider-logo {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 5px;
}

/* Provider name: same sliding underline as the footer menu links. */
.provider-name { position: relative; }
.provider-name::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
}
.providers a:hover .provider-name::after { transform: scaleX(1); }

/* ---- FAQ screenshots + CSS-only lightbox (tap to zoom) ------------------ */

/* A row of tappable phone screenshots inside an answer. Wraps on narrow
   screens; the whole thing is pure CSS — a hidden checkbox per shot drives
   the lightbox, so open/close animate with no JavaScript. */
.faq__answer-inner .shots,
.release__details-body .shots {
  display: flex;
  justify-content: center;
  /* Two-shot rows (e.g. the Activity Log pair) sit side by side and
     share the width; the gap keeps single-shot blocks untouched.
     Wrapping lets 3+ shots flow onto centred rows (2+1) instead of
     squeezing — grouped-at-section-end with naming captions is the
     chosen pattern (no carousels — cadrage Matthieu 1ᵉʳ août 2026). */
  flex-wrap: wrap;
  gap: 14px;
  margin: 18px 0 22px;
}
.faq__answer-inner .shots .shot,
.release__details-body .shots .shot {
  margin: 0;
  width: 100%;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
/* The release card's inner width (~632px: page column − timeline gutter −
   card padding) can't seat two 320px shots + gap — cap changelog shots at
   300px so pairs sit side by side and a third wraps to a centred row. */
.release__details-body .shots .shot { max-width: 300px; }
/* A hero shot (e.g. the iPad three-column layout) spans the card's width. */
.release__details-body .shots .shot--full { max-width: 100%; }
.shot figcaption {
  font-size: 0.82rem;
  line-height: 1.4;
  text-align: center;
  color: var(--text-secondary);
}

/* Thumbnail — a framed card that lifts on hover, with a zoom badge. It's an
   <a> (opens the GLightbox viewer), so kill the link underline/colour. */
.shot__thumb {
  position: relative;
  display: block;
  cursor: zoom-in;
  text-decoration: none;
  /* Fixed frame → identical height for every shot. Since all the screenshots
     share one width (always an iPhone), the image fills the width at its NATURAL
     scale (no zoom) and the frame simply crops the bottom. The lightbox shows
     the whole, uncropped screenshot. */
  aspect-ratio: 7 / 8;
  border-radius: 18px;
  overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
  transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.25, 1), box-shadow 0.25s ease;
}
.shot__thumb img { display: block; width: 100%; height: auto; }
/* A landscape (shorter) screenshot: the fixed 7/8 portrait frame would leave
   empty space below it. Match the frame to the image's own aspect instead, so it
   fills exactly — no gap, no crop, and the space stays reserved (no lazy-load
   layout shift). Set to each wide image's ratio. */
.shot__thumb--wide { aspect-ratio: 1206 / 903; }
.shot__thumb--square { aspect-ratio: 1 / 1; }
/* Text-band excerpts (e.g. the Reading Assist mode strips): frame matches
   the image exactly, like --wide/--square. */
.shot__thumb--strip { aspect-ratio: 920 / 279; }
/* iPad landscape screenshots (2420×1668): same full-image rule. */
.shot__thumb--pad { aspect-ratio: 2420 / 1668; }
/* Partial-height sheet excerpts (e.g. the Add-a-feed panel, 1206×1394). */
.shot__thumb--sheet { aspect-ratio: 1206 / 1394; }
/* Soft fade where the bottom is cropped, so the cut dissolves into the dark
   instead of ending on a hard edge. */
.shot__thumb::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 30%;
  background: linear-gradient(to bottom, rgba(32, 32, 32, 0), var(--bg));
  pointer-events: none;
}
/* The fade SIGNALS a crop. Frames sized to their image's own aspect
   (--wide/--square/--strip) show the whole picture — no fade on them
   (règle 1ᵉʳ août 2026: un fade sur une image entière ment à l'œil). */
.shot__thumb--wide::after,
.shot__thumb--square::after,
.shot__thumb--strip::after,
.shot__thumb--pad::after,
.shot__thumb--sheet::after { display: none; }
/* Hover affordances ONLY for real pointing devices. On touch, a :hover rule
   makes the FIRST tap paint the hover state instead of firing the click (iOS
   "sticky hover") → the user has to tap twice to open. Gating with
   `@media (hover: hover)` keeps the lift/reveal on desktop and makes the first
   tap open on mobile. */
@media (hover: hover) {
  .shot__thumb:hover { transform: translateY(-4px); box-shadow: 0 16px 38px rgba(0, 0, 0, 0.42); }
}
/* Frosted-glass circular zoom badge on the thumbnail. Low-alpha fill so what's
   behind blurs through, plus a hairline highlight border — like the app's glass
   controls, not a flat disc. */
.shot__zoom {
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: #fff;
  background: rgba(40, 40, 42, 0.35);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.22);
}

/* Zoom badge — bottom-right of the thumbnail, revealed on hover. */
.shot__zoom {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 2;                     /* above the bottom fade */
  width: 34px;
  height: 34px;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
@media (hover: hover) {
  .shot__thumb:hover .shot__zoom { opacity: 1; transform: scale(1); }
}
/* Touch: no hover to reveal it → show the badge permanently as the tap affordance. */
@media (hover: none) {
  .shot__zoom { opacity: 1; transform: scale(1); }
}
.shot__zoom svg { width: 17px; height: 17px; }

@media (prefers-reduced-motion: reduce) {
  .shot__thumb,
  .shot__zoom { transition: none; }
}

/* ---- Contact cards ------------------------------------------------------ */

.contact-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 30px 0 8px;
}

.contact-card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 20px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--card);
  color: var(--text-primary);
  transition:
    transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
    border-color 0.2s ease,
    background 0.2s ease;
}
.contact-card:hover {
  text-decoration: none;
  transform: translateY(-3px);
  border-color: rgba(241, 116, 94, 0.45);
  background: rgba(54, 54, 54, 0.72);
}

.contact-card__icon {
  flex: none;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(241, 116, 94, 0.14);
  color: var(--accent);
}
.contact-card__icon svg { width: 22px; height: 22px; }

.contact-card__text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.contact-card__label {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.contact-card__value {
  font-size: 1.05rem;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.contact-card__arrow {
  flex: none;
  color: var(--text-secondary);
  transition:
    transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
    color 0.2s ease;
}
.contact-card__arrow svg { width: 18px; height: 18px; display: block; }
.contact-card:hover .contact-card__arrow {
  transform: translateX(4px);
  color: var(--accent);
}

/* ---- Footer (shared) ---------------------------------------------------- */

.site-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 36px 24px 44px;
  text-align: center;
  font-size: 0.9rem;
  color: var(--text-secondary);
}

.site-footer__nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 24px;
  font-size: 0.95rem;
}
.site-footer__nav a {
  position: relative;
  color: var(--text-body);
  transition: color 0.2s ease;
}
/* Modern sliding underline that grows from the left on hover. */
.site-footer__nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
}
.site-footer__nav a:hover { color: var(--text-primary); text-decoration: none; }
.site-footer__nav a:hover::after { transform: scaleX(1); }

/* Social links (Bluesky, Mastodon) — discreet muted glyphs, side by side,
   tinting coral on hover. */
.site-footer__elsewhere {
  display: inline-flex;
  align-items: center;
  gap: 16px;
}
.site-footer__elsewhere a {
  display: inline-flex;
  color: var(--text-body);
  transition: color 0.2s ease;
}
.site-footer__elsewhere a:hover { color: var(--accent); }
.site-footer__elsewhere svg { width: 18px; height: 18px; }

.site-footer__copy { margin: 0; font-size: 0.85rem; color: var(--text-secondary); }

/* ---- Animations --------------------------------------------------------- */

@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .logo, .wordmark, .tagline, .coming-soon, .site-footer { animation: none; }
}

/* ============================================================================
   GLightbox reskin → the old custom-lightbox look (frosted glass discs, rounded
   image, italic caption, "n / total" pill, Lucide chevrons, blurred backdrop).
   style.css loads AFTER glightbox.min.css, so these win at equal specificity.
   ============================================================================ */

/* Blurred, dark backdrop (0.86 alpha + blur 6, like the app's glass). */
.glightbox-container .goverlay {
  background: rgba(16, 16, 16, 0.86);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

/* Rounded image with a hairline outline + soft shadow. */
.glightbox-clean .gslide-image img {
  border-radius: 16px;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06), 0 14px 40px rgba(0, 0, 0, 0.45);
}
/* Kill GLightbox's OWN drop-shadow on the media container — we shadow the image
   itself (above) — AND cap the container to the same width as the (capped) image.
   GLightbox sizes the container to the UN-capped fit (e.g. 868px for a landscape
   shot); left alone it shows a ghost shadow "outline" at that old width, and the
   invisible strip beside the narrower image still counts as the media so a tap
   there won't close the lightbox. Matching it to the image makes that strip
   backdrop again (tap-to-close works). Portrait shots are already this narrow. */
.glightbox-clean .gslide-media,
.glightbox-clean .gslide-image {
  box-shadow: none;
  max-width: min(100%, 560px) !important;
}
/* GLightbox's tap-to-close (closeOnOutsideClick) treats any click INSIDE
   `.ginner-container` as "keep open" — but it makes that container FULL-WIDTH, so
   the empty area beside a narrower image is a dead zone (tap doesn't close).
   Shrink it to wrap the media so ONLY the image itself keeps the lightbox open. */
.glightbox-clean .ginner-container {
  width: -moz-fit-content !important;
  width: fit-content !important;
  max-width: min(100%, 560px) !important;
}
/* Landscape media (class set by the changelog's lightbox init from each
   image's NATURAL ratio): the 560px portrait cap above would display them
   SMALLER than the page thumbnail (bug Matthieu 1ᵉʳ août 2026 — the iPad
   hero opened at 560×386 vs a 632px-wide preview). Widen their stage; the
   3-class specificity beats the 2-class cap regardless of order. */
.glightbox-clean .gslide--landscape .gslide-media,
.glightbox-clean .gslide--landscape .gslide-image,
.glightbox-clean .gslide--landscape .ginner-container {
  max-width: min(100%, 1080px) !important;
}

/* Frosted circular buttons — close + arrows (the app's glass-control recipe). */
.glightbox-clean .gclose,
.glightbox-clean .gnext,
.glightbox-clean .gprev {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(40, 40, 42, 0.35);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.22);
  opacity: 1;
  transition: background-color 0.2s ease, transform 0.2s ease;
}
.glightbox-clean .gclose { top: 16px; right: 16px; }
.glightbox-clean .gclose:hover,
.glightbox-clean .gnext:hover,
.glightbox-clean .gprev:hover {
  background: rgba(62, 62, 64, 0.5);
  opacity: 1;
}
.glightbox-clean .gclose:active,
.glightbox-clean .gnext:active,
.glightbox-clean .gprev:active { transform: scale(0.9); }

/* Our opacity:1 above would otherwise reveal arrows GLightbox has disabled
   (a single-image answer, or an unreachable direction) as fully-opaque but dead
   buttons. Hide them outright instead. */
.glightbox-clean .gnext.disabled,
.glightbox-clean .gprev.disabled,
.glightbox-clean .glightbox-button-hidden { display: none !important; }

/* Our injected Lucide glyphs — stroked, centred. GLightbox ships
   `.glightbox-clean .gnext path { fill:#fff }`, which (CSS beats the SVG's own
   `fill="none"` presentation attr) fills our chevrons solid → must override the
   PATH, not just the svg. */
.glightbox-clean .gclose svg,
.glightbox-clean .gnext svg,
.glightbox-clean .gprev svg {
  width: 22px;
  height: 22px;
}
.glightbox-clean .gclose svg path,
.glightbox-clean .gnext svg path,
.glightbox-clean .gprev svg path {
  fill: none;
  stroke: #fff;
}

/* Caption: hide GLightbox's own in-flow description (white box, awkward layout)
   and render our OWN fixed italic caption above the counter pill (like the old
   lightbox) — updated in JS from the current slide. Cap the image height so it
   never reaches down into the caption + pill band. */
.glightbox-clean .gslide-description { display: none; }
/* GLightbox sizes the image with an INLINE `max-height: calc(100vh - Npx)` where
   N is its (now hidden) description's height = 0, so the image would fill the
   full height and sit under our caption + pill. !important beats that inline. */
.glightbox-clean .gslide-image img {
  max-height: calc(100vh - 150px) !important;
  /* Also cap the WIDTH so a landscape/short screenshot doesn't blow up on wide
     screens (its height cap never binds). Portrait shots are height-limited and
     stay far narrower than this, so they're unaffected. */
  max-width: min(100%, 560px) !important;
}
/* Landscape slides (`.gslide--landscape`, tagged by the changelog's init from
   the image's natural ratio) get the wider stage — this img cap is the third
   place the 560px limit lives, and the one that actually binds. */
.glightbox-clean .gslide--landscape .gslide-image img {
  max-width: min(100%, 1080px) !important;
}
.feedler-caption {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 56px;
  padding: 0 20px;
  text-align: center;
  font-size: 0.85rem;
  font-style: italic;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.72);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  z-index: 1000000;
}

/* "n / total" frosted pill, bottom-centre (injected in JS as .feedler-counter). */
.feedler-counter {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.92);
  background: rgba(40, 40, 42, 0.35);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.18);
  z-index: 1000000;
  pointer-events: none;
}

/* Fade our caption + pill OUT with the close — otherwise they stay fully solid
   and then pop at the very END of the close (they aren't part of GLightbox's own
   fade). GLightbox adds `.glightbox-closing` to the container while closing (its
   fade is `gfade* 0.5s ease`). Transition-only (no keyframe animation, which left
   them stuck at opacity 0 on open); they show instantly with the content. */
.feedler-caption,
.feedler-counter { transition: opacity 0.4s ease; }
.glightbox-container.glightbox-closing .feedler-caption,
.glightbox-container.glightbox-closing .feedler-counter { opacity: 0; }

/* Phones: no gutter for side arrows over the image → hide; swipe + pill carry
   nav (exactly the old lightbox behaviour). */
@media (max-width: 700px) {
  .glightbox-clean .gnext,
  .glightbox-clean .gprev { display: none; }
}

/* Phones: let a path that can't fit the narrow flex column wrap at its
   separators (the <wbr> points) instead of overflowing. Labels stay glued
   by &nbsp;, so it only ever breaks at a ›, never mid-label. */
@media (max-width: 700px) {
  .path { white-space: normal; }
}

/* ---- Changelog timeline ------------------------------------------------- */
/* Vertical rail + release cards. The rail is a soft gradient line; each
   release is a node (accent dot) + a card that reveals on scroll
   (IntersectionObserver adds .is-visible — inline script in changelog.html).
   The full notes expand with the modern grid-rows 0fr→1fr technique: smooth
   auto-height without any JS measurement. */

.timeline {
  position: relative;
  margin-top: 10px;
  padding-left: 34px;
}
.timeline::before {
  content: "";
  position: absolute;
  left: 9px;
  top: 6px;
  bottom: 6px;
  width: 2px;
  border-radius: 1px;
  background: linear-gradient(
    to bottom,
    rgba(241, 116, 94, 0.65),
    rgba(241, 116, 94, 0.22) 30%,
    var(--separator) 75%,
    transparent
  );
}

.release {
  position: relative;
  margin: 0 0 26px;
}
.release:last-child { margin-bottom: 0; }

/* Node dot — sits on the rail, pops in with its card. */
.release::before {
  content: "";
  position: absolute;
  /* Horizontal: rail center = timeline.left + 10 (left 9 + half of 2px);
     padding 34 − 30 + 12/2 lands the dot's center exactly there. */
  left: -30px;
  /* Dot centre = 40px, aligned on the version number's line centre
     (card padding 20 + h2 line-box 39.7/2 ≈ 39.8) — the node sits level
     with the middle of "1.6.1". */
  top: 34px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--bg-tertiary);
  border: 2px solid var(--text-tertiary);
  box-shadow: 0 0 0 4px var(--bg);
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
              background 0.35s ease, border-color 0.35s ease;
}
.release--latest::before {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--bg), 0 0 18px 2px var(--accent-glow);
}

/* Card */
.release__card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  padding: 20px 22px 18px;
  transition: border-color 0.35s ease, box-shadow 0.35s ease;
}
.release--latest .release__card { border-color: rgba(241, 116, 94, 0.35); }
.release__card:hover { border-color: rgba(255, 255, 255, 0.16); }
.release--latest .release__card:hover { border-color: rgba(241, 116, 94, 0.55); }

/* Scroll reveal — cards rise, dots pop. Motion-safe only. */
@media (prefers-reduced-motion: no-preference) {
  .release { opacity: 0; transform: translateY(22px); }
  .release::before { transform: scale(0); }
  .release.is-visible {
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
                transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .release.is-visible::before { transform: scale(1); transition-delay: 0.15s; }
  /* No breathing on the latest node: its animated box-shadow forced a
     CPU repaint every frame that made the whole card's text shimmer on
     iOS (documented Safari bug), and the compositor-safe rewrite was
     judged too loud — the static coral dot + glow is the "you are here". */
}

/* Header row: version + date + Latest pill */
.release__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-bottom: 10px;
}
/* Scoped selector: `.page h2` (38px top margin) would otherwise outrank the
   bare class and push a big empty band above every version number. */
.release .release__version {
  margin: 0;
  font-size: 1.55rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}
.release__date {
  font-size: 0.88rem;
  color: var(--text-secondary);
}
.release__badge {
  align-self: center;
  margin-left: auto;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
  background: linear-gradient(135deg, var(--accent), #e2543c);
  box-shadow: 0 4px 14px var(--accent-glow);
}
/* Transient build-status LINE ("Under Apple review") — deliberately NOT a
   pill (every capsule variant read as a sibling of the Latest badge or the
   area chips): a bare metadata row under the header, deploy-status idiom.
   Config-gated via data/config.js (buildUnderReview + reviewShownUntil). */
/* `.release .release__status` (not the bare class): the page's generic
   paragraph rule outranks a lone class and was clamping color + size. */
.release .release__status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  margin: 18px 0 26px;
  font-size: 1.15rem;
  letter-spacing: 0.02em;
  color: var(--text-primary);
}
/* Our display:flex outranks the UA's [hidden]{display:none} — restate it,
   or the config-gated line would always show. */
.release .release__status[hidden] { display: none; }
/* The Apple glyph from the homepage badge, normal monochrome (recoloring
   the Apple logo is off-limits — cadrage Matthieu). */
.release__status-apple {
  flex: none;
  /* EXACT 3:4 (the glyph's 384×512 viewBox): a mismatched box (e.g.
     17×21) makes the SVG center its content on fractional pixels, which
     reads as horizontal jitter while the line floats. */
  width: 15.75px;
  height: 21px;
  fill: currentColor;
  /* Nudged UP from the flex-centred position: the glyph is taller than
     the text's cap height, so a geometrically centred Apple reads as
     sitting lower than the words next to it (device verdict, 2 août
     2026 — the pane's metrics say "centred", the eye says otherwise).
     em-based so it holds at any font size. */
  position: relative;
  top: -0.1em;
}
/* "In progress" = the WHOLE line floats with the homepage logo's gentle
   rhythm, everywhere. (The text shimmer once blamed on this float was in
   fact the latest node's animated box-shadow repainting the card every
   frame — removed.) */
@media (prefers-reduced-motion: no-preference) {
  .release__status {
    animation: status-float 4.5s ease-in-out infinite;
    will-change: transform;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
  }
}
@keyframes status-float {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -3px, 0); }
}

/* Key points — always visible */
.release__summary {
  list-style: none;
  margin: 0;
  padding: 0;
}
.release__summary li {
  position: relative;
  padding-left: 20px;
  margin: 7px 0;
  font-size: 0.98rem;
  line-height: 1.6;
  color: var(--text-body);
}
.release__summary li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.62em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.85;
}

/* Expand toggle */
.release__toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 14px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.88rem;
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}
.release__toggle:hover {
  border-color: rgba(241, 116, 94, 0.5);
  background: rgba(241, 116, 94, 0.08);
  color: var(--accent);
}
.release__toggle svg {
  width: 15px;
  height: 15px;
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.release--open .release__toggle svg { transform: rotate(180deg); }
.release__toggle-label--open { display: none; }
.release--open .release__toggle-label--closed { display: none; }
.release--open .release__toggle-label--open { display: inline; }

/* Full notes — grid-rows animation (0fr → 1fr) */
.release__details {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}
.release--open .release__details { grid-template-rows: 1fr; }
.release__details-inner {
  overflow: hidden;
  min-height: 0;
}
/* Content fades/rises slightly AFTER the container starts opening. No
   panel, no rail: the expanded notes flow bare in the card — the same
   expansion language as the FAQ answers. The coral section headers do
   the structuring (variants with a left rail and a pill-material panel
   were tried and rejected as visually heavy, 1ᵉʳ août 2026). */
.release__details-body {
  padding-top: 16px;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.release--open .release__details-body {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.12s;
}
.release__details h3 {
  margin: 18px 0 6px;
  font-size: 0.82rem;
  font-weight: 650;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--accent);
}
.release__details h3:first-child { margin-top: 0; }
.release__details ul {
  margin: 0;
  padding-left: 20px;
}
.release__details li {
  margin: 5px 0;
  font-size: 0.94rem;
  line-height: 1.65;
  color: var(--text-body);
}
.release__details li::marker { color: var(--text-tertiary); }
/* Note callout inside the notes ("Note: …") — important info (storage
   migrations, platform caveats) that must be SEEN: a soft tinted block with
   an info glyph. The first cut was an italic aside; Matthieu judged it too
   easy to miss (1ᵉʳ août 2026). */
.release__details .release__note {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 12px 0 4px;
  padding: 11px 13px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--text-secondary);
}
.release__details .release__note svg {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 4px;
  color: var(--accent);
}
.release__details .release__note p { margin: 0; }

/* Small screens: tighten the gutter so cards keep their width. */
@media (max-width: 560px) {
  .timeline { padding-left: 26px; }
  .timeline::before { left: 6px; }
  /* Same alignment rule at the tighter metrics: padding 16 + h2 line-box
     34.6/2 ≈ 33.3 → 10px dot top = 28. Horizontal: rail center sits at
     timeline.left + 7 (left 6 + half of 2px) → dot left -24 puts the 10px
     dot's center there too (was -26: 2px off, visible on mobile). */
  .release::before { left: -24px; width: 10px; height: 10px; top: 29px; }
  .release__card { padding: 16px 16px 15px; }
  /* Match the base rule's specificity (`.release .release__version`) or
     this mobile size never applies. */
  .release .release__version { font-size: 1.35rem; }
  /* The Latest badge keeps its right anchor on mobile too (the old
     margin-left:0 override dated from a design where badges wrapped to
     their own left-aligned line). */
}

/* Area chips — scannable "what changed where" (Linear/Vercel pattern). */
.release__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 12px;
}
.release__chip {
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  font-size: 0.72rem;
  font-weight: 550;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
}
