// templates.jsx — 16°S social-post templates, 5 systems × 4 aspect ratios
// Each template adapts to its container size via inline CSS vars so the same
// component renders crisp at 1:1, 4:5, 9:16 and 16:9.
//
// Photo areas are <image-slot> drop zones. Slots within a single template
// system share an id, so dropping one image fills all four aspect-ratio
// previews for that system at once.

// Pull MediaSlot from window (declared in media-slot.jsx, loaded before
// this file). Each Babel <script> gets its own scope, so we have to alias.
const MediaSlot = window.MediaSlot;
const Rise = window.Rise;
const Scramble = window.Scramble;
const AutoFitText = window.AutoFitText;
const ResizeHandle = window.ResizeHandle;

const SAMPLES = {
  coords: {
    location: 'Valencia',
    region: 'España',
    lat: '39°28′12″N',
    lng: '0°22′04″W',
    client: 'Mercedes-AMG',
    project: 'F1 Pre-Season Test',
    date: 'May 2026',
    frame: 'A_047',
  },
  callsheet: {
    location: 'Porto Cervo',
    region: 'Sardinia',
    lat: '41°08′25″N',
    lng: '9°31′58″E',
    client: 'Oyster Yachts',
    project: 'Regatta',
    date: 'Sep 2025',
    day: 'Day 03',
    frame: 'A_0214',
  },
  parallel: {
    location: 'Cape Town',
    lat: '33°55′S',
    lng: '18°25′E',
    client: 'Volvo Ocean Race',
    project: 'Leg 3',
    headline: 'Open\nsea.',
    date: '2025',
  },
  bigtype: {
    line1: 'Built',
    line2: 'for the',
    accent: 'chase.',
    client: 'Porsche Motorsport',
    location: 'Le Mans',
    coords: '47°57′N · 0°12′E',
    date: '24h · 2026',
  },
  weather: {
    location: 'Southern Ocean',
    lat: '50°02′S',
    lng: '30°14′W',
    wind: '38',
    dir: 'NNW',
    sea: '6.2m',
    event: 'Clipper Round-the-World',
    day: 'Day 47',
    client: 'Clipper Race',
    project: 'No Going Back',
    date: '2024',
  },
};

