/* Services. Structure follows notes/offer-strategy.md: how to start (the
   ladder), what I cover (the four areas), how I work, what I do not do. No
   prices - those are internal. Dark surface is the source site's signature
   for this screen. */

/* Compact ladder strip: name, length, one line. The full sales anatomy
   (deliverable lists, tags) stays out - the door is ajar, not wedged open. */
function LadderCard({ item, index, mono }) {
  const core = !!item.core;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', boxSizing: 'border-box' }}>
      <div style={{ height: core ? 3 : 1.5, background: core ? 'var(--gradient-hero)' : 'var(--border-hairline-dark)', borderRadius: 2 }} />
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12, marginTop: 18 }}>
        <span className="sm-mono" style={mono}>{String(index).padStart(2, '0')}</span>
        <span className="sm-mono" style={mono}>{item.length}</span>
      </div>
      <h3 style={{ margin: '14px 0 0', fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(19px, 2vw, 24px)', lineHeight: 1.12, letterSpacing: '-0.02em', color: 'var(--white)' }}>{item.name}</h3>
      <p style={{ margin: '10px 0 0', fontFamily: 'var(--font-body)', fontSize: 15, lineHeight: 1.6, color: 'var(--gray-on-dark-200)' }}>{item.short || item.what}</p>
    </div>
  );
}

/* The four steps of an engagement read as a sequence, not four equal
   paragraphs: one continuous track that draws itself once the section is in
   view, with the nodes lighting up behind it in order. No horizontal rules -
   the track carries the separation. Falls back to the finished state when
   there is no observer or the visitor asked for less motion. */
function HowAxis({ rows }) {
  const ref = React.useRef(null);
  const [on, setOn] = React.useState(false);
  const reduce = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  React.useEffect(() => {
    if (reduce || !('IntersectionObserver' in window)) { setOn(true); return; }
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setOn(true); io.disconnect(); } }, { threshold: 0.2, rootMargin: '0px 0px -10% 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, [reduce]);
  const ease = 'cubic-bezier(.22,1,.36,1)';
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <span aria-hidden="true" style={{
        position: 'absolute', left: 16, top: 26, bottom: 26, width: 2, borderRadius: 1,
        background: 'linear-gradient(to bottom, #a83bd6 0%, #3355d1 55%, rgba(255,255,255,0.2) 100%)',
        transformOrigin: 'top', transform: on ? 'scaleY(1)' : 'scaleY(0)',
        transition: reduce ? 'none' : `transform 1100ms ${ease}`,
      }} />
      {rows.map(([k, v], i) => (
        <div key={k} className="sm-how-row" style={{ position: 'relative', display: 'grid', gridTemplateColumns: '34px 200px minmax(0, 1fr)', gap: 22, padding: '14px 0' }}>
          <span aria-hidden="true" style={{
            width: 34, height: 34, borderRadius: 999, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            background: 'var(--black)', border: '2px solid rgba(255,255,255,0.32)',
            fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: 10.5, color: 'var(--white)',
            opacity: on ? 1 : 0, transform: on ? 'scale(1)' : 'scale(0.6)',
            transition: reduce ? 'none' : `opacity 420ms ${ease} ${200 + i * 190}ms, transform 420ms ${ease} ${200 + i * 190}ms`,
          }}>{String(i + 1).padStart(2, '0')}</span>
          <h3 style={{ margin: '7px 0 0', fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 18, lineHeight: 1.3, letterSpacing: '-0.015em', color: 'var(--white)' }}>{k}</h3>
          <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-body)', fontSize: 16, lineHeight: 1.68, color: 'var(--gray-on-dark-200)', maxWidth: '54ch' }}>{v}</p>
        </div>
      ))}
    </div>
  );
}

const END_SWAP = {
  '--ink-850': '#ffffff',
  '--white': '#0a0a0a',
  '--black': '#ffffff',
  '--gray-on-dark-200': '#2a2a2a',
  '--gray-on-dark-400': '#4d4d4d',
  '--gray-on-dark-500': '#6b6b6b',
  '--border-hairline-dark': 'rgba(10,10,10,0.16)',
  '--gray-600': '#c9c9c9',
  '--gray-700': '#dcdcdc',
};

function ServiceBullets({ items, thumb, mono, more, fewer }) {
  const rootRef = React.useRef(null);
  const [narrow, setNarrow] = React.useState(() => typeof window !== 'undefined' && window.matchMedia('(max-width: 900px)').matches);
  const [open, setOpen] = React.useState(false);
  const toggle = () => {
    const wasOpen = open;
    setOpen(!wasOpen);
    if (!wasOpen) return;
    /* Two frames: one for React to paint the shorter list, one for the
       ResizeObserver to give the carousel its new height. */
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const el = rootRef.current;
      if (!el) return;
      const card = el.closest('[data-service-card]');
      const layer = el.closest('.sm-screen-layer');
      if (!card || !layer) return;
      const top = card.getBoundingClientRect().top - layer.getBoundingClientRect().top;
      layer.scrollTo({ top: Math.max(0, layer.scrollTop + top - 24), behavior: 'smooth' });
    }));
  };
  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 900px)');
    const on = () => setNarrow(mq.matches);
    mq.addEventListener('change', on);
    return () => mq.removeEventListener('change', on);
  }, []);
  const collapsed = narrow && !open && items.length > 3;
  const shown = collapsed ? items.slice(0, 4) : items;
  const rest = items.length - 3;
  return (
    <div ref={rootRef}>
      <ul className="sm-service-list" style={{ margin: '18px 0 0', padding: 0, listStyle: 'none', display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', columnGap: 40 }}>
        {shown.map(([k, v], i) => {
          const peek = collapsed && i === 3;
          return (
            <li key={k} style={{ position: 'relative', display: 'flex', gap: 14, alignItems: 'flex-start', padding: '15px 0', borderTop: '1.5px solid var(--border-hairline-dark)', maxHeight: peek ? 66 : 'none', overflow: peek ? 'hidden' : 'visible' }}>
              <span aria-hidden="true" style={{ width: 13, height: 13, borderRadius: 4, background: thumb, flexShrink: 0, marginTop: 5 }} />
              <span>
                <span style={{ display: 'block', fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 16.5, lineHeight: 1.3, letterSpacing: '-0.015em', color: 'var(--white)' }}>{k}</span>
                <span style={{ display: 'block', marginTop: 3, fontFamily: 'var(--font-body)', fontSize: 14.5, lineHeight: 1.55, color: 'var(--gray-on-dark-400)' }}>{v}</span>
              </span>
              {peek && <span aria-hidden="true" style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 44, background: 'linear-gradient(to bottom, rgba(28,28,28,0) 0%, var(--ink-850) 72%)', pointerEvents: 'none' }} />}
            </li>
          );
        })}
      </ul>
      {narrow && items.length > 3 && (
        <div style={{ display: 'flex', justifyContent: 'center', marginTop: collapsed ? 16 : 12, marginBottom: 6 }}>
          <button type="button" className="sm-mono" onClick={toggle} aria-expanded={open}
            style={{ ...mono, display: 'inline-flex', alignItems: 'center', gap: 8, minHeight: 44, padding: '0 20px', border: '1.5px solid var(--border-hairline-dark)', borderRadius: 999, background: 'transparent', color: 'var(--white)', cursor: 'pointer' }}>
            {open ? fewer : String(more || '').replace('{n}', rest)}
            <span aria-hidden="true">{open ? '\u2191' : '\u2193'}</span>
          </button>
        </div>
      )}
    </div>
  );
}

