/* ——— Hero → Projects sticky overlap ———
   --nav-h — высота «схлопнутого» меню, на которой блок кейсов паркуется.
   --ov — путь наезда: блок проходит весь герой и стыкуется с низом меню,
   после чего герой открепляется и страница скроллится обычным образом. */
:root{ --nav-h:62px; --ov:calc(100vh - var(--nav-h)); }
[data-hero-stage]{ position:relative; z-index:0; height:calc(100vh + var(--ov)); }
[data-hero-sec]{
  position:sticky; top:0; z-index:0; height:100vh; min-height:600px; overflow:hidden;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  padding:clamp(88px,11vh,120px) 24px clamp(44px,6vh,68px);
  background:radial-gradient(120% 90% at 50% 4%, #F2F6F7 0%, #EAF6FB 56%, #D9E8ED 100%);
  text-align:center;
}
[data-hero-shader]{ mask-image:radial-gradient(135% 125% at 50% 46%, #000 62%, rgba(0,0,0,0.6) 84%, transparent 100%); -webkit-mask-image:radial-gradient(135% 125% at 50% 46%, #000 62%, rgba(0,0,0,0.6) 84%, transparent 100%); }
/* C: размываемый слой рендерим в половинном линейном разрешении — коробку канваса
   делаем 50% и увеличиваем scale ×2, радиус блюра тоже вдвое. На глаз ≈ прежний
   blur(14px) c transform:scale(1.05) (покрытие 105%, центрировано translate(-50%,-50%)),
   но CSS-фильтр обсчитывает вчетверо меньше device-пикселей за один пересчёт. Меш —
   гладкий градиент без мелких деталей, поэтому падение исходного разрешения на глаз
   не читается. Вместе с троттлингом до 30 к/с (mesh-shader.js) резко снижает GPU-нагрузку. */
[data-hero-shader] canvas{
  position:absolute; top:50%; left:50%; width:50%; height:50%;
  transform:translate(-50%,-50%) scale(2.1);   /* 2 (компенсация box 50%) × 1.05 (прежний оверскан) */
  filter:blur(6.7px);                           /* 6.7 × 2 ≈ прежние 14px */
}
@media (max-width:720px){
  [data-hero-shader] canvas{ filter:blur(5.7px) !important; }   /* 5.7 × 2 ≈ прежние 12px на мобиле */
}

[data-hero-parallax]{ position:relative; z-index:1; width:100%; will-change:transform, opacity; }
[data-hero-canvas]{ display:block; width:min(100%,1600px); height:clamp(320px,60vh,720px); margin:0 auto; }
/* На мобиле надпись занимала лишь треть высоты этой коробки (487px против ~165px
   самого текста) — коробку укорачиваем: буквы становятся крупнее относительно неё,
   а блок с текстом и двумя кнопками поднимается примерно на 100px. Брейкпоинт тот же,
   что у мобильной калибровки частиц в js/home.js — менять их нужно вместе. */
@media (max-width:720px){ [data-hero-canvas]{ height:clamp(220px,36vh,340px); } }
.hero-bottom{ display:flex; flex-direction:column; align-items:center; gap:26px; max-width:600px; margin:0 auto; }
.hero-bottom p{ font-size:clamp(15px,1.5vw,18.5px); line-height:1.6; color:var(--iw-body); }
.hero-bottom p .accent{ color:var(--iw-accent); font-weight:700; }
.hero-buttons{ display:flex; flex-wrap:wrap; gap:14px; justify-content:center; }

[data-projects]{
  position:relative; z-index:2; margin-top:calc(-1 * var(--ov));
  box-shadow:0 -30px 60px -34px rgba(20,37,49,0.22); will-change:transform;
  background:var(--iw-cloud); padding:clamp(72px,11vh,128px) clamp(18px,5vw,72px);
}
@media (max-width:640px){ [data-projects]{ box-shadow:0 -18px 40px -28px rgba(20,37,49,0.20); } }
@media (prefers-reduced-motion: reduce){
  [data-hero-sec]{ position:relative !important; top:auto !important; }
  [data-hero-stage]{ height:auto !important; }
  [data-projects]{ margin-top:0 !important; box-shadow:none !important; }
  [data-hero-parallax]{ transform:none !important; opacity:1 !important; }
}

.section-inner{ max-width:var(--iw-container); margin:0 auto; }
.section-head{ max-width:1100px; margin-bottom:clamp(48px,6vw,80px); }
.section-head h2{ font-weight:800; font-size:clamp(27px,3.6vw,46px); line-height:1.08; letter-spacing:-0.025em; margin-bottom:20px; text-wrap:balance; }
.section-head p{ font-family:'Manrope',sans-serif; font-size:16px; line-height:1.6; color:var(--iw-body); max-width:460px; }

/* ——— work grid ——— */
[data-workgrid]{ display:grid; grid-template-columns:repeat(12,1fr); gap:clamp(24px,3vw,56px); row-gap:clamp(48px,6vw,92px); }
@media (max-width:820px){
  [data-workgrid]{ grid-template-columns:1fr !important; }
  [data-workgrid] > a{ grid-column:auto !important; margin-top:0 !important; align-self:auto !important; }
}
[data-proj]{ display:block; color:var(--iw-ink); }
[data-pmedia]{ --mx:50%; --my:50%; position:relative; overflow:hidden; background:var(--iw-media); }
[data-ph]{ position:absolute; inset:0; will-change:transform; transition:transform .8s cubic-bezier(.22,1,.36,1); }
/* Единый плавный hover-zoom для ВСЕХ проектов: масштабируется контейнер [data-ph] целиком,
   а не отдельные media. Так все внутренние JS-трансформы (salam-slide, сцена БК) и разная
   разметка (video / две картинки Гольфстрима / сцена) увеличиваются одинаково и не конфликтуют. */
[data-proj]:hover [data-ph]{ transform:scale(1.03); }
[data-ph] video, [data-ph] img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; transition:filter .6s ease; }
[data-proj]:hover [data-ph] video, [data-proj]:hover [data-ph] img{ filter:saturate(1.06); }
/* ——— Gulfstream: two stacked images crossfade in CSS (replaces the video) ———
   Bottom img (day) is the rest state and is always visible; top img (night) sits at
   opacity:0 and animates in, covering the day frame, then fades back out.
   The night layer is deliberately the *animated* one: at rest it never paints, so a
   late-decoding day frame can no longer let the night frame flash through while the
   card scrolls into view. Compositing is identical to the old day-on-top version —
   the keyframes below are the exact complement of the previous ones. */
[data-gulf-crossfade] img[data-gulf-top]{ animation:gulfstreamCrossfade 3.005s linear infinite; animation-play-state:paused; }
[data-proj]:hover [data-gulf-crossfade] img[data-gulf-top]{ animation-play-state:running; }
/* 2px overscan on every side (clipped by [data-pmedia] overflow:hidden) — kills the sub-pixel gap from object-fit:cover on a fractional-height box.
   max-width/max-height:none opts out of tokens.css `img,video{max-width:100%}`, which would otherwise clamp the width overscan back to 100% and leave a side strip. */
[data-gulf-crossfade] img{ inset:-2px; width:calc(100% + 4px); height:calc(100% + 4px); max-width:none; max-height:none; }
@keyframes gulfstreamCrossfade{
  0%      { opacity:0; animation-timing-function:ease-in; }
  30.877% { opacity:1; }
  75.178% { opacity:1; animation-timing-function:cubic-bezier(0.5,0,0.5,1); }
  91.923% { opacity:0; }
  100%    { opacity:0; }
}
@media (prefers-reduced-motion: reduce){
  [data-gulf-crossfade] img[data-gulf-top]{ animation:none; opacity:0; }
}
/* ——— SalamPay: image slides horizontally (ping-pong) instead of a video ——— */
[data-ph] img[data-salam-slide]{
  inset:auto; right:auto; bottom:auto; left:0; top:0;
  width:auto; height:100%;          /* full height, no upscaling — the long image overflows horizontally */
  max-width:none; max-height:none;  /* opt out of tokens.css `img{max-width:100%}` so the image can be wider than the frame */
  transform:translate3d(0,0,0); will-change:transform;
  transition:none; /* JS drives the transition per leg */
}
/* the generic hover scale must not fight the JS-driven translate (inline transform wins anyway) */
[data-proj]:hover [data-ph] img[data-salam-slide]{ filter:saturate(1.06); }
@media (prefers-reduced-motion: reduce){
  [data-ph] img[data-salam-slide]{ transform:translate3d(0,0,0) !important; transition:none !important; }
}
/* ——— Братья Караваевы: CSS-сцена вместо видео ———
   Слои (снизу вверх по z-index):
   A .bk-bg          — статичный фон с едой (подложка)
   B .bk-phone-left  — левый наклонённый телефон, качается вверх-вниз (наклон вшит в картинку)
   C .bk-phone-right — правый прямой телефон: лента .bk-scroll-track скроллится в окне
                       .bk-screen (overflow:hidden), корпус .bk-body лежит ПОВЕРХ ленты
                       и своей прозрачной областью экрана показывает ленту. */
[data-bk-scene]{
  position:absolute; inset:0; overflow:hidden;
  background:#ebe0d3;                                   /* фолбэк-беж под цвет фона, против пустоты при незагрузке */
  transition:transform .8s cubic-bezier(.22,1,.36,1);
}
/* сцена БК больше НЕ масштабируется отдельно — общий hover-zoom даёт контейнер [data-ph] (см. выше) */
/* нейтрализуем родовой фильтр на отдельных <img> — сцена цельная */
[data-proj]:hover [data-bk-scene] img{ filter:none; }
[data-bk-scene] .bk-bg{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
[data-bk-scene] .bk-phone{ position:absolute; }
/* сброс родовых [data-ph] img только для «текучих» картинок (левый телефон + лента);
   корпус .bk-body НАМЕРЕННО исключён — ему нужен position:absolute + z-index (иначе лента перекроет рамку). */
[data-bk-scene] .bk-phone-left img, [data-bk-scene] .bk-scroll{ position:static; inset:auto; width:100%; height:auto; object-fit:contain; display:block; }
/* B — левый телефон, целиком качается. Стартует на паузе (0-й кадр), JS играет по наведению и
   отматывает назад на уходе. fill-mode:both — чтобы после обратной отмотки анимация не «выпадала»
   из getAnimations() (иначе повторное наведение её бы не находило). */
.bk-phone-left{ left:8.4%; top:6%; width:38%; z-index:1; will-change:transform; animation:karavaevPhoneLeft 3.857s linear infinite both; animation-play-state:paused; }
/* C — правый телефон: рамка фиксирована, прибит к низу блока; аспект = корпусу (720×1204), окно экрана считается в % */
.bk-phone-right{ left:57.2%; bottom:0; width:22%; z-index:2; aspect-ratio:720/1204; }
/* C1 — окно экрана под Dynamic Island: overflow висит здесь, не на всём корпусе.
   Границы прозрачной области экрана в исходнике phone_1: x 43..827, y 43..1455 из 870×1455. */
.bk-screen{ position:absolute; left:4.94%; top:2.96%; width:90.11%; height:97.04%; overflow:hidden; z-index:1; }
/* лента скроллится в 1.5× медленнее качания: 3.857s × 1.5 = 5.7855s */
.bk-scroll-track{ position:absolute; top:0; left:0; width:100%; will-change:transform; animation:karavaevContent 5.7855s linear infinite both; animation-play-state:paused; }
.bk-scroll-track .bk-scroll{ width:100%; height:auto; display:block; }
/* C2 — корпус (рамка + Dynamic Island) ПОВЕРХ ленты; прозрачный экран показывает ленту.
   Селектор со специфичностью (0,3,0) — перебивает родовой [data-bk-scene] .bk-phone img, чтобы position/z-index не сбрасывались. */
[data-bk-scene] .bk-phone-right .bk-body{ position:absolute; inset:0; width:100%; height:100%; object-fit:contain; z-index:2; }
@keyframes karavaevPhoneLeft{
  0%      { transform:translateY(0); animation-timing-function:ease-in-out; }
  45.588% { transform:translateY(-2.94%); animation-timing-function:ease-in-out; } /* ≈ −16.864px Фигмы в масштабе телефона */
  91.176% { transform:translateY(0); }
  100%    { transform:translateY(0); }
}
/* −733.413px из Фигмы пересчитаны под наш масштаб: полный ход = (высота_ленты − высота_окна)
   = 60.33% собственной высоты ленты (окно 1.801×ширины, лента 4.5396×ширины). */
@keyframes karavaevContent{
  0%      { transform:translateY(0); }
  75.283% { transform:translateY(-60.33%); animation-timing-function:ease-in-out; }
  87.322% { transform:translateY(0); }
  100%    { transform:translateY(0); }
}
@media (prefers-reduced-motion: reduce){
  .bk-phone-left, .bk-scroll-track{ animation:none !important; transform:none !important; }
  [data-bk-scene]{ transition:none; }
  /* при reduced-motion отключаем общий hover-zoom карточек */
  [data-ph]{ transition:none; }
  [data-proj]:hover [data-ph]{ transform:none; }
}
[data-ripple-el]{
  position:absolute; inset:0; opacity:0; transition:opacity .4s ease; mix-blend-mode:multiply; pointer-events:none;
  background:radial-gradient(220px 220px at var(--mx,50%) var(--my,50%), rgba(5,178,216,0.5), rgba(5,178,216,0.12) 38%, transparent 62%);
}
/* Only while the cursor is over the image itself — the ripple follows the
   pointer (--mx/--my update on pointermove over [data-pmedia]). Scoping it to
   the card would keep the glow frozen at the last position when the pointer
   moves onto the title, which is still inside [data-proj]. */
[data-pmedia]:hover [data-ripple-el]{ opacity:1; }
[data-pmeta]{
  display:flex; align-items:baseline; justify-content:flex-start; gap:18px; flex-wrap:wrap;
  padding-top:22px; border-top:1px solid rgba(20,37,49,0.14); transition:border-color .4s ease;
}
[data-proj]:hover [data-pmeta]{ border-top-color:var(--iw-accent) !important; }
[data-pname]{ font-weight:700; font-size:clamp(1.5rem,3vw,2.6rem); letter-spacing:-0.03em; line-height:1; transition:color .35s ease, transform .6s cubic-bezier(.22,1,.36,1); }
[data-proj]:hover [data-pname]{ color:var(--iw-accent-press) !important; transform:translateX(6px); }

/* ——— touch / no-hover: JS toggles .is-inview on the card while it is on
   screen (see bindProjectAutoplayInView). Mirror the meaningful hover polish
   here so the scroll-triggered animation feels as finished as the desktop
   hover. Ripple is cursor-driven, so it is intentionally left out. ——— */
@media (hover: none){
  [data-proj].is-inview [data-ph]{ transform:scale(1.03); }
  [data-proj].is-inview [data-ph] video,
  [data-proj].is-inview [data-ph] img{ filter:saturate(1.06); }
  [data-proj].is-inview [data-gulf-crossfade] img[data-gulf-top]{ animation-play-state:running; }
  [data-proj].is-inview [data-pmeta]{ border-top-color:var(--iw-accent) !important; }
  [data-proj].is-inview [data-pname]{ color:var(--iw-accent-press) !important; transform:translateX(6px); }
}

/* section subtitle hint swaps with the input model: "hover" on pointer
   devices, "scroll" on touch (matches the JS trigger above) */
[data-hint="scroll"]{ display:none; }
@media (hover: none){
  [data-hint="hover"]{ display:none; }
  [data-hint="scroll"]{ display:inline; }
}
.pcat{ margin-left:auto; font-family:'IBM Plex Mono',monospace; font-size:12px; letter-spacing:var(--iw-eyebrow-track); color:var(--iw-label); text-transform:uppercase; }
.cases-cta{ text-align:center; margin-top:clamp(48px,6vw,80px); }
.cases-cta .arrow{ font-family:'IBM Plex Mono',monospace; font-size:12.5px; letter-spacing:var(--iw-eyebrow-track); opacity:.82; }

/* ——— stats ——— */
.stats{ background:var(--iw-ink); color:var(--iw-on-dark-bright); padding:clamp(56px,8vh,88px) clamp(18px,5vw,72px); }
.stats-grid{ max-width:var(--iw-container); margin:0 auto; display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:clamp(28px,4vw,64px); }
[data-count]{ display:block; font-weight:800; font-size:clamp(56px,8vw,104px); line-height:0.9; letter-spacing:-0.04em; }
.stat p{ font-size:15px; color:var(--iw-on-dark); max-width:260px; margin-top:10px; }

/* ——— clients marquee ——— */
.trust{
  /* --logo-base: previous optical unit; --logo-h: 1.2× bigger logos.
     Block height is kept constant by removing half the logo growth from the
     vertical padding, so the top/bottom gaps shrink as the logos grow. */
  --logo-base:clamp(38px,4.6vw,62px);
  --logo-h:calc(var(--logo-base) * 1.2);
  background:var(--iw-cloud);
  padding:calc(clamp(72px,11vh,128px) - (var(--logo-h) - var(--logo-base)) / 2) 0;
  overflow:hidden;
}
[data-marquee]{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
[data-track]{
  display:flex; align-items:center;
  gap:clamp(60px,8vw,132px); width:max-content; padding-left:clamp(60px,8vw,132px);
  animation:iw-marquee 42s linear infinite;
  /* --logo-h (optical unit) is inherited from .trust; per-logo heights scale off it */
}
@media (prefers-reduced-motion: reduce){ [data-track]{ animation:none !important; } }
[data-logo]{
  display:flex; align-items:center; flex:0 0 auto;
}
[data-logo] img{
  width:auto; display:block; object-fit:contain;
  user-select:none; -webkit-user-drag:none;
}
/* per-logo optical alignment: each logo has different content height inside its
   PNG (extra icons/subtitles/stacked lines), so equal box height isn't enough —
   scale each so the visible mark reads at the same optical size. */
[data-logo] img[src*="neft_magistral"]  { height:calc(var(--logo-h) * 1.00); }
[data-logo] img[src*="bratya_karavaevi"]{ height:calc(var(--logo-h) * 0.90); }
[data-logo] img[src*="golfstream"]      { height:calc(var(--logo-h) * 0.83); }
[data-logo] img[src*="helix"]           { height:calc(var(--logo-h) * 0.80); }
[data-logo] img[src*="sletat"]          { height:calc(var(--logo-h) * 0.80); }
[data-logo] img[src*="salampay"]        { height:calc(var(--logo-h) * 0.77); }
[data-logo] img[src*="pikabu"]          { height:calc(var(--logo-h) * 0.73); }
[data-logo] img[src*="barrier"]         { height:calc(var(--logo-h) * 0.73); }
[data-logo] img[src*="tomato"]          { height:calc(var(--logo-h) * 0.67); }
[data-logo] img[src*="eka"]             { height:calc(var(--logo-h) * 0.67); }

/* ——— services ——— */
.services{ position:relative; background:var(--iw-surface); border-top:1px solid var(--iw-border); padding:clamp(72px,11vh,128px) clamp(18px,5vw,72px); overflow:hidden; }
.services .section-head h2{ max-width:720px; }
.services-grid{
  position:relative; z-index:1;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:1px; background:var(--iw-border);
}
/* position+z-index: облако частиц маркера вылетает за границы карточки, и без
   подъёма его перекрыл бы белый фон соседней карточки */
[data-svc]{ position:relative; background:var(--iw-white); padding:clamp(28px,3vw,40px); transition:background .3s; }
[data-svc].is-hot{ background:#F2F7FC; z-index:3; }
.svc-top{ display:flex; justify-content:flex-start; align-items:flex-start; margin-bottom:20px; }
/* Маркер: сам квадрат — фон этого span (он же фолбэк, если JS не сработал), а поверх
   центрируется канвас с частицами. Как только канвас взял отрисовку на себя, JS
   ставит .is-live и подложку убирает — иначе при разлёте она осталась бы висеть
   цельным квадратом. Канвас крупнее маркера: частицам нужно поле разлёта. */
[data-svcdot]{ width:10px; height:10px; position:relative; z-index:4; background:var(--iw-ink); }
[data-svcdot].is-live{ background:transparent; }
[data-svcdot] canvas{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); pointer-events:none; }
[data-svc] h3{ font-weight:700; font-size:21px; letter-spacing:-0.01em; margin-bottom:12px; }
[data-svc] p{ font-size:14.5px; line-height:1.6; color:var(--iw-body); }
.services-cta{ position:relative; z-index:1; text-align:center; margin-top:clamp(48px,6vw,80px); }

/* ——— testimonials ——— */
.testimonials{ background:var(--iw-page); padding:clamp(72px,11vh,128px) clamp(18px,5vw,72px); overflow:hidden; }
.testimonials-inner{ max-width:1060px; margin:0 auto; }
.t-head{ display:flex; justify-content:space-between; align-items:center; margin-bottom:30px; }
.t-arrows{ display:flex; gap:10px; }
[data-tprev], [data-tnext]{
  flex:0 0 44px;                 /* фиксируем размер: не сжимаются в узком флексе */
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  border:1px solid #B7C0CA; color:#1A2730; background:transparent; font-size:18px; cursor:pointer;
  transition:background .25s ease, border-color .25s ease, color .25s ease;
}
/* Стрелки — инлайновый SVG (а не Unicode «←/→»): на Android/Samsung голые
   символы брались из разных fallback-шрифтов и рендерились разного размера.
   SVG-стрелки геометрически зеркальны и одинаковы на любом устройстве. */
[data-tprev] svg, [data-tnext] svg{ width:18px; height:18px; display:block; }
[data-tviewport]{ position:relative; transition:height .4s var(--iw-ease); }
/* Initial slide state, mirrored from what initTestimonials() sets inline. Without
   it, before the JS runs every [data-tslide] sits in normal flow and the slides
   stack on top of each other — visible as an artifact when the page is reloaded
   while this block is on screen. Declaring it in CSS makes the first paint already
   correct (only the first testimonial shown); the JS then takes over seamlessly. */
[data-tslide]{ position:absolute; top:0; left:0; right:0; opacity:0; transform:translateX(24px); pointer-events:none; }
[data-tslide]:first-child{ position:relative; opacity:1; transform:none; pointer-events:auto; }
[data-tslide] blockquote{ font-weight:600; font-size:clamp(19px,2.3vw,28px); line-height:1.42; letter-spacing:-0.015em; }
[data-tslide] blockquote .accent{ color:var(--iw-accent); }
.t-author{ display:flex; flex-direction:column; align-items:flex-start; gap:5px; margin-top:28px; }
.t-name{ font-weight:700; font-size:15.5px; line-height:1.35; }
.t-project{ font-size:14px; color:var(--iw-accent); line-height:1.35; }
[data-tdots]{ display:flex; gap:9px; margin-top:30px; }

/* ——— blog ——— */
.blog{ background:var(--iw-surface); border-top:1px solid var(--iw-border); padding:clamp(72px,11vh,128px) clamp(18px,5vw,72px); }
.blog-head{ display:flex; justify-content:space-between; align-items:baseline; flex-wrap:wrap; gap:16px; margin-bottom:clamp(40px,5vw,64px); }
.blog-head h2{ font-weight:800; font-size:clamp(27px,3.6vw,46px); letter-spacing:-0.025em; }
.blog-all{
  font-family:'IBM Plex Mono',monospace; font-size:13px; letter-spacing:var(--iw-eyebrow-track); color:var(--iw-accent);
  border-bottom:1px solid rgba(5,178,216,0.4); padding-bottom:3px;
}
.blog-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:clamp(20px,2.5vw,32px); }
[data-post]{
  position:relative; display:block; background:var(--iw-white);
  overflow:hidden; transition:transform .4s cubic-bezier(.22,1,.36,1);
}
.post-cover{
  position:relative; z-index:1; aspect-ratio:16/10; overflow:hidden;
  background:var(--iw-media);
}
.post-cover img{ width:100%; height:100%; object-fit:cover; transition:transform .6s var(--iw-ease); }
[data-post]:hover .post-cover img{ transform:scale(1.04); }
.post-body{ position:relative; z-index:1; padding:24px; display:flex; flex-direction:column; gap:11px; }
.post-body h3{ font-weight:700; font-size:19px; line-height:1.25; letter-spacing:-0.01em; color:var(--iw-ink); margin:0; }
.post-desc{
  font-size:14px; line-height:1.55; color:var(--iw-body); margin:0;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}
.post-date{
  margin-top:2px; font-family:'IBM Plex Mono',monospace; font-size:11px;
  letter-spacing:var(--iw-eyebrow-track); text-transform:uppercase; color:var(--iw-accent);
}

/* ——— CTA card (matches other pages, without the outline) ——— */
.cta-card{ background:var(--iw-surface-dark); padding:clamp(30px,3.4vw,48px); display:flex; flex-direction:column; gap:26px; }
.cta-card p{ font-size:clamp(19px,2.2vw,26px); font-weight:700; line-height:1.3; letter-spacing:-0.015em; }