// ──────────────────────────────────────────────────────────────────────────
// useDraggable — generic drag-to-reposition hook with localStorage persistence.
// Position stored as percentage of container so it survives across aspect
// ratios and renders identically at export resolution.
// ──────────────────────────────────────────────────────────────────────────
function useDraggable(storageKey, defaultPos = { x: 30, y: 50 }) {
  const [pos, setPos] = React.useState(() => {
    if (!storageKey || typeof localStorage === 'undefined') return defaultPos;
    try {
      const v = localStorage.getItem(storageKey);
      if (v) return JSON.parse(v);
    } catch (e) {}
    return defaultPos;
  });
  // Persist
  React.useEffect(() => {
    if (!storageKey) return;
    try { localStorage.setItem(storageKey, JSON.stringify(pos)); } catch (e) {}
  }, [pos, storageKey]);
  // If the default changes (storageKey switch via prop change), re-read.
  React.useEffect(() => {
    if (!storageKey) return;
    try {
      const v = localStorage.getItem(storageKey);
      setPos(v ? JSON.parse(v) : defaultPos);
    } catch (e) { setPos(defaultPos); }
  }, [storageKey]);
  const [dragging, setDragging] = React.useState(false);

  const onPointerDown = (e) => {
    // Only respond to the primary button.
    if (e.button != null && e.button !== 0) return;
    e.preventDefault();
    e.stopPropagation();
    const handle = e.currentTarget;
    const container = handle.offsetParent || handle.parentElement;
    if (!container) return;
    const containerRect = container.getBoundingClientRect();
    const handleRect = handle.getBoundingClientRect();
    // Where inside the card was the click, relative to the card's center?
    const offsetFromCenterX = e.clientX - (handleRect.left + handleRect.width / 2);
    const offsetFromCenterY = e.clientY - (handleRect.top  + handleRect.height / 2);
    setDragging(true);

    const onMove = (me) => {
      // New center position in container px → convert to %.
      const cx = me.clientX - offsetFromCenterX - containerRect.left;
      const cy = me.clientY - offsetFromCenterY - containerRect.top;
      // Clamp center so the card stays mostly inside the frame.
      const halfWPct = (handleRect.width  / 2) / containerRect.width  * 100;
      const halfHPct = (handleRect.height / 2) / containerRect.height * 100;
      const xPct = (cx / containerRect.width)  * 100;
      const yPct = (cy / containerRect.height) * 100;
      setPos({
        x: Math.max(halfWPct, Math.min(100 - halfWPct, xPct)),
        y: Math.max(halfHPct, Math.min(100 - halfHPct, yPct)),
      });
    };
    const onUp = () => {
      setDragging(false);
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
  };

  const reset = React.useCallback(() => {
    setPos(defaultPos);
    if (storageKey) {
      try { localStorage.removeItem(storageKey); } catch (e) {}
    }
  }, [storageKey, defaultPos.x, defaultPos.y]);

  return { pos, dragging, onPointerDown, reset };
}

// Compute a stable storage suffix per ratio so each aspect can remember its
// own position.
function ratioKey(w, h) {
  const r = w / h;
  if (Math.abs(r - 1)       < 0.05) return '1x1';
  if (Math.abs(r - 4/5)     < 0.05) return '4x5';
  if (Math.abs(r - 9/16)    < 0.05) return '9x16';
  if (Math.abs(r - 16/9)    < 0.05) return '16x9';
  return r > 1 ? 'wide' : 'tall';
}
window.useDraggable = useDraggable;
window.ratioKey = ratioKey;

// ─────────────────────────────────────────────────────────────────────────
// Shared bits
// ─────────────────────────────────────────────────────────────────────────

function TopoSvg({ opacity = 0.18, color = '#61D4F2' }) {
  // Inline isobar-style wavy lines + concentric pressure system, lifted from
  // the 16ds-site weather chart aesthetic. Scales with container.
  return (
    <svg
      viewBox="0 0 600 600"
      preserveAspectRatio="xMidYMid slice"
      style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', opacity, pointerEvents: 'none' }}
      aria-hidden="true"
    >
      <g fill="none" stroke={color} strokeWidth="1">
        {/* concentric pressure system */}
        <circle cx="300" cy="300" r="40" />
        <circle cx="300" cy="300" r="80" />
        <circle cx="300" cy="300" r="130" />
        <circle cx="300" cy="300" r="190" />
        <circle cx="300" cy="300" r="260" />
        {/* isobar waves */}
        <path d="M -50 100 Q 100 60 220 110 T 480 120 T 700 90" />
        <path d="M -50 170 Q 120 130 250 180 T 500 190 T 700 160" />
        <path d="M -50 470 Q 100 430 220 480 T 480 490 T 700 460" />
        <path d="M -50 540 Q 120 500 250 550 T 500 560 T 700 530" />
      </g>
    </svg>
  );
}

function PhotoSlot({ id, placeholder = 'Drop a photo or video frame', mediaKind = 'photo' }) {
  // Branches on mediaKind. When 'video', renders the <media-slot> drop zone
  // (auto-loop muted preview); otherwise the existing <image-slot>.
  if (mediaKind === 'video') {
    return (
      <MediaSlot
        id={`${id}--vid`}
        placeholder="Drop a video (mp4 / mov / webm)"
        style={{ background: 'transparent' }}
      />
    );
  }
  return (
    <image-slot
      id={id}
      shape="rect"
      placeholder={placeholder}
      style={{
        position: 'absolute',
        inset: 0,
        width: '100%',
        height: '100%',
        background: 'linear-gradient(135deg, #02102B 0%, #0F6D96 60%, #001172 100%)',
        '--is-bg': 'transparent',
      }}
    ></image-slot>
  );
}

// Bottom-darken gradient so headline type stays readable on any photo.
function BottomGrad({ from = 0.45, opacity = 0.92 }) {
  return (
    <div
      style={{
        position: 'absolute',
        inset: 0,
        background: `linear-gradient(180deg, rgba(2,16,43,0) ${from * 100}%, rgba(2,16,43,${opacity}) 100%)`,
        pointerEvents: 'none',
      }}
    />
  );
}

// Per-template card chrome — provides the relative wrapper, font defaults, and
// the unit (rem-like) `--u` so a single number sizes everything proportionally.
function Card({ w, h, children, style }) {
  // Pick a unit so all type scales with the smaller dimension. 1080px-base
  // displays would use ~10.8px = 1 unit. Our display sizes vary so we derive
  // from min dimension.
  const u = Math.min(w, h) / 100;
  return (
    <div
      style={{
        position: 'relative',
        width: w,
        height: h,
        background: 'var(--navy-deep)',
        color: 'var(--cream)',
        overflow: 'hidden',
        '--u': `${u}px`,
        fontFamily: 'var(--font-body)',
        ...style,
      }}
    >
      {children}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// TEMPLATE 01 — COORDINATES
// Full-bleed photo, minimal slate annotations at corners.
// ─────────────────────────────────────────────────────────────────────────
function TplCoordinates({ w, h, slotId = 'tpl-coords', data }) {
  const d = { ...SAMPLES.coords, ...(data || {}) };
  const u = Math.min(w, h) / 100;
  const isWide = w / h > 1.2;
  return (
    <Card w={w} h={h}>
      <PhotoSlot id={slotId} placeholder="Drop a photo" mediaKind={d.mediaKind} />
      {/* Topo overlay on photo */}
      <div style={{ position: 'absolute', inset: 0, mixBlendMode: 'screen', opacity: 0.4, pointerEvents: 'none' }}>
        <TopoSvg opacity={0.5} />
      </div>
      <BottomGrad from={0.35} />
      {/* Top edge — 16°S monogram + // CLIENT */}
      <div style={{
        position: 'absolute', top: u * 5, left: u * 5, right: u * 5,
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        zIndex: 2,
      }}>
        <Rise enabled={d.mediaKind === 'video'} delay={0} style={{
          fontFamily: 'var(--font-display)', color: 'var(--blue-light)',
          fontSize: u * 5.5, letterSpacing: '-0.02em', lineHeight: 1,
        }}>16°S</Rise>
        <Rise enabled={d.mediaKind === 'video'} delay={0.15} style={{
          fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
          fontSize: u * 2.2, fontWeight: 600, letterSpacing: '0.18em',
          textTransform: 'uppercase',
        }}>// {d.client}</Rise>
      </div>
      {/* Top-right — coords readout */}
      <div style={{
        position: 'absolute', top: u * 14, right: u * 5,
        textAlign: 'right', zIndex: 2,
        fontFamily: 'var(--font-brand)', fontWeight: 500,
        color: 'var(--cream)', fontSize: u * 2.4, letterSpacing: '0.08em',
        lineHeight: 1.5,
      }}>
        <Rise enabled={d.mediaKind === 'video'} delay={0.35} style={{ color: 'var(--blue-light)', fontSize: u * 1.8, marginBottom: u * 0.5, display: 'block' }}>// COORDINATES</Rise>
        <div><Scramble enabled={d.mediaKind === 'video'} value={d.lat} delay={0.5} duration={0.9} /></div>
        <div><Scramble enabled={d.mediaKind === 'video'} value={d.lng} delay={0.7} duration={0.9} /></div>
      </div>
      {/* Bottom-left — location in Heavitas */}
      <div style={{
        position: 'absolute', bottom: u * 5, left: u * 5, right: u * 5,
        zIndex: 2,
      }}>
        <Rise enabled={d.mediaKind === 'video'} delay={0.25} style={{
          fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
          fontSize: u * 2.2, fontWeight: 600, letterSpacing: '0.18em',
          textTransform: 'uppercase', marginBottom: u * 1.5,
          display: 'block',
        }}>// {d.project} · {d.date}</Rise>
        <Rise enabled={d.mediaKind === 'video'} delay={0.55} style={{
          fontFamily: 'var(--font-display)', color: 'var(--cream)',
          lineHeight: 0.92,
          letterSpacing: '-0.02em', textTransform: 'uppercase',
          display: 'block',
        }}>
          <AutoFitText baseSize={isWide ? u * 11 : u * 13}>
            {d.location}<span style={{ color: 'var(--blue-light)' }}>.</span>
          </AutoFitText>
        </Rise>
        <Rise enabled={d.mediaKind === 'video'} delay={0.85} style={{
          fontFamily: 'var(--font-brand)', color: 'var(--grey)',
          fontSize: u * 2.4, fontWeight: 500, letterSpacing: '0.16em',
          textTransform: 'uppercase', marginTop: u * 1.5,
          display: 'block',
        }}>{d.region}</Rise>
      </div>
    </Card>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// TEMPLATE 02 — CALL SHEET
// Photo on top portion, navy slate strip beneath with metadata grid.
// ─────────────────────────────────────────────────────────────────────────
function TplCallSheet({ w, h, slotId = 'tpl-callsheet', data }) {
  const d = { ...SAMPLES.callsheet, ...(data || {}) };
  const u = Math.min(w, h) / 100;
  const ratio = w / h;
  // Strip layout adapts: wide (16:9) = strip on right; tall (9:16) = strip
  // bottom 28%; square/portrait = strip bottom 32%.
  const stripOnRight = ratio > 1.3;
  const stripSize = stripOnRight ? '38%' : ratio > 0.9 ? '32%' : '30%';
  return (
    <Card w={w} h={h}>
      {/* Photo area */}
      <div style={{
        position: 'absolute',
        top: 0,
        left: 0,
        right: stripOnRight ? stripSize : 0,
        bottom: stripOnRight ? 0 : stripSize,
      }}>
        <PhotoSlot id={slotId} placeholder="Drop a frame" mediaKind={d.mediaKind} />
        {/* Topo overlay on photo */}
        <div style={{ position: 'absolute', inset: 0, mixBlendMode: 'screen', opacity: 0.4, pointerEvents: 'none' }}>
          <TopoSvg opacity={0.5} />
        </div>
      </div>
      {/* Slate strip */}
      <div style={{
        position: 'absolute',
        ...(stripOnRight
          ? { top: 0, right: 0, bottom: 0, width: stripSize }
          : { left: 0, right: 0, bottom: 0, height: stripSize }),
        background: 'var(--navy-deep)',
        borderTop: stripOnRight ? 'none' : '1px solid var(--blue-light)',
        borderLeft: stripOnRight ? '1px solid var(--blue-light)' : 'none',
        overflow: 'hidden',
      }}>
        <TopoSvg opacity={0.1} />
        <div style={{
          position: 'absolute', inset: 0,
          padding: u * 4,
          display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
          zIndex: 2,
        }}>
          {/* Top row — slate ID */}
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            fontFamily: 'var(--font-brand)', fontWeight: 600,
            fontSize: u * 2, letterSpacing: '0.18em', textTransform: 'uppercase',
          }}>
            <Rise enabled={d.mediaKind === 'video'} delay={0} style={{ color: 'var(--blue-light)' }}>// 16°S CONTENT FACTORY</Rise>
            <Rise enabled={d.mediaKind === 'video'} delay={0.1} style={{ color: 'var(--grey)' }}>{d.frame}</Rise>
          </div>
          {/* Middle — big location */}
          <div style={{ marginTop: u * 2 }}>
            <Rise enabled={d.mediaKind === 'video'} delay={0.2} style={{
              fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
              fontSize: u * 2, fontWeight: 600, letterSpacing: '0.18em',
              textTransform: 'uppercase', marginBottom: u * 1.5,
              display: 'block',
            }}>// LOCATION</Rise>
            <Rise enabled={d.mediaKind === 'video'} delay={0.3} style={{
              fontFamily: 'var(--font-display)', color: 'var(--cream)',
              lineHeight: 0.95,
              letterSpacing: '-0.02em', textTransform: 'uppercase',
              display: 'block',
            }}>
              <AutoFitText baseSize={stripOnRight ? u * 7 : u * 8.5}>
                {d.location}<span style={{ color: 'var(--blue-light)' }}>.</span>
              </AutoFitText>
            </Rise>
            <Rise enabled={d.mediaKind === 'video'} delay={0.4} style={{
              fontFamily: 'var(--font-brand)', color: 'var(--grey)',
              fontSize: u * 2.2, fontWeight: 500, letterSpacing: '0.14em',
              textTransform: 'uppercase', marginTop: u * 1.2,
              display: 'block',
            }}>{d.region}</Rise>
          </div>
          {/* Bottom — metadata grid */}
          <div style={{
            display: 'grid',
            gridTemplateColumns: stripOnRight ? '1fr' : '1fr 1fr',
            gap: u * 2.5,
            paddingTop: u * 3,
            borderTop: '1px solid var(--line)',
          }}>
            <Meta label="// CLIENT" value={d.client} u={u} animate={d.mediaKind === 'video'} delay={0.5} />
            <Meta label="// COORDINATES" value={`${d.lat}\n${d.lng}`} u={u} mono animate={d.mediaKind === 'video'} delay={0.65} />
            <Meta label="// PROJECT" value={`${d.project} · ${d.day}`} u={u} animate={d.mediaKind === 'video'} delay={0.8} />
            <Meta label="// DATE" value={d.date} u={u} animate={d.mediaKind === 'video'} delay={0.95} />
          </div>
        </div>
      </div>
    </Card>
  );
}

function Meta({ label, value, u, mono, animate, delay = 0 }) {
  return (
    <div>
      <Rise enabled={animate} delay={delay} style={{
        fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
        fontSize: u * 1.7, fontWeight: 600, letterSpacing: '0.18em',
        textTransform: 'uppercase', marginBottom: u * 0.6,
        display: 'block',
      }}>{label}</Rise>
      <Rise enabled={animate} delay={delay + 0.1} style={{
        fontFamily: mono ? 'var(--font-brand)' : 'var(--font-brand)',
        color: 'var(--cream)', fontSize: u * 2.2, fontWeight: 500,
        letterSpacing: mono ? '0.06em' : '0.04em',
        textTransform: 'uppercase', whiteSpace: 'pre-line', lineHeight: 1.35,
        display: 'block',
      }}>{value}</Rise>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// TEMPLATE 03 — PARALLEL
// Photo full-bleed, dashed cyan latitude line cuts across with lat/long at
// endpoints. Echoes the 16°S parallel motif from the site.
// ─────────────────────────────────────────────────────────────────────────
function TplParallel({ w, h, slotId = 'tpl-parallel', data }) {
  const d = { ...SAMPLES.parallel, ...(data || {}) };
  const u = Math.min(w, h) / 100;
  const isWide = w / h > 1.2;
  return (
    <Card w={w} h={h}>
      <PhotoSlot id={slotId} placeholder="Drop a photo" mediaKind={d.mediaKind} />
      {/* Topo overlay on photo */}
      <div style={{ position: 'absolute', inset: 0, mixBlendMode: 'screen', opacity: 0.4, pointerEvents: 'none' }}>
        <TopoSvg opacity={0.5} />
      </div>
      {/* Top vignette to keep top metadata readable */}
      <div style={{
        position: 'absolute', inset: 0,
        background: 'linear-gradient(180deg, rgba(2,16,43,0.6) 0%, rgba(2,16,43,0) 25%, rgba(2,16,43,0) 55%, rgba(2,16,43,0.85) 100%)',
        pointerEvents: 'none',
      }} />
      {/* Dashed latitude line cutting across, ~50% down */}
      <div style={{
        position: 'absolute', left: 0, right: 0, top: '52%',
        height: 1, borderTop: '1.5px dashed var(--blue-light)',
        opacity: 0.85,
        zIndex: 2,
      }} />
      {/* Lat label at left endpoint */}
      <Rise enabled={d.mediaKind === 'video'} delay={0.35} style={{
        position: 'absolute', left: u * 4, top: 'calc(52% - ' + (u * 4.5) + 'px)',
        fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
        fontSize: u * 2.2, fontWeight: 600, letterSpacing: '0.16em',
        textTransform: 'uppercase', zIndex: 2,
        background: 'rgba(2,16,43,0.7)', padding: `${u * 0.6}px ${u * 1.2}px`,
      }}><Scramble enabled={d.mediaKind === 'video'} value={d.lat} delay={0.5} duration={0.9} /></Rise>
      {/* Long label at right endpoint */}
      <Rise enabled={d.mediaKind === 'video'} delay={0.45} style={{
        position: 'absolute', right: u * 4, top: 'calc(52% - ' + (u * 4.5) + 'px)',
        fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
        fontSize: u * 2.2, fontWeight: 600, letterSpacing: '0.16em',
        textTransform: 'uppercase', zIndex: 2,
        background: 'rgba(2,16,43,0.7)', padding: `${u * 0.6}px ${u * 1.2}px`,
      }}><Scramble enabled={d.mediaKind === 'video'} value={d.lng} delay={0.6} duration={0.9} /></Rise>
      {/* Tiny tick + parallel label center */}
      <Rise enabled={d.mediaKind === 'video'} delay={0.7} style={{
        position: 'absolute', left: '50%', top: '52%', transform: 'translate(-50%, -50%)',
        fontFamily: 'var(--font-brand)', color: 'var(--navy-deep)',
        background: 'var(--blue-light)',
        fontSize: u * 1.8, fontWeight: 700, letterSpacing: '0.18em',
        textTransform: 'uppercase', padding: `${u * 0.5}px ${u * 1.2}px`,
        zIndex: 3,
      }}>{d.lat.replace(/′.*/, '′')} PARALLEL</Rise>
      {/* Top — eyebrow */}
      <div style={{
        position: 'absolute', top: u * 5, left: u * 5, right: u * 5,
        display: 'flex', justifyContent: 'space-between', zIndex: 2,
        fontFamily: 'var(--font-brand)', fontWeight: 600,
        fontSize: u * 2.2, letterSpacing: '0.18em', textTransform: 'uppercase',
      }}>
        <Rise enabled={d.mediaKind === 'video'} delay={0} style={{ color: 'var(--blue-light)' }}>// {d.client}</Rise>
        <Rise enabled={d.mediaKind === 'video'} delay={0.1} style={{ color: 'var(--cream)' }}>{d.project} · {d.date}</Rise>
      </div>
      {/* Bottom — headline + location */}
      <div style={{
        position: 'absolute', bottom: u * 5, left: u * 5, right: u * 5,
        zIndex: 2,
      }}>
        <Rise enabled={d.mediaKind === 'video'} delay={0.85} as="div" style={{
          fontFamily: 'var(--font-display)', color: 'var(--cream)',
          lineHeight: 0.92,
          letterSpacing: '-0.02em', textTransform: 'uppercase',
          display: 'block',
        }}>
          {(() => {
            const lines = (d.headline || '').split('\n');
            const main = lines.slice(0, -1);
            const accent = lines[lines.length - 1] || '';
            const baseSize = isWide ? u * 13 : u * 16;
            return (
              <>
                {main.map((line, i) => line ? (
                  <AutoFitText key={i} baseSize={baseSize}>{line}</AutoFitText>
                ) : null)}
                {accent ? (
                  <AutoFitText baseSize={baseSize} style={{ color: 'var(--blue-light)' }}>{accent}</AutoFitText>
                ) : null}
              </>
            );
          })()}
        </Rise>
        <Rise enabled={d.mediaKind === 'video'} delay={1.05} style={{
          marginTop: u * 2,
          fontFamily: 'var(--font-brand)', color: 'var(--grey)',
          fontSize: u * 2.2, fontWeight: 500, letterSpacing: '0.14em',
          textTransform: 'uppercase', display: 'block',
        }}>{d.location}</Rise>
      </div>
    </Card>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// TEMPLATE 04 — BIG TYPE
// Asymmetric split: navy + topo panel with stacked headline on one side,
// photo on the other. Wide → text left, photo right.
// Tall → photo top, text bottom.
// ─────────────────────────────────────────────────────────────────────────
function TplBigType({ w, h, slotId = 'tpl-bigtype', data }) {
  const d = { ...SAMPLES.bigtype, ...(data || {}) };
  const u = Math.min(w, h) / 100;
  const ratio = w / h;
  const layout = ratio > 1.2 ? 'horiz' : ratio < 0.85 ? 'vert' : 'vert';
  const photoFrac = layout === 'horiz' ? 0.55 : 0.5;

  return (
    <Card w={w} h={h}>
      {/* Photo half */}
      <div style={{
        position: 'absolute',
        ...(layout === 'horiz'
          ? { right: 0, top: 0, bottom: 0, width: `${photoFrac * 100}%` }
          : { top: 0, left: 0, right: 0, height: `${photoFrac * 100}%` }),
      }}>
        <PhotoSlot id={slotId} placeholder="Drop a photo" mediaKind={d.mediaKind} />
      </div>
      {/* Type half */}
      <div style={{
        position: 'absolute',
        ...(layout === 'horiz'
          ? { left: 0, top: 0, bottom: 0, width: `${(1 - photoFrac) * 100}%` }
          : { bottom: 0, left: 0, right: 0, height: `${(1 - photoFrac) * 100}%` }),
        background: 'var(--navy-deep)',
        overflow: 'hidden',
      }}>
        <TopoSvg opacity={0.22} />
        <div style={{
          position: 'absolute', inset: 0, padding: u * 5,
          display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
          zIndex: 2,
        }}>
          {/* Top eyebrow */}
          <Rise enabled={d.mediaKind === 'video'} delay={0} style={{
            fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
            fontSize: u * 2.2, fontWeight: 600, letterSpacing: '0.18em',
            textTransform: 'uppercase', display: 'block',
          }}>// {d.client}</Rise>
          {/* Stacked headline */}
          <div style={{
            fontFamily: 'var(--font-display)', color: 'var(--cream)',
            lineHeight: 0.92,
            letterSpacing: '-0.025em', textTransform: 'uppercase',
          }}>
            {d.line1 ? (
              <Rise enabled={d.mediaKind === 'video'} delay={0.2} as="div">
                <AutoFitText baseSize={layout === 'horiz' ? u * 14 : u * 17}>{d.line1}</AutoFitText>
              </Rise>
            ) : null}
            {d.line2 ? (
              <Rise enabled={d.mediaKind === 'video'} delay={0.35} as="div">
                <AutoFitText baseSize={layout === 'horiz' ? u * 14 : u * 17}>{d.line2}</AutoFitText>
              </Rise>
            ) : null}
            {d.accent ? (
              <Rise enabled={d.mediaKind === 'video'} delay={0.5} as="div" style={{ color: 'var(--blue-light)' }}>
                <AutoFitText baseSize={layout === 'horiz' ? u * 14 : u * 17}>{d.accent}</AutoFitText>
              </Rise>
            ) : null}
          </div>
          {/* Bottom meta */}
          <div style={{
            display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end',
            gap: u * 2,
            paddingTop: u * 2, borderTop: '1px solid var(--line)',
          }}>
            <div>
              <Rise enabled={d.mediaKind === 'video'} delay={0.75} style={{
                fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
                fontSize: u * 1.7, fontWeight: 600, letterSpacing: '0.18em',
                textTransform: 'uppercase', marginBottom: u * 0.6,
                display: 'block',
              }}>// LOCATION</Rise>
              <Rise enabled={d.mediaKind === 'video'} delay={0.85} style={{
                fontFamily: 'var(--font-brand)', color: 'var(--cream)',
                fontSize: u * 2.2, fontWeight: 500, letterSpacing: '0.10em',
                textTransform: 'uppercase', display: 'block',
              }}>{d.location}</Rise>
            </div>
            <div style={{ textAlign: 'right' }}>
              <Rise enabled={d.mediaKind === 'video'} delay={0.9} style={{
                fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
                fontSize: u * 1.7, fontWeight: 600, letterSpacing: '0.18em',
                textTransform: 'uppercase', marginBottom: u * 0.6,
                display: 'block',
              }}>// FRAME</Rise>
              <Rise enabled={d.mediaKind === 'video'} delay={1.0} style={{
                fontFamily: 'var(--font-brand)', color: 'var(--cream)',
                fontSize: u * 2.2, fontWeight: 500, letterSpacing: '0.10em',
                textTransform: 'uppercase', display: 'block',
              }}>{d.date}</Rise>
            </div>
          </div>
        </div>
      </div>
    </Card>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// TEMPLATE 05 — WEATHER SLATE
// Photo + technical telemetry readout (wind, bearing, sea state). Most
// data-dense / dramatic option, leans into the weather-chart aesthetic.
// ─────────────────────────────────────────────────────────────────────────
function TplWeather({ w, h, slotId = 'tpl-weather', data, draggable = true }) {
  const d = { ...SAMPLES.weather, ...(data || {}) };
  const u = Math.min(w, h) / 100;
  const isWide = w / h > 1.2;
  // Default position: center-left (matches the old fixed layout).
  const drag = useDraggable(draggable ? `16ds-weather-pos-dark-${ratioKey(w, h)}` : null, { x: 28, y: 50 });
  const resize = window.useResizable(draggable ? `16ds-weather-scale-dark-${ratioKey(w, h)}` : null, 1, 0.5, 2.2);
  return (
    <Card w={w} h={h}>
      <PhotoSlot id={slotId} placeholder="Drop a photo" mediaKind={d.mediaKind} />
      {/* Bottom + top gradient */}
      <div style={{
        position: 'absolute', inset: 0,
        background: 'linear-gradient(180deg, rgba(2,16,43,0.65) 0%, rgba(2,16,43,0) 22%, rgba(2,16,43,0) 50%, rgba(2,16,43,0.93) 100%)',
        pointerEvents: 'none',
      }} />
      {/* Topo overlay on photo — pointer-events:none so it doesn't block
          the photo slot's drop/click handlers underneath */}
      <div style={{ position: 'absolute', inset: 0, mixBlendMode: 'screen', opacity: 0.4, pointerEvents: 'none' }}>
        <TopoSvg opacity={0.5} />
      </div>
      {/* Top row — eyebrow + frame */}
      <div style={{
        position: 'absolute', top: u * 5, left: u * 5, right: u * 5,
        display: 'flex', justifyContent: 'space-between', zIndex: 2,
        fontFamily: 'var(--font-brand)', fontWeight: 600,
        fontSize: u * 2.2, letterSpacing: '0.18em', textTransform: 'uppercase',
      }}>
        <Rise enabled={d.mediaKind === 'video'} delay={0} style={{ color: 'var(--blue-light)' }}>// {d.client}</Rise>
        <Rise enabled={d.mediaKind === 'video'} delay={0.1} style={{ color: 'var(--cream)' }}>{d.project}</Rise>
      </div>
      {/* Compass + wind callout — DRAGGABLE + RESIZABLE. Size reduced 25%, white-w/-opacity bg. */}
      <div
        data-resizable-card="weather-dark"
        onPointerDown={draggable ? drag.onPointerDown : undefined}
        style={{
          position: 'absolute',
          left: `${drag.pos.x}%`,
          top:  `${drag.pos.y}%`,
          transform: `translate(-50%, -50%) scale(${resize.scale})`,
          transformOrigin: 'center center',
          zIndex: 2,
          display: 'flex', alignItems: 'center', gap: u * 2.25,
          padding: `${u * 2}px ${u * 2.5}px`,
          background: 'rgba(255, 255, 255, 0.5)',
          backdropFilter: 'blur(8px)',
          WebkitBackdropFilter: 'blur(8px)',
          cursor: draggable ? (drag.dragging ? 'grabbing' : 'grab') : 'default',
          touchAction: draggable ? 'none' : 'auto',
          userSelect: 'none',
          outline: (drag.dragging || resize.resizing) ? '1px dashed rgba(97,212,242,0.6)' : 'none',
          outlineOffset: '4px',
        }}
      >
        <CompassArrow dir={d.dir} size={u * 10.5} navy="#02102B" accent="#0F6D96" />
        <div>
          <Rise enabled={d.mediaKind === 'video'} delay={0.2} style={{
            fontFamily: 'var(--font-brand)', color: '#0F6D96',
            fontSize: u * 1.35, fontWeight: 700, letterSpacing: '0.18em',
            textTransform: 'uppercase', marginBottom: u * 0.3,
            display: 'block',
          }}>// WIND</Rise>
          <Rise enabled={d.mediaKind === 'video'} delay={0.3} style={{
            fontFamily: 'var(--font-display)', color: '#02102B',
            fontSize: u * 6.75, lineHeight: 0.9, letterSpacing: '-0.02em',
            textTransform: 'uppercase', display: 'block',
          }}><Scramble enabled={d.mediaKind === 'video'} value={d.wind} delay={0.45} duration={0.7} /><span style={{ fontSize: u * 3, color: '#0F6D96' }}>KTS</span></Rise>
          <Rise enabled={d.mediaKind === 'video'} delay={0.5} style={{
            fontFamily: 'var(--font-brand)', color: '#02102B',
            fontSize: u * 1.5, fontWeight: 600, letterSpacing: '0.14em',
            textTransform: 'uppercase', marginTop: u * 0.45,
            display: 'block',
          }}>{d.dir} · sea {d.sea}</Rise>
          {/* Event + Day line — only render if at least one is set */}
          {(d.event || d.day) && (
            <Rise enabled={d.mediaKind === 'video'} delay={0.7} style={{
              marginTop: u * 1.05,
              paddingTop: u * 0.9,
              borderTop: '1px solid rgba(2,16,43,0.15)',
              display: 'flex', flexWrap: 'wrap', alignItems: 'baseline', gap: `${u * 0.6}px ${u * 1.2}px`,
              fontFamily: 'var(--font-brand)', fontWeight: 600,
              fontSize: u * 1.275, letterSpacing: '0.16em', textTransform: 'uppercase',
              color: '#02102B',
            }}>
              {d.event && <span><span style={{ color: '#0F6D96' }}>// EVENT</span> &nbsp;{d.event}</span>}
              {d.day   && <span><span style={{ color: '#0F6D96' }}>// </span>{d.day}</span>}
            </Rise>
          )}
        </div>
        <ResizeHandle onPointerDown={resize.onPointerDown} color="#02102B" bg="rgba(2,16,43,0.15)" />
      </div>
      {/* Bottom — location stack */}
      <div style={{
        position: 'absolute', bottom: u * 5, left: u * 5, right: u * 5,
        zIndex: 2,
        display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end',
        gap: u * 2,
      }}>
        <div>
          <Rise enabled={d.mediaKind === 'video'} delay={0.9} style={{
            fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
            fontSize: u * 1.8, fontWeight: 600, letterSpacing: '0.18em',
            textTransform: 'uppercase', marginBottom: u * 1,
            display: 'block',
          }}>// LOCATION</Rise>
          <Rise enabled={d.mediaKind === 'video'} delay={1.0} style={{
            fontFamily: 'var(--font-display)', color: 'var(--cream)',
            lineHeight: 0.95,
            letterSpacing: '-0.02em', textTransform: 'uppercase',
            display: 'block',
          }}>
            <AutoFitText baseSize={isWide ? u * 8 : u * 9}>
              {d.location}<span style={{ color: 'var(--blue-light)' }}>.</span>
            </AutoFitText>
          </Rise>
        </div>
        <div style={{ textAlign: 'right' }}>
          <Rise enabled={d.mediaKind === 'video'} delay={1.1} style={{
            fontFamily: 'var(--font-brand)', color: 'var(--blue-light)',
            fontSize: u * 1.6, fontWeight: 600, letterSpacing: '0.18em',
            textTransform: 'uppercase', marginBottom: u * 0.6,
            display: 'block',
          }}>// COORDS</Rise>
          <Rise enabled={d.mediaKind === 'video'} delay={1.2} style={{
            fontFamily: 'var(--font-brand)', color: 'var(--cream)',
            fontSize: u * 2, fontWeight: 500, letterSpacing: '0.08em',
            textTransform: 'uppercase', lineHeight: 1.4,
            display: 'block',
          }}>
            <Scramble enabled={d.mediaKind === 'video'} value={d.lat} delay={1.3} duration={0.8} /><br />
            <Scramble enabled={d.mediaKind === 'video'} value={d.lng} delay={1.5} duration={0.8} />
          </Rise>
        </div>
      </div>
    </Card>
  );
}

// Make them globally available to other Babel scripts
Object.assign(window, { TplCoordinates, TplCallSheet, TplParallel, TplBigType, TplWeather });