const svcReachLink = { display: 'inline-flex', alignItems: 'center', gap: 9, minHeight: 44, paddingTop: 14, paddingBottom: 14, marginTop: -14, marginBottom: -14, fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: 9.5, letterSpacing: 'var(--tracking-label)', textTransform: 'uppercase', color: 'var(--black)', textDecoration: 'underline', textUnderlineOffset: 3 };

function ServicesDetail({ s, t, onBack, onContact }) {
  const { Button, Icon } = window.SevcikMarketingDesignSystem_3203fa;
  const d = s.servicesDetail;
  /* A home-page service row can hand over which section to open (and be
     scrolled to) via window.__smServiceSection - consumed exactly once. */
  const initRef = React.useRef(undefined);
  if (initRef.current === undefined) {
    initRef.current = Number.isInteger(window.__smServiceSection) ? window.__smServiceSection : null;
    window.__smServiceSection = undefined;
  }
  const [active, setActive] = React.useState(initRef.current ?? 0);
  const contentRef = React.useRef(null);
  const readFrom = () => {
    /* Measure the carousel viewport, never a slide: slides are translated and
       mid-transition after a swipe, so anchoring to one lands the scroll in the
       wrong place. */
    const el = viewportRef.current || contentRef.current; if (!el) return;
    const layer = el.closest('.sm-screen-layer'); if (!layer) return;
    /* Land ABOVE the headline: enough headroom that the section title is the
       first thing read, never clipped under the sticky mobile bar. */
    const pad = 96 + (parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-bar-h')) || 0);
    layer.scrollTo({ top: Math.max(0, el.getBoundingClientRect().top - layer.getBoundingClientRect().top + layer.scrollTop - pad), behavior: 'smooth' });
  };
  /* Centering has to be re-run after the plate's width transition (.45s)
     finishes - measuring mid-animation lands the card off-centre, which is
     what a fresh redirect used to do. */
  const centerPlate = (i, retries = 3) => {
    const strip = stripRef.current; if (!strip) return;
    const el = strip.children[i]; if (!el) return;
    const put = (smooth) => {
      const el2 = strip.children[i]; if (!el2) return;
      const left = Math.max(0, el2.offsetLeft - (strip.clientWidth - el2.offsetWidth) / 2);
      strip.scrollTo({ left, behavior: smooth ? 'smooth' : 'auto' });
    };
    put(true);
    for (let k = 1; k <= retries; k++) setTimeout(() => put(k === retries ? false : true), k * 240);
  };
  React.useEffect(() => {
    if (initRef.current === null) return;
    const t1 = setTimeout(() => { centerPlate(initRef.current, 4); readFrom(); }, 320);
    return () => clearTimeout(t1);
  }, []);
  const stripRef = React.useRef(null);
  const drag = React.useRef({ down: false, moved: false, x: 0, left: 0 });
  /* Mouse drag-to-scroll for the cover strip, same as the About chapter
     strip - a real drag (>6px) swallows the click; touch scrolls natively. */
  const onStripDown = (ev) => {
    if (ev.pointerType === 'touch') return;
    const s = stripRef.current; if (!s) return;
    drag.current = { down: true, moved: false, x: ev.clientX, left: s.scrollLeft };
  };
  const onStripMove = (ev) => {
    const g = drag.current; if (!g.down) return;
    const dx = ev.clientX - g.x;
    if (Math.abs(dx) > 6) g.moved = true;
    if (g.moved) stripRef.current.scrollLeft = g.left - dx;
  };
  const onStripUp = () => { setTimeout(() => { drag.current.down = false; }, 0); };
  const goTo = (i, scroll = true) => {
    setActive(i);
    requestAnimationFrame(() => centerPlate(i));
    if (scroll) setTimeout(readFrom, 80);
  };
  const goToRef = React.useRef(goTo);
  goToRef.current = goTo;
  /* A real carousel rather than a switch: all four sections sit side by side in
     one track and the drag moves the track under your finger, 1:1. Release
     snaps to whichever slide is nearest. Horizontal intent still has to beat
     vertical by a clear margin - on a phone a thumb going down the page is
     never a section change. */
  const swipe = React.useRef({ id: null, x: 0, y: 0, lock: null });
  const viewportRef = React.useRef(null);
  const layerRef = React.useRef(null);
  const slideRefs = React.useRef([]);
  /* The wheel listener is registered ONCE and reads the live index through a
     ref. Re-registering it per section (which an [active] dependency does)
     resets the spent-gesture guard on every step, and one trackpad fling then
     walks through every section it can reach. */
  const activeRef = React.useRef(active);
  activeRef.current = active;
  const [trackH, setTrackH] = React.useState(null);
  const trackRef = React.useRef(null);
  /* The follow offset is written straight to the element. Holding it in React
     state re-renders all four slides on every wheel tick, which is what made
     flipping feel heavy - the gesture is 60fps compositor work, not a data
     change. */
  const paint = (px, animate) => {
    const el = trackRef.current;
    if (!el) return;
    el.style.transition = animate ? 'transform .45s cubic-bezier(.22,1,.36,1)' : 'none';
    el.style.transform = `translateX(calc(${-activeRef.current * 100}% + ${px}px))`;
  };
  const setGrab = (on) => {
    /* The whole screen listens, for the same reason as the About chapters: a
       wheel event goes to whatever is under the cursor, and the carousel viewport
       is measured to the slide on show, so it moves out from under the pointer.
       The plate strip is excluded because it scrolls horizontally itself. */
    const host = viewportRef.current;
    const el = (host && host.closest('.sm-screen-layer')) || host;
    if (!el) return;
    el.style.cursor = on ? 'grabbing' : 'grab';
    el.style.userSelect = on ? 'none' : 'auto';
    el.style.webkitUserSelect = on ? 'none' : 'auto';
  };
  React.useEffect(() => { paint(0, true); }, [active]);
  /* The track is as tall as the slide on show, measured rather than guessed, so
     a short section does not leave a hole under it. */
  React.useEffect(() => {
    const measure = () => {
      const el = slideRefs.current[active];
      if (el) setTrackH(el.getBoundingClientRect().height);
    };
    measure();
    const t = setTimeout(measure, 60);
    window.addEventListener('resize', measure);
    let ro = null;
    if ('ResizeObserver' in window) {
      ro = new ResizeObserver(measure);
      const el = slideRefs.current[active];
      if (el) ro.observe(el);
    }
    return () => { clearTimeout(t); window.removeEventListener('resize', measure); if (ro) ro.disconnect(); };
  }, [active, d]);
  /* A gesture never scrolls the page: you are already looking at the section
     you just dragged into view. Only a card or the pager re-anchors. */
  const step = (dir) => {
    const n = d.sections.length;
    goTo((active + dir + n) % n, false);
  };
  /* Trackpad. A two-finger swipe is a wheel event, not a pointer drag, so the
     gesture above never sees it. Horizontal delta has to beat vertical or the
     page keeps its scroll, and one gesture spends itself once - a trackpad
     fling emits dozens of events with a long tail. */
  React.useEffect(() => {
    /* The whole screen listens, for the same reason as the About chapters: a
       wheel event goes to whatever is under the cursor, and the carousel viewport
       is measured to the slide on show, so it moves out from under the pointer.
       The plate strip is excluded because it scrolls horizontally itself. */
    const host = viewportRef.current;
    const el = (host && host.closest('.sm-screen-layer')) || host;
    if (!el) return;
    /* One flip per stream, decided by the stream's SHAPE, with no clocks
       except the 150ms gap detector. Timing gates kept failing because macOS
       momentum can emit for seconds and preview/iframe clocks lie (see
       notes/handover-swipe-gesture.md for the full failure table). The rules:
       - events under 4px never accumulate: that is tail noise, and a dying tail
         emitting 1-2px for a second could otherwise creep past the threshold;
       - after a step, the stream must prove the hand lifted before it may step
         again: four consecutive sub-4px events (an exhausted tail) or a 150ms
         gap. A push-ease-push sweep never goes that quiet, so it stays one
         flip; a real second flick lands on an exhausted tail and re-arms. */
    let acc = 0, idle = null, last = 0, stepped = false, smallRun = 0, lastDir = 0;
    const rec = [];
    let peak = 0, inTail = false;
    const LOG = (window.__wheelLog = window.__wheelLog || []);
    const STEP = 90;
    const onWheel = (ev) => {
      const mag = Math.abs(ev.deltaX);
      if (mag < Math.abs(ev.deltaY) * 1.2 || mag < 2) return;
      /* Swipes only mean "change section" over the carousel itself. Everything
         below it - the how sections, the ladder, the closing CTA - and the plate
         strip opt out, same as the About page's clients section. */
      /* The card that ends the page opts out by class: it is a div, not a
         section, and fullscreen it covers the viewport - without this every
         wheel over it was cancelled and there was no way back up. */
      if (ev.target.closest && ev.target.closest('.sm-highlights, section, .sm-service-close, .sm-service-cta, .sm-cta-slot')) return;
      ev.preventDefault();
      const now = performance.now();
      if (now - last > 150) { acc = 0; stepped = false; smallRun = 0; rec.length = 0; peak = 0; inTail = false; }
      /* A sign flip is the one unambiguous signal a wheel stream carries:
         momentum never reverses direction, only a hand does. A reversal of 8px
         or more re-arms instantly - without this, starting a right swipe while
         a left fling's tail was still emitting left the stream locked until the
         next 150ms gap, which is exactly "works a few times, then dead". */
      const dirNow = ev.deltaX > 0 ? 1 : -1;
      if (mag >= 8 && lastDir && dirNow !== lastDir) { acc = 0; stepped = false; smallRun = 0; peak = 0; inTail = false; }
      if (mag >= 8) lastDir = dirNow;
      last = now;
      LOG.push([Math.round(now), Math.round(ev.deltaX), stepped ? 1 : 0]);
      if (LOG.length > 600) LOG.splice(0, 200);
      clearTimeout(idle);
      idle = setTimeout(() => { acc = 0; stepped = false; smallRun = 0; rec.length = 0; peak = 0; inTail = false; paint(0, true); }, 150);
      smallRun = mag < 4 ? smallRun + 1 : 0;
      /* Re-arm rules, all shape-based. A step often lands while the fling's
         own deltas are still growing, so "deltas rising" alone re-armed against
         the same gesture and double-flipped. The stream must first prove it
         entered its tail - decayed below 40% of the peak seen since the step -
         and only a rise out of THAT is a new flick. Momentum never rises out of
         its own tail; jitter (13,15,14) fails the growth test. The other ways
         back: the tail dies (four sub-4px events), the direction flips
         (above), or a 150ms gap. */
      if (stepped) {
        peak = Math.max(peak, mag);
        if (mag < peak * 0.4) inTail = true;
      }
      const m1 = rec.length > 1 ? rec[rec.length - 2] : 0;
      const m2 = rec.length ? rec[rec.length - 1] : 0;
      const rising = mag > m2 && m2 > m1 && (mag - m1) >= 15 && mag >= 25;
      if (stepped && (smallRun >= 4 || (inTail && rising))) { stepped = false; acc = 0; }
      rec.push(mag);
      if (rec.length > 4) rec.shift();
      if (stepped || mag < 4) return;
      acc += ev.deltaX;
      paint(Math.max(-120, Math.min(120, -acc * 0.7)), false);
      if (Math.abs(acc) > STEP) {
        const dir = acc > 0 ? 1 : -1;
        acc = 0;
        stepped = true;
        peak = mag; inTail = false;
        const n = d.sections.length;
        goToRef.current((activeRef.current + dir + n) % n, false);
      }
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => { clearTimeout(idle); el.removeEventListener('wheel', onWheel); };
  }, [d.sections.length]);
  const onBodyDown = (ev) => {
    if (ev.pointerType === 'mouse' && ev.button !== 0) return;
    if (ev.target.closest('button, a')) return;
    swipe.current = { id: ev.pointerId, x: ev.clientX, y: ev.clientY, lock: null };
  };
  const onBodyMove = (ev) => {
    const g = swipe.current;
    if (g.id !== ev.pointerId) return;
    const dx = ev.clientX - g.x, dy = ev.clientY - g.y;
    if (!g.lock) {
      if (Math.abs(dx) < 12 && Math.abs(dy) < 12) return;
      g.lock = Math.abs(dx) > Math.abs(dy) * 1.4 ? 'x' : 'y';
    }
    if (g.lock !== 'x') return;
    /* Once the gesture is horizontal, it owns the pointer: without this the
       browser starts selecting the prose it is dragging over, and the blue
       highlight survives the release. */
    if (ev.cancelable) ev.preventDefault();
    const s = window.getSelection && window.getSelection();
    if (s && !s.isCollapsed) s.removeAllRanges();
    const n = d.sections.length;
    /* Half-speed past either end, so the first and last slide push back
       instead of leaving the column empty. */
    const over = (active === 0 && dx > 0) || (active === n - 1 && dx < 0);
    paint(over ? dx * 0.35 : dx, false);
    setGrab(true);
  };
  const onBodyUp = (ev) => {
    const g = swipe.current;
    if (g.id !== ev.pointerId) return;
    const dx = ev.clientX - g.x;
    swipe.current = { id: null, x: 0, y: 0, lock: null };
    setGrab(false);
    const w = viewportRef.current ? viewportRef.current.clientWidth : 600;
    if (g.lock === 'x' && Math.abs(dx) > Math.min(140, w * 0.22)) step(dx < 0 ? 1 : -1);
    else paint(0, true);
  };
  const pickSection = (i) => {
    if (drag.current.moved) { drag.current.moved = false; return; }
    goTo(i);
  };
  const [backHover, setBackHover] = React.useState(false);
  const mono = { fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: 9.5, letterSpacing: 'var(--tracking-label)', textTransform: 'uppercase', color: 'var(--gray-on-dark-500)' };
  const wrap = { maxWidth: 1080, margin: '0 auto', padding: '0 32px' };
  const sec = d.sections[active];

  const [atEnd, setAtEnd] = React.useState(false);
  /* Move off the bottom edge far enough that the listener's own threshold
     retracts the panel. */
  const closeCta = () => {
    const l = layerRef.current;
    const el = l && l.scrollHeight - l.clientHeight > 4 ? l : (document.scrollingElement || document.documentElement);
    const target = Math.max(0, el.scrollTop - Math.max(200, el.clientHeight * 0.5));
    if (typeof el.scrollTo === 'function') el.scrollTo(0, target);
    el.scrollTop = target;
  };
  React.useEffect(() => {
    const pick = () => {
      const l = layerRef.current;
      if (l && l.scrollHeight - l.clientHeight > 4) return l;
      return document.scrollingElement || document.documentElement;
    };
    const read = () => {
      const el = pick();
      const rem = el.scrollHeight - el.scrollTop - el.clientHeight;
      setAtEnd((was) => (was ? rem <= 8 : rem <= 4));
    };
    const onScroll = read;
    /* Capture phase on the document: scroll events do not bubble, and which
       element actually scrolls depends on the viewport. */
    const l0 = layerRef.current;
    if (l0) l0.addEventListener('scroll', onScroll, { passive: true });
    document.addEventListener('scroll', onScroll, { passive: true, capture: true });
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    read();
    /* Mount runs before the layer has its final scrollHeight, and expanding
       a list or switching a card changes it later - so watch the box too. */
    let ro = null;
    if ('ResizeObserver' in window) {
      ro = new ResizeObserver(onScroll);
      const l = layerRef.current;
      if (l) { ro.observe(l); if (l.firstElementChild) ro.observe(l.firstElementChild); }
    }
    const settle = setTimeout(read, 400);
    return () => { if (l0) l0.removeEventListener('scroll', onScroll); document.removeEventListener('scroll', onScroll, { capture: true }); window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); if (ro) ro.disconnect(); clearTimeout(settle); };
  }, [d, active]);
  const reduceEnd = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const ctaRef = React.useRef(null);
  const [ctaBox, setCtaBox] = React.useState(null);
  const [ctaFull, setCtaFull] = React.useState(false);
  React.useEffect(() => {
    const el = ctaRef.current;
    if (!el) return;
    if (atEnd) {
      const r = el.getBoundingClientRect();
      const layer = layerRef.current;
      const lr = layer ? layer.getBoundingClientRect() : { top: 0, left: 0 };
      setCtaBox({ top: r.top - lr.top, left: r.left - lr.left, width: r.width, height: r.height });
      const id = requestAnimationFrame(() => requestAnimationFrame(() => setCtaFull(true)));
      const t0 = setTimeout(() => setCtaFull(true), 60);
      return () => { cancelAnimationFrame(id); clearTimeout(t0); };
    }
    setCtaFull(false);
    /* Held until the card is back on its own footprint - dropping it out of
       fixed positioning sooner makes it jump. */
    const t = setTimeout(() => setCtaBox(null), reduceEnd ? 0 : 560);
    return () => clearTimeout(t);
  }, [atEnd, reduceEnd]);
  const ctaMove = reduceEnd ? 'none' : 'top .56s cubic-bezier(.65,0,.35,1), left .56s cubic-bezier(.65,0,.35,1), width .56s cubic-bezier(.65,0,.35,1), height .56s cubic-bezier(.65,0,.35,1), border-radius .56s cubic-bezier(.65,0,.35,1), padding .56s cubic-bezier(.65,0,.35,1)';
  const ctaStyle = ctaBox ? {
    position: 'fixed', zIndex: 8, overflow: 'hidden', pointerEvents: ctaFull ? 'none' : 'auto',
    top: ctaFull ? 0 : ctaBox.top, left: ctaFull ? 0 : ctaBox.left,
    width: ctaFull ? '100%' : ctaBox.width, height: ctaFull ? '100%' : ctaBox.height,
    borderRadius: ctaFull ? 0 : 'var(--radius-card)',
    display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start',
    transition: ctaMove,
  } : null;
  /* Whatever top padding the layer has at this width (0 on desktop, the
     mobile bar's height below 1024px). */
  const [padTop, setPadTop] = React.useState(0);
  React.useEffect(() => {
    const read = () => {
      const l = layerRef.current;
      if (l) setPadTop(parseFloat(getComputedStyle(l).paddingTop) || 0);
    };
    read();
    window.addEventListener('resize', read);
    return () => window.removeEventListener('resize', read);
  }, []);

  return (
    <div ref={layerRef} className="sm-screen-layer sm-end-swap" style={{ position: 'fixed', inset: 0, zIndex: 60, background: 'var(--ink-850)', overflowY: 'auto', ...(atEnd ? END_SWAP : null) }}>
      <div style={{ minHeight: '100%' }}>
      <div className="sm-service-wrap" style={{ ...wrap, paddingTop: 28, paddingBottom: 96 }}>
        <div style={{ opacity: atEnd ? 0 : 1, transition: reduceEnd ? 'none' : `opacity 320ms ease ${atEnd ? 380 : 460}ms`, pointerEvents: atEnd ? 'none' : 'auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 20, marginBottom: 'clamp(40px, 8vh, 88px)' }}>
          <button onClick={onBack} onMouseEnter={() => setBackHover(true)} onMouseLeave={() => setBackHover(false)}
            style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 44, border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, ...mono, color: backHover ? 'var(--white)' : 'var(--gray-on-dark-400)', transition: 'color .25s ease' }}>
            <span style={{ width: 44, height: 44, borderRadius: 'var(--radius-card)', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: backHover ? 'var(--white)' : 'rgba(255,255,255,0.08)', transition: 'background-color .25s ease' }}>
              <Icon name="arrow-up-left" size={16} color={backHover ? 'var(--black)' : 'var(--white)'} />
            </span>
            {t.backHome}
          </button>
          <span style={mono}>{t.servicesBtn}</span>
        </div>

        {d.heading ? (
          <React.Fragment>
            <h1 style={{ margin: 0, maxWidth: '14ch', fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(34px, 5.6vw, 82px)', lineHeight: 1.02, letterSpacing: '-0.03em', color: 'var(--white)' }}>{d.heading}</h1>
            <p style={{ margin: '24px 0 0', maxWidth: '62ch', fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 'clamp(17px, 1.9vw, 22px)', lineHeight: 1.5, color: 'var(--gray-on-dark-200)' }}>{String(d.lead).split('\n').map((line, i) => (<React.Fragment key={i}>{i > 0 && ' '}{i > 0 && <br className="sm-br-wide" />}{line}</React.Fragment>))}</p>
            <h2 style={{ margin: 'clamp(44px, 8vh, 88px) 0 0', fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(26px, 3.4vw, 44px)', lineHeight: 1.06, letterSpacing: '-0.025em', color: 'var(--white)' }}>{d.coverTitle}</h2>
            <p style={{ margin: '16px 0 0', maxWidth: '52ch', fontFamily: 'var(--font-body)', fontSize: 17, lineHeight: 1.65, color: 'var(--gray-on-dark-200)' }}>{d.coverNote}</p>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <h1 style={{ margin: 0, maxWidth: '22ch', fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(32px, 4.6vw, 68px)', lineHeight: 1.03, letterSpacing: '-0.03em', color: 'var(--white)', textWrap: 'balance' }}>{d.coverTitle}</h1>
            <p style={{ margin: '24px 0 0', maxWidth: '62ch', fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 'clamp(17px, 1.9vw, 22px)', lineHeight: 1.5, color: 'var(--gray-on-dark-200)', textWrap: 'pretty' }}>{d.coverNote}</p>
          </React.Fragment>
        )}

        <div ref={stripRef} className="sm-highlights" onPointerDown={onStripDown} onPointerMove={onStripMove} onPointerUp={onStripUp} onPointerLeave={onStripUp} style={{ display: 'flex', gap: 10, marginTop: 32, overflowX: 'auto', paddingBottom: 6, WebkitOverflowScrolling: 'touch', cursor: 'grab', touchAction: 'pan-x pan-y' }}>
          {d.sections.map((it, i) => {
            const on = i === active;
            return (
              <button key={it.title} onClick={() => pickSection(i)} aria-current={on}
                className="sm-highlight-plate"
                style={{
                  /* Five plates share the row rather than scrolling off it, so the
                     whole offer is visible the moment the page opens. Below 900px
                     the stylesheet puts them back to a fixed width and the strip
                     scrolls again. */
                  position: 'relative', flex: '1 1 0', minWidth: 0, minHeight: 300,
                  border: 'none', cursor: 'pointer', textAlign: 'left',
                  borderRadius: 'var(--radius-card)', overflow: 'hidden',
                  background: it.thumb, display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
                  padding: 'clamp(20px, 2.2vw, 26px)', boxSizing: 'border-box',
                  boxShadow: on ? 'inset 0 0 0 3px var(--white)' : 'none',
                  opacity: on ? 1 : 0.82, transition: 'opacity .3s ease, box-shadow .3s ease, width .45s cubic-bezier(.22,1,.36,1)',
                }}>
                <span style={{ position: 'absolute', inset: 0, background: 'radial-gradient(120% 90% at 22% 8%, rgba(255,255,255,0.42) 0%, rgba(255,255,255,0) 52%), linear-gradient(to top, rgba(10,10,10,0.5) 0%, rgba(10,10,10,0) 55%)', pointerEvents: 'none' }} />
                <span className="sm-plate-num" style={{ position: 'relative', fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 46, lineHeight: 1, letterSpacing: '-0.04em', color: 'var(--white)', textShadow: '0 1px 14px rgba(0,0,0,0.28)' }}>{String(i + 1).padStart(2, '0')}</span>
                <span className="sm-plate-title" style={{ position: 'relative', fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: s.code === 'CZ' ? 18 : 22, lineHeight: 1.2, letterSpacing: '-0.015em', color: 'var(--white)', textShadow: '0 1px 16px rgba(0,0,0,0.4)' }}>{it.title}</span>
              </button>
            );
          })}
        </div>
        <div ref={viewportRef} onPointerDown={onBodyDown} onPointerMove={onBodyMove} onPointerUp={onBodyUp} onPointerCancel={onBodyUp} style={{ marginTop: 'clamp(32px, 5vh, 48px)', overflow: 'hidden', touchAction: 'pan-y', cursor: 'grab', height: trackH ? trackH : undefined, transition: 'height .4s cubic-bezier(.22,1,.36,1)' }}>
          <div ref={trackRef} style={{ display: 'flex', alignItems: 'flex-start', width: '100%', willChange: 'transform', transform: `translateX(${-active * 100}%)`, transition: 'transform .45s cubic-bezier(.22,1,.36,1)' }}>
            {d.sections.map((sc, si) => (
              <div key={sc.title} ref={(el) => { slideRefs.current[si] = el; }} aria-hidden={si !== active} style={{ flex: '0 0 100%', width: '100%', boxSizing: 'border-box' }}>
          <div ref={si === active ? contentRef : null} data-service-card="" style={{ position: 'relative', scrollMarginTop: 20 }}>
          <span aria-hidden="true" className="sm-ghost-num" style={{ position: 'absolute', right: 0, top: 0, fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(64px, 7.6vw, 116px)', lineHeight: 0.82, letterSpacing: '-0.05em', background: sc.thumb, WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent', opacity: 0.3, pointerEvents: 'none', userSelect: 'none' }}>{String(si + 1).padStart(2, '0')}</span>
          <span aria-hidden="true" style={{ display: 'block', width: 54, height: 3, borderRadius: 2, background: sc.thumb, marginBottom: 16 }} />
          <h3 style={{ margin: 0, paddingRight: 'clamp(0px, 11vw, 168px)', fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(24px, 3vw, 38px)', lineHeight: 1.08, letterSpacing: '-0.025em', color: 'var(--white)' }}>{sc.title}</h3>
          {sc.intro && <p style={{ margin: '16px 0 0', maxWidth: '78ch', fontFamily: 'var(--font-body)', fontSize: 16.5, lineHeight: 1.65, color: 'var(--gray-on-dark-200)' }}>{sc.intro}</p>}
          {sc.start && <p className="sm-mono" style={{ ...mono, color: 'var(--gray-on-dark-400)', margin: '18px 0 0' }}>{sc.start}</p>}
          <ServiceBullets items={sc.bullets} thumb={sc.thumb} mono={mono} more={d.bulletsMore} fewer={d.bulletsFewer} />
          </div>
              </div>
            ))}
          </div>
        </div>
        {(() => {
          const n = d.sections.length, prevI = (active + n - 1) % n, nextI = (active + 1) % n;
          const navBtn = { border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, minHeight: 44, fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: 9.5, letterSpacing: 'var(--tracking-label)', textTransform: 'uppercase', color: 'var(--gray-on-dark-400)' };
          return (
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, marginTop: 8, paddingTop: 6, borderTop: '1.5px solid var(--border-hairline-dark)' }}>
              <button onClick={() => goTo(prevI)} style={{ ...navBtn, textAlign: 'left' }} aria-label={d.sections[prevI].title}>← {d.sections[prevI].title}</button>
              <span className="sm-mono" style={{ ...mono, color: 'var(--gray-on-dark-500)', flexShrink: 0 }}>{String(active + 1).padStart(2, '0')} / {String(n).padStart(2, '0')}</span>
              <button onClick={() => goTo(nextI)} style={{ ...navBtn, textAlign: 'right' }} aria-label={d.sections[nextI].title}>{d.sections[nextI].title} →</button>
            </div>
          );
        })()}

        {/* Heavier break than a hairline: the reader has just come out of five
            long cards and needs to be told the argument has changed. */}
        <div aria-hidden="true" style={{ marginTop: 'clamp(56px, 9vh, 104px)', display: 'flex', alignItems: 'center', gap: 16 }}>
          <span style={{ width: 54, height: 3, borderRadius: 2, background: 'var(--gradient-hero)', flexShrink: 0 }} />
          <span style={{ flex: 1, height: 1.5, background: 'var(--border-hairline-dark)' }} />
        </div>
        <section className="sm-service-how" style={{ display: 'grid', gridTemplateColumns: '300px minmax(0, 1fr)', gap: 'clamp(24px, 4vw, 56px)', marginTop: 'clamp(36px, 5vh, 60px)', alignItems: 'start' }}>
          <h2 style={{ margin: 0, fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(26px, 3.4vw, 44px)', lineHeight: 1.06, letterSpacing: '-0.025em', color: 'var(--white)' }}>{d.howTitle}</h2>
          <HowAxis rows={d.how} />
        </section>

        {d.always && (
        <section className="sm-service-how" style={{ display: 'grid', gridTemplateColumns: '300px minmax(0, 1fr)', gap: 'clamp(24px, 4vw, 56px)', marginTop: 'clamp(48px, 8vh, 88px)', alignItems: 'start' }}>
          <div>
            <span aria-hidden="true" style={{ display: 'block', width: 54, height: 3, borderRadius: 2, background: 'var(--gradient-hero)', marginBottom: 16 }} />
            <h2 style={{ margin: 0, fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(26px, 3.4vw, 44px)', lineHeight: 1.06, letterSpacing: '-0.025em', color: 'var(--white)' }}>{d.alwaysTitle}</h2>
          </div>
          <div style={{ position: 'relative' }}>
            <span aria-hidden="true" className="sm-axis-tail" style={{
              position: 'absolute', left: 16, width: 2, borderRadius: 1,
              top: 'calc(-1 * clamp(48px, 8vh, 88px) - 26px)', height: 'calc(100% + clamp(48px, 8vh, 88px))',
              background: 'linear-gradient(to bottom, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.12) 45%, rgba(255,255,255,0) 100%)',
              pointerEvents: 'none',
            }} />
            {d.always.map(([k, v]) => (
              <div key={k} className="sm-how-row" style={{ position: 'relative', display: 'grid', gridTemplateColumns: '34px 200px minmax(0, 1fr)', gap: 22, padding: '14px 0' }}>
                <span aria-hidden="true" />
                <h3 style={{ margin: '5px 0 0', fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 18, lineHeight: 1.3, letterSpacing: '-0.015em', color: 'var(--white)' }}>{k}</h3>
                <p style={{ margin: '6px 0 0', fontFamily: 'var(--font-body)', fontSize: 16, lineHeight: 1.68, color: 'var(--gray-on-dark-200)', maxWidth: '54ch', textWrap: 'pretty' }}>{v}</p>
              </div>
            ))}
          </div>
        </section>
        )}

        {d.pullQuote && (
          <section className="sm-service-band" style={{ display: 'grid', gridTemplateColumns: '240px minmax(0, 1fr)', gap: 'clamp(24px, 4vw, 56px)', alignItems: 'center', marginTop: 'clamp(56px, 9vh, 104px)', paddingTop: 'clamp(32px, 5vh, 52px)', borderTop: '1.5px solid var(--border-hairline-dark)' }}>
            <img src="../../assets/photos/martin-byline.png" alt="" width="240" height="240" style={{ display: 'block', width: '100%', height: 'auto', borderRadius: 'var(--radius-card)' }} />
            <blockquote style={{ margin: 0 }}>
              <p style={{ margin: 0, maxWidth: '28ch', fontFamily: 'var(--font-display)', fontWeight: 500, fontStyle: 'italic', fontSize: 'clamp(22px, 2.9vw, 38px)', lineHeight: 1.16, letterSpacing: '-0.025em', color: 'var(--white)', textWrap: 'balance' }}>{d.pullQuote}</p>
            </blockquote>
          </section>
        )}
        <section className="sm-service-how" style={{ display: 'grid', gridTemplateColumns: '300px minmax(0, 1fr)', gap: 'clamp(24px, 4vw, 56px)', marginTop: 'clamp(56px, 9vh, 104px)', alignItems: 'center' }}>
          <div>
            <h2 style={{ margin: 0, fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(26px, 3.4vw, 44px)', lineHeight: 1.06, letterSpacing: '-0.025em', color: 'var(--white)' }}>{d.noTitle}</h2>
            <p style={{ margin: '14px 0 0', fontFamily: 'var(--font-body)', fontSize: 15.5, lineHeight: 1.6, color: 'var(--gray-on-dark-400)' }}>{d.noNote}</p>
          </div>
          <ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
            {d.no.map((n) => (
              <li key={n} style={{ display: 'flex', alignItems: 'flex-start', gap: 14, padding: '9px 0', fontFamily: 'var(--font-body)', fontSize: 16.5, lineHeight: 1.6, color: 'var(--gray-on-dark-200)' }}>
                <span style={{ width: 11, height: 1.5, flexShrink: 0, marginTop: 12, background: 'var(--gray-on-dark-500)' }} />
                <span>{n}</span>
              </li>
            ))}
            <li style={{ listStyle: 'none' }} />
          </ul>
        </section>

        {d.ladder && (
        <section style={{ marginTop: 'clamp(56px, 9vh, 104px)' }}>
          <h2 style={{ margin: 0, fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(26px, 3.4vw, 44px)', lineHeight: 1.06, letterSpacing: '-0.025em', color: 'var(--white)' }}>{d.ladderTitle}</h2>
          <p style={{ margin: '16px 0 0', maxWidth: '52ch', fontFamily: 'var(--font-body)', fontSize: 17, lineHeight: 1.65, color: 'var(--gray-on-dark-200)' }}>{String(d.ladderNote).split('\n').map((line, i) => (<React.Fragment key={i}>{i > 0 && ' '}{i > 0 && <br className="sm-br-wide" />}{line}</React.Fragment>))}</p>
          <div className="sm-ladder" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 'clamp(24px, 3vw, 40px)', marginTop: 32 }}>
            {d.ladder.map((it, i) => <LadderCard key={it.name} item={it} index={i + 1} mono={mono} />)}
          </div>
        </section>
        )}

        </div>
        {d.closeHeading ? (
          <div className="sm-cta-slot" style={{ marginTop: 'clamp(88px, 17vh, 190px)', height: ctaBox ? ctaBox.height : 'auto' }}>
          <div ref={ctaRef} className="sm-service-cta" style={{ position: 'relative', overflow: 'hidden', background: 'var(--white)', borderRadius: 'var(--radius-card)', padding: ctaFull ? 'clamp(36px, 7vw, 96px)' : 'clamp(36px, 5vw, 64px)', ...ctaStyle }}>
            {ctaFull && (
              <button type="button" onClick={closeCta} aria-label={d.ctaClose || 'Zavřít'} title={d.ctaClose || 'Zavřít'}
                onMouseEnter={(e) => { e.currentTarget.firstChild.style.background = 'var(--black)'; e.currentTarget.dataset.h = '1'; }}
                onMouseLeave={(e) => { e.currentTarget.firstChild.style.background = 'rgba(10,10,10,0.05)'; e.currentTarget.dataset.h = ''; }}
                style={{
                  position: 'absolute', top: 'clamp(18px, 2vw, 30px)', left: 'clamp(18px, 2vw, 30px)', zIndex: 3,
                  display: 'flex', alignItems: 'center', gap: 10, minHeight: 44,
                  border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, pointerEvents: 'auto',
                  opacity: atEnd ? 1 : 0, transition: reduceEnd ? 'none' : 'opacity 260ms linear 700ms',
                }}>
                <span style={{ width: 44, height: 44, borderRadius: 'var(--radius-card)', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(10,10,10,0.05)', transition: 'background .25s ease' }}>
                  <Icon name="arrow-up-left" size={16} color="var(--black)" />
                </span>
              </button>
            )}
            {/* Arrives once the page is at the bottom and brings the second
                route with it: the button next to it writes, this one opens
                WhatsApp. */}
            <a className="sm-cta-call" href={`https://wa.me/${CONTACT.whatsapp.replace(/[^\d]/g, '')}`}
              target="_blank" rel="noopener noreferrer"
              aria-label={`${d.callLabel}: ${CONTACT.phone}`} style={{
              position: 'absolute', top: 0, right: 0, bottom: 0, width: 'clamp(104px, 13vw, 152px)', pointerEvents: 'auto',
              background: 'var(--orange-500)', textDecoration: 'none', display: 'flex', flexDirection: 'column',
              alignItems: 'center', justifyContent: 'center', gap: 14,
              clipPath: atEnd ? 'inset(0 0 0 0)' : 'inset(0 0 100% 0)',
              transition: reduceEnd ? 'none' : `clip-path ${atEnd ? 720 : 240}ms cubic-bezier(.65,0,.35,1) ${atEnd ? 260 : 0}ms`,
            }}>
              <Icon name="whatsapp" size={34} color="#0a0a0a" />
              <span className="sm-mono" style={{ ...mono, color: '#0a0a0a', writingMode: 'vertical-rl', transform: 'rotate(180deg)', opacity: atEnd ? 1 : 0, transition: reduceEnd ? 'none' : `opacity 320ms linear ${atEnd ? 820 : 0}ms` }}>{d.callLabel}</span>
            </a>
            <h2 style={{ margin: 0, maxWidth: '24ch', fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(30px, 4vw, 52px)', lineHeight: 1.04, letterSpacing: '-0.025em', color: 'var(--black)', textWrap: 'balance' }}>{d.closeHeading}</h2>
            <p style={{ margin: '20px 0 32px', maxWidth: '46ch', fontFamily: 'var(--font-body)', fontSize: 17.5, lineHeight: 1.65, color: 'var(--gray-700)', textWrap: 'pretty' }}>{d.closeNote}</p>
            <span style={{ pointerEvents: 'auto' }}><Button variant="pill-dark" size="md" onClick={onContact}>{d.ctaBtn}</Button></span>
            <div className="sm-cta-reach" style={{ pointerEvents: 'auto', alignSelf: 'stretch', marginTop: 32, paddingTop: 22, borderTop: '1.5px solid rgba(10,10,10,0.16)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24, flexWrap: 'wrap' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 24, flexWrap: 'wrap' }}>
                <a href={`mailto:${CONTACT.email}`} style={svcReachLink}>
                  <Icon name="mail" size={15} color="var(--black)" />{CONTACT.email}
                </a>
                <a href={`tel:${CONTACT.whatsapp}`} style={svcReachLink}>
                  <Icon name="phone" size={15} color="var(--black)" />{CONTACT.phone}
                </a>
              </div>
            </div>
          </div>
          </div>
        ) : (
        <div className="sm-service-close" style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 320px', gap: 48, marginTop: 'clamp(56px, 9vh, 104px)', alignItems: 'start' }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
            {d.closing.map((p, i) => (
              <p key={i} style={{ margin: 0, fontFamily: 'var(--font-body)', fontSize: 17.5, lineHeight: 1.7, color: 'var(--gray-on-dark-200)', maxWidth: '58ch', textWrap: 'pretty', whiteSpace: 'normal' }}>{String(p).split('\n').map((line, k) => (<React.Fragment key={k}>{k > 0 && ' '}{k > 0 && <br className="sm-br-wide" />}{line}</React.Fragment>))}</p>
            ))}
            <p style={{ margin: '6px 0 0', fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 22, lineHeight: 1.4, letterSpacing: '-0.015em', color: 'var(--white)', textWrap: 'balance' }}><span className="sm-closing-link" role="link" tabIndex={0} onClick={onContact} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onContact(); } }}>{d.closingBold}<span className="sm-closing-arrow" aria-hidden="true">&#8594;</span></span></p>
          </div>
          <div style={{ position: 'relative', overflow: 'hidden', borderRadius: 'var(--radius-card)', background: 'var(--gradient-hero)', padding: '28px 26px' }}>
            <div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(120% 90% at 22% 8%, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0) 52%), linear-gradient(to top, rgba(10,10,10,0.45) 0%, rgba(10,10,10,0) 60%)', pointerEvents: 'none' }} />
            <div style={{ position: 'relative' }}>
              <p style={{ ...mono, color: 'rgba(255,255,255,0.85)', margin: 0 }}>{t.canIHelp}</p>
              <p style={{ margin: '14px 0 22px', fontFamily: 'var(--font-body)', fontSize: 15.5, lineHeight: 1.6, color: 'var(--white)' }}>{d.ctaNote}</p>
              <Button variant="pill-on-dark" size="md" onClick={onContact}>{t.contactMe}</Button>
            </div>
          </div>
        </div>
        )}
      </div>
      </div>
    </div>
  );
}

Object.assign(window, { ServicesDetail });
