// cms-skin.jsx
// The "presentation layer" / skin. Every section is a pure function of its
// `content` prop (+ shared `theme`/`colors`). No content strings live here —
// swap this whole file for a different skin and the same document re-renders.
//
// Exposed on window for the registry + admin (cross-file Babel scripts don't
// share scope). Load AFTER React + cms-data.js, BEFORE the main app script.

const { useState, useEffect, useRef } = React;

// ── Responsive hook ───────────────────────────────────────────────
// The skin uses inline styles (which beat CSS media queries on specificity),
// so layout breakpoints are driven in JS. `bp` returns booleans for the two
// breakpoints the layouts care about.
function useViewport() {
  const get = () => (typeof window !== "undefined" ? window.innerWidth : 1280);
  const [w, setW] = useState(get);
  useEffect(() => {
    let raf = 0;
    const onResize = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => setW(get())); };
    window.addEventListener("resize", onResize);
    return () => { window.removeEventListener("resize", onResize); cancelAnimationFrame(raf); };
  }, []);
  return { w, mobile: w <= 760, narrow: w <= 560 };
}

// Section padding that tightens on small screens.
const secPad = (narrow) => (narrow ? "72px 20px" : "120px 48px");

// ── Shared helpers ────────────────────────────────────────────────
const CREAM = "oklch(93% 0.016 85)";

// Emphasise any *asterisk-wrapped* span in the accent colour (italic). The
// markers are editorial — whatever you wrap in *single asterisks* lights up,
// any word, any position; the asterisks themselves are never shown.
function renderAccented(text, accent, emWeight) {
  const parts = String(text).split(/(\*[^*\n]+\*)/g);
  return parts.map((chunk, i) =>
    /^\*[^*\n]+\*$/.test(chunk)
      ? <em key={i} style={{ color: accent, fontStyle: "italic", fontWeight: emWeight }}>{chunk.slice(1, -1)}</em>
      : <React.Fragment key={i}>{chunk}</React.Fragment>
  );
}

// Render a string with "\n" as hard line breaks.
function renderLines(text) {
  const lines = String(text).split("\n");
  return lines.map((ln, i) => (
    <React.Fragment key={i}>{ln}{i < lines.length - 1 ? <br /> : null}</React.Fragment>
  ));
}

// ── SVG Mark ("fl.") ──────────────────────────────────────────────
function FGMark({ color = "currentColor", height = 40, cssHeight }) {
  const aspect = 71.040703 / 75.338379;
  if (cssHeight) {
    return (
      <div style={{ height: cssHeight, aspectRatio: String(aspect) }}>
        <svg width="100%" height="100%" viewBox="0 0 71.040703 75.338379" fill="none">
          <g transform="translate(-69.056253,-110.86042)">
            <path d={window.FG_MARK_PATH} fill={color} stroke="none" />
          </g>
        </svg>
      </div>
    );
  }
  const w = height * aspect;
  return (
    <svg width={w} height={height} viewBox="0 0 71.040703 75.338379" fill="none" style={{ flexShrink: 0, display: "block" }}>
      <g transform="translate(-69.056253,-110.86042)">
        <path d={window.FG_MARK_PATH} fill={color} stroke="none" />
      </g>
    </svg>
  );
}

// ── SVG Wordmark ("flairground") ──────────────────────────────────
function FGWordmark({ color = "currentColor", width = 200, cssWidth }) {
  const aspect = 161.26669 / 32.844387;
  if (cssWidth) {
    return (
      <div style={{ width: cssWidth }}>
        <svg width="100%" height="auto" viewBox="0 0 161.26669 32.844387" fill="none" style={{ display: "block" }}>
          <g transform="translate(-18.146622,-121.99224)">
            <path d={window.FG_WORDMARK_PATH} fill={color} stroke="none" />
          </g>
        </svg>
      </div>
    );
  }
  const h = width / aspect;
  return (
    <svg width={width} height={h} viewBox="0 0 161.26669 32.844387" fill="none" style={{ display: "block" }}>
      <g transform="translate(-18.146622,-121.99224)">
        <path d={window.FG_WORDMARK_PATH} fill={color} stroke="none" />
      </g>
    </svg>
  );
}

// ── Animated Nav Logo — layered-clip slide ─────────────────────────
function AnimatedLogo({ colors, height = 26 }) {
  const VB_W = 161.26669, VB_H = 32.844387;
  const aspect = VB_W / VB_H;
  const h = Math.round(height);
  const width = Math.round(h * aspect);
  const renderH = h;
  const [phase, setPhase] = useState("open");
  const HOLD_OPEN = 10000, COLLAPSE = 1000, HOLD_SHUT = 10000, EXPAND = 1000;
  const AIR_SLIDE = 89.736;
  const DOT_SLIDE = 85.1;
  const CLIP_TOP = 98.25, CLIP_BOT = 94.2;
  const FL_TOP = 13.05,  FL_BOT = 9.75;
  const AIR_TOP = 9.5,   AIR_BOT = 7.999;
  const AIR_RIGHT_TOP = 99.1, AIR_RIGHT_BOT = 94.15;
  const dotColor = colors.accent;

  useEffect(() => {
    const NEXT = { open: "collapsing", collapsing: "shut", shut: "expanding", expanding: "open" };
    const DUR  = { open: HOLD_OPEN, collapsing: COLLAPSE, shut: HOLD_SHUT, expanding: EXPAND };
    const t = setTimeout(() => setPhase(p => NEXT[p]), DUR[phase]);
    return () => clearTimeout(t);
  }, [phase]);

  const isShut = phase === "collapsing" || phase === "shut";
  const transition = "transform 1s cubic-bezier(0.65,0,0.35,1)";
  const airX = isShut ? -AIR_SLIDE : 0;
  const dotX = isShut ? -DOT_SLIDE : 0;

  const layerStyle = { position: "absolute", inset: 0, width: "100%", height: "100%", willChange: "transform" };
  const svgStyle = { display: "block", width: "100%", height: "auto" };
  const renderPath = (fill) => (
    <svg viewBox={`0 0 ${VB_W} ${VB_H}`} xmlns="http://www.w3.org/2000/svg" style={svgStyle}>
      <g transform="translate(-18.146622,-121.99224)">
        <path fill={fill} fillRule="evenodd" d={window.FG_WORDMARK_PATH} />
      </g>
    </svg>
  );

  return (
    <div style={{ position: "relative", width, height: renderH, flexShrink: 0, transform: "translateZ(0)" }}>
      <div style={{ ...layerStyle, clipPath: `polygon(0% 0%, ${FL_TOP}% 0%, ${FL_BOT}% 100%, 0% 100%)` }}>
        {renderPath(CREAM)}
      </div>
      <div style={{ ...layerStyle, clipPath: `polygon(${AIR_TOP}% 0%, 100% 0%, 100% 100%, ${AIR_BOT}% 100%)`, overflow: "visible" }}>
        <div style={{
          ...layerStyle,
          clipPath: `polygon(0% 0%, ${AIR_RIGHT_TOP}% 0%, ${AIR_RIGHT_BOT}% 100%, 0% 100%)`,
          transform: `translateX(${airX}%)`,
          transition,
        }}>
          {renderPath(CREAM)}
        </div>
      </div>
      <div style={{
        ...layerStyle,
        clipPath: `polygon(${CLIP_TOP}% 0%, 110% 0%, 110% 100%, ${CLIP_BOT}% 100%)`,
        transform: `translateX(${dotX}%)`,
        transition,
      }}>
        {renderPath(dotColor)}
      </div>
    </div>
  );
}

// ── Static wordmark ───────────────────────────────────────────────
function WordmarkSVG({ size = "lg" }) {
  const VB_W = 161.26669, VB_H = 32.844387;
  const heights = { sm: 22, lg: 40, hero: "clamp(52px,9vw,120px)" };
  const h = heights[size] || heights.lg;
  return (
    <div style={{ height: h, aspectRatio: String(VB_W / VB_H) }}>
      <svg viewBox={`0 0 ${VB_W} ${VB_H}`} style={{ display: "block", width: "100%", height: "100%" }}>
        <g transform="translate(-18.146622,-121.99224)">
          <path fill={CREAM} fillRule="evenodd" d={window.FG_WORDMARK_PATH} />
        </g>
      </svg>
    </div>
  );
}

// ── Nav ────────────────────────────────────────────────────────────
function Nav({ content, theme, colors }) {
  const [scrolled, setScrolled] = useState(false);
  const { narrow } = useViewport();
  useEffect(() => {
    const h = () => setScrolled(window.scrollY > 60);
    window.addEventListener("scroll", h, { passive: true });
    return () => window.removeEventListener("scroll", h);
  }, []);

  // "glass" (default): always a blurred dark bar, even over the hero.
  // "minimal": transparent over the hero, blurred bar only once scrolled.
  const glass = (theme && theme.navStyle) !== "minimal";
  const atTopBg = glass ? "oklch(9% 0.018 55 / 0.55)" : "transparent";
  const atTopBorder = glass ? "1px solid oklch(93% 0.016 85 / 0.04)" : "1px solid transparent";
  const blur = (glass || scrolled) ? "blur(10px)" : "none";

  return (
    <nav style={{
      position: "fixed", top: "var(--fg-nav-top, 0px)", left: 0, right: "var(--fg-dock-right, 0px)", zIndex: 100,
      display: "flex", alignItems: "center", justifyContent: "space-between",
      padding: narrow ? "0 20px" : "0 48px", height: narrow ? 60 : 72,
      background: scrolled ? "oklch(9% 0.018 55 / 0.92)" : atTopBg,
      backdropFilter: blur, WebkitBackdropFilter: blur,
      borderBottom: scrolled ? `1px solid oklch(93% 0.016 85 / 0.06)` : atTopBorder,
      transition: "background 0.5s, border-color 0.5s, backdrop-filter 0.5s",
    }}>
      <AnimatedLogo colors={colors} height={narrow ? 26 : 33} />
      {!narrow && (
        <div style={{ display: "flex", gap: 40, alignItems: "center" }}>
          {(content.links || []).map(l => (
            <a key={l} href={`#${l.toLowerCase()}`} style={{
              fontFamily: "var(--fg-body)", fontSize: 14, fontWeight: 400,
              color: "oklch(93% 0.016 85 / 0.6)", textDecoration: "none",
              letterSpacing: "0.06em", textTransform: "uppercase",
              transition: "color 0.2s",
            }}
            onMouseEnter={e => e.target.style.color = colors.accent}
            onMouseLeave={e => e.target.style.color = "oklch(93% 0.016 85 / 0.6)"}
            >{l}</a>
          ))}
        </div>
      )}
    </nav>
  );
}

// ── Hero ───────────────────────────────────────────────────────────
function Hero({ content, theme, colors }) {
  const [visible, setVisible] = useState(false);
  const { mobile, narrow } = useViewport();
  const videoRef = useRef(null);
  const [vidProgress, setVidProgress] = useState(null); // null = no live video → use the ambient animation
  useEffect(() => { const t = setTimeout(() => setVisible(true), 120); return () => clearTimeout(t); }, []);
  useEffect(() => { setVidProgress(null); }, [content.videoUrl]);

  const hw = theme.headlineWeight || 900;

  return (
    <section style={{
      height: "100vh", minHeight: 620,
      display: "flex", flexDirection: "column", justifyContent: "flex-end",
      padding: narrow ? "0 20px 56px" : "0 48px 80px",
      position: "relative", overflow: "hidden",
      background: "oklch(11% 0.018 55)",
    }}>
      {content.videoUrl ? (
        <video
          key={content.videoUrl}
          ref={videoRef}
          autoPlay muted loop playsInline preload="auto"
          src={content.videoUrl}
          onTimeUpdate={(e) => { const v = e.currentTarget; if (v.duration) setVidProgress(v.currentTime / v.duration); }}
          onError={(e) => { e.currentTarget.style.display = "none"; setVidProgress(null); }}
          style={{
            position: "absolute", inset: 0, zIndex: 1,
            width: "100%", height: "100%", objectFit: "cover",
            background: "transparent",
            filter: "saturate(0.85) brightness(0.78)",
          }}
        />
      ) : null}

      <div style={{ position: "absolute", inset: 0, zIndex: 0, pointerEvents: "none", overflow: "hidden", background: "oklch(11% 0.018 55)" }}>
        <style>{`
          @keyframes heroDrift { 0% { transform: translate3d(0,0,0) scale(1.06); } 100% { transform: translate3d(-3%,-2%,0) scale(1.14); } }
          @keyframes heroPulse { 0%,100% { opacity: 0.85; } 50% { opacity: 0.25; } }
          @keyframes heroProgress { 0% { transform: scaleX(0); transform-origin: left; } 100% { transform: scaleX(1); transform-origin: left; } }
          @keyframes heroFlare {
            0%   { transform: translate(-30%, 40%) scale(1); opacity: 0; }
            15%  { opacity: 0.7; }
            50%  { transform: translate(40%, -10%) scale(1.3); opacity: 0.5; }
            85%  { opacity: 0.4; }
            100% { transform: translate(110%, -40%) scale(1); opacity: 0; }
          }
          @keyframes heroGrade { 0% { opacity: 0.18; } 33% { opacity: 0.32; } 66% { opacity: 0.10; } 100% { opacity: 0.18; } }
          @keyframes heroGrain {
            0%, 100% { transform: translate(0, 0); }
            10% { transform: translate(-2%, 1%); } 20% { transform: translate(1%, -2%); }
            30% { transform: translate(-1%, 2%); } 40% { transform: translate(2%, 1%); }
            50% { transform: translate(-2%, -1%); } 60% { transform: translate(1%, 2%); }
            70% { transform: translate(-1%, -2%); } 80% { transform: translate(2%, -1%); }
            90% { transform: translate(-2%, 1%); }
          }
        `}</style>

        <div style={{ position: "absolute", inset: "-4%", animation: "heroDrift 32s ease-in-out infinite alternate" }}>
          <svg width="100%" height="100%" style={{ display: "block" }}>
            <defs>
              <pattern id="heroBed" width="42" height="42" patternUnits="userSpaceOnUse" patternTransform="rotate(22)">
                <line x1="0" y1="0" x2="0" y2="42" stroke="oklch(93% 0.016 85 / 0.045)" strokeWidth="1" />
              </pattern>
              <radialGradient id="heroVignette" cx="50%" cy="50%" r="70%">
                <stop offset="0%" stopColor={colors.accent} stopOpacity="0.05" />
                <stop offset="100%" stopColor="oklch(9% 0.018 55)" stopOpacity="1" />
              </radialGradient>
            </defs>
            <rect width="100%" height="100%" fill="url(#heroVignette)" />
            <rect width="100%" height="100%" fill="url(#heroBed)" />
          </svg>
        </div>

        <div style={{ position: "absolute", inset: "-6%", animation: "heroDrift 48s ease-in-out infinite alternate-reverse", opacity: 0.55 }}>
          <svg width="100%" height="100%" style={{ display: "block" }}>
            <defs>
              <pattern id="heroBedFar" width="78" height="78" patternUnits="userSpaceOnUse" patternTransform="rotate(-14)">
                <line x1="0" y1="0" x2="0" y2="78" stroke="oklch(93% 0.016 85 / 0.035)" strokeWidth="1" />
              </pattern>
            </defs>
            <rect width="100%" height="100%" fill="url(#heroBedFar)" />
          </svg>
        </div>

        <div style={{
          position: "absolute", top: "30%", left: "20%",
          width: "60vw", height: "60vw", maxWidth: 900, maxHeight: 900,
          borderRadius: "50%",
          background: `radial-gradient(circle, ${colors.accent} 0%, transparent 55%)`,
          mixBlendMode: "screen", animation: "heroFlare 22s ease-in-out infinite", pointerEvents: "none",
        }} />

        <div style={{
          position: "absolute", inset: 0,
          background: `linear-gradient(135deg, ${colors.accent} 0%, transparent 60%)`,
          mixBlendMode: "overlay", animation: "heroGrade 14s ease-in-out infinite", pointerEvents: "none",
        }} />

        <div style={{
          position: "absolute", inset: "-10%", opacity: 0.35, mixBlendMode: "overlay",
          animation: "heroGrain 0.6s steps(8) infinite",
          backgroundImage: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.55 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>")`,
        }} />

        <div style={{
          position: "absolute", bottom: 80, left: 48, display: mobile ? "none" : "block",
          fontFamily: "monospace", fontSize: 10, letterSpacing: "0.2em", textTransform: "uppercase",
          color: "oklch(93% 0.016 85 / 0.22)",
        }}>
          ◉ rec · 23.976 fps · 4K · ARRI Alexa Mini
        </div>
      </div>

      <div style={{
        position: "absolute", inset: 0, zIndex: 1, pointerEvents: "none",
        background: "linear-gradient(180deg, transparent 0%, transparent 52%, oklch(9% 0.018 55 / 0.5) 78%, oklch(9% 0.018 55 / 0.96) 100%)",
      }} />

      <div style={{
        position: "relative", zIndex: 2,
        opacity: visible ? 1 : 0,
        transform: `translateY(${visible ? 0 : 30}px)`,
        transition: "opacity 1.2s ease 0.2s, transform 1.2s cubic-bezier(0.16,1,0.3,1) 0.2s",
        maxWidth: "min(1180px, 88vw)",
      }}>
        {(content.eyebrow || "").trim() ? (
          <div style={{ marginBottom: 28 }}>
            <span style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase", color: colors.accent }}>
              {content.eyebrow}
            </span>
          </div>
        ) : null}
        <h1 style={{
          fontFamily: "var(--fg-head)",
          fontSize: "clamp(32px, 4.75vw, 78px)",
          fontWeight: hw, letterSpacing: "-0.02em", lineHeight: 1.0,
          textWrap: "balance", marginBottom: 36, color: "oklch(96% 0.016 85)",
        }}>
          {renderAccented(content.headline, colors.accent, Math.max(300, hw - 100))}
        </h1>
        <div style={{ display: "flex", gap: narrow ? 10 : 16, alignItems: "center", flexWrap: "wrap" }}>
          {(content.primaryCta || "").trim() ? (
          <a href="#work" style={{
            display: "inline-flex", alignItems: "center", gap: 10, padding: narrow ? "13px 24px" : "14px 32px",
            background: colors.accent, color: "oklch(9% 0.018 55)",
            fontFamily: "var(--fg-head)", fontWeight: 700,
            fontSize: 15, letterSpacing: "0.1em", textTransform: "uppercase", textDecoration: "none",
            transition: "opacity 0.2s, transform 0.2s",
          }}
          onMouseEnter={e => { e.currentTarget.style.opacity = "0.85"; e.currentTarget.style.transform = "translateY(-1px)"; }}
          onMouseLeave={e => { e.currentTarget.style.opacity = "1"; e.currentTarget.style.transform = "translateY(0)"; }}
          >{content.primaryCta}</a>
          ) : null}
          {(content.secondaryCta || "").trim() ? (
          <a href="#about" style={{
            display: "inline-flex", alignItems: "center", gap: 8, padding: narrow ? "13px 24px" : "14px 32px",
            border: "1px solid oklch(93% 0.016 85 / 0.2)", color: "oklch(93% 0.016 85 / 0.7)",
            fontFamily: "var(--fg-head)", fontWeight: 500,
            fontSize: 15, letterSpacing: "0.1em", textTransform: "uppercase", textDecoration: "none",
            transition: "border-color 0.2s, color 0.2s",
          }}
          onMouseEnter={e => { e.currentTarget.style.borderColor = colors.accent; e.currentTarget.style.color = colors.accent; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = "oklch(93% 0.016 85 / 0.2)"; e.currentTarget.style.color = "oklch(93% 0.016 85 / 0.7)"; }}
          >{content.secondaryCta}</a>
          ) : null}
        </div>
      </div>

      <div style={{
        position: "absolute", bottom: 0, left: 0, right: 0, zIndex: 2,
        height: 1, background: "oklch(93% 0.016 85 / 0.08)",
        opacity: visible ? 1 : 0, transition: "opacity 1.5s ease 0.8s",
      }}>
        {vidProgress === null ? (
          <div style={{ height: "100%", background: colors.accent, animation: "heroProgress 18s linear infinite" }} />
        ) : (
          <div style={{ height: "100%", width: `${Math.max(0, Math.min(1, vidProgress)) * 100}%`, background: colors.accent, transition: "width 0.15s linear" }} />
        )}
      </div>

      <div style={{
        position: "absolute", bottom: 28, right: 48, zIndex: 2,
        display: mobile ? "none" : "flex", flexDirection: "column", alignItems: "center", gap: 8,
        opacity: visible ? 0.45 : 0, transition: "opacity 1.5s ease 1s",
      }}>
        <span style={{ fontFamily: "monospace", fontSize: 10, letterSpacing: "0.14em", textTransform: "uppercase", writingMode: "vertical-rl" }}>scroll</span>
        <div style={{ width: 1, height: 36, background: "oklch(93% 0.016 85 / 0.4)" }} />
      </div>
    </section>
  );
}

// ── Ticker ─────────────────────────────────────────────────────────
function Ticker({ content, colors }) {
  const items = content.items || [];
  const repeated = [...items, ...items, ...items];
  return (
    <div style={{
      borderTop: "1px solid oklch(93% 0.016 85 / 0.07)",
      borderBottom: "1px solid oklch(93% 0.016 85 / 0.07)",
      padding: "18px 0", overflow: "hidden", background: "oklch(11% 0.018 55)",
    }}>
      <style>{`@keyframes ticker { from { transform: translateX(0) } to { transform: translateX(-33.33%) } }`}</style>
      <div style={{ display: "flex", gap: 48, whiteSpace: "nowrap", animation: "ticker 22s linear infinite", width: "max-content" }}>
        {repeated.map((item, i) => (
          <span key={i} style={{ display: "inline-flex", alignItems: "center", gap: 48 }}>
            <span style={{ fontFamily: "var(--fg-head)", fontSize: 15, fontWeight: 600, letterSpacing: "0.12em", textTransform: "uppercase", color: "oklch(93% 0.016 85 / 0.45)" }}>{item}</span>
            <span style={{ width: 5, height: 5, borderRadius: "50%", background: colors.accent, display: "inline-block", flexShrink: 0, opacity: 0.7 }} />
          </span>
        ))}
      </div>
    </div>
  );
}

// ── Work ───────────────────────────────────────────────────────────
// Layout morphs by viewport AND project count: a ResizeObserver measures the
// grid, derives a column count from a ratio-coupled minimum tile width, and
// sizes rows so every cover lands on the chosen aspect. A "feature" tile spans
// 2×2 (same aspect, 4× area); it clamps to the available columns so it never
// overflows on narrow screens.
function parseRatio(str) {
  const m = String(str || "2:1").split(/[:/]/).map(Number);
  const n = (m[0] && m[1]) ? m[0] / m[1] : 2;
  return n > 0 ? n : 2;
}
const isFeatureShape = (s) => s === "feature" || s === "wide" || s === "tall" || s === "big";

// Resolve a tile's column/row span from its size + the live column count.
// Spans clamp down as columns shrink so nothing overflows on narrow screens.
function spanFor(shape, cols) {
  switch (shape) {
    case "wide":    return { colSpan: Math.min(2, cols), rowSpan: 1 };
    case "tall":    return { colSpan: 1, rowSpan: 2 };
    case "feature":
    case "big":     return { colSpan: Math.min(2, cols), rowSpan: 2 };
    default:        return { colSpan: 1, rowSpan: 1 };
  }
}

function WorkCard({ project, index, colors, onOpen, cols }) {
  const [hovered, setHovered] = useState(false);
  const [coverFailed, setCoverFailed] = useState(false);
  const { colSpan, rowSpan } = spanFor(project.shape || "single", cols);
  const pid = `pwork${index}`;
  const hasVideo = !!(project.video && project.video.trim());
  const cover = (project.cover && typeof project.cover === "object") ? project.cover : { url: "", focalX: 0.5, focalY: 0.5 };
  const coverUrl = (cover.url || "").trim();
  const showCover = !!coverUrl && !coverFailed;
  const focalPos = `${(typeof cover.focalX === "number" ? cover.focalX : 0.5) * 100}% ${(typeof cover.focalY === "number" ? cover.focalY : 0.5) * 100}%`;

  const details = Array.isArray(project.details) ? project.details.filter((d) => d && (d.item || d.value)) : [];
  const hasDetails = details.length > 0;
  const gallery = Array.isArray(project.gallery) ? project.gallery.filter((g) => g && (g.url || "").trim()) : [];
  const galleryCount = gallery.length;
  const openable = hasVideo || galleryCount > 0 || hasDetails;
  const open = () => { if (openable) onOpen(project); };

  return (
    <div
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      onClick={open}
      onKeyDown={(e) => { if (openable && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); open(); } }}
      role={openable ? "button" : undefined}
      tabIndex={openable ? 0 : undefined}
      aria-label={openable ? `${hasVideo ? "Play" : galleryCount ? "View photos from" : "View"} ${project.title}` : undefined}
      style={{
        gridColumn: `span ${colSpan}`, gridRow: `span ${rowSpan}`,
        background: "oklch(14% 0.018 55)",
        border: `1px solid oklch(93% 0.016 85 / ${hovered ? "0.12" : "0.05"})`,
        cursor: openable ? "pointer" : "default", position: "relative", overflow: "hidden",
        transition: "border-color 0.3s",
      }}
    >
      {/* diagonal-stripe placeholder (base layer / fallback when no cover) */}
      <svg width="100%" height="100%" style={{ position: "absolute", inset: 0 }}>
        <defs>
          <pattern id={pid} width="24" height="24" patternUnits="userSpaceOnUse" patternTransform="rotate(30)">
            <line x1="0" y1="0" x2="0" y2="24" stroke="oklch(93% 0.016 85 / 0.03)" strokeWidth="1" />
          </pattern>
        </defs>
        <rect width="100%" height="100%" fill={`url(#${pid})`} />
      </svg>

      {!showCover && (
        <div style={{
          position: "absolute", top: "50%", left: "50%", transform: "translate(-50%,-50%)",
          fontFamily: "monospace", fontSize: 11, color: "oklch(93% 0.016 85 / 0.18)",
          letterSpacing: "0.08em", textAlign: "center", lineHeight: 2,
        }}>
          {String(project.type || "").toUpperCase()}
          <br />add cover image
        </div>
      )}

      {/* cover image */}
      {showCover && (
        <img
          src={coverUrl}
          alt={project.title}
          onError={() => setCoverFailed(true)}
          style={{
            position: "absolute", inset: 0, width: "100%", height: "100%",
            objectFit: "cover", objectPosition: focalPos,
            transform: hovered ? "scale(1.05)" : "scale(1)",
            transition: "transform 0.6s cubic-bezier(0.16,1,0.3,1)",
          }}
        />
      )}

      {/* legibility veil over the cover so the info bar text stays readable */}
      {showCover && (
        <div style={{
          position: "absolute", inset: 0, pointerEvents: "none",
          background: "linear-gradient(180deg, oklch(9% 0.018 55 / 0.15) 0%, transparent 35%, transparent 55%, oklch(9% 0.018 55 / 0.85) 100%)",
        }} />
      )}

      {/* hover glow */}
      <div style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        background: `radial-gradient(circle at 50% 50%, ${colors.accentGlow} 0%, transparent 70%)`,
        opacity: hovered ? 1 : 0, transition: "opacity 0.4s",
      }} />

      {/* details-only affordance (no video, no gallery) */}
      {!hasVideo && galleryCount === 0 && hasDetails && (
        <div style={{
          position: "absolute", top: "calc(50% - 14px)", left: "50%",
          transform: `translate(-50%,-50%) scale(${hovered ? 1.06 : 1})`,
          width: 44, height: 44, borderRadius: "50%",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: "var(--fg-head)", fontWeight: 700, fontSize: 20, fontStyle: "italic",
          color: hovered ? "oklch(9% 0.018 55)" : "oklch(93% 0.016 85)",
          background: hovered ? colors.accent : "oklch(9% 0.018 55 / 0.55)",
          border: `1.5px solid ${hovered ? colors.accent : "oklch(93% 0.016 85 / 0.55)"}`,
          backdropFilter: "blur(4px)", pointerEvents: "none",
          transition: "background 0.3s, border-color 0.3s, transform 0.3s, color 0.3s",
        }}>i</div>
      )}

      {/* info bar */}
      <div style={{
        position: "absolute", bottom: 0, left: 0, right: 0, padding: "20px 24px",
        background: showCover ? "transparent" : `linear-gradient(transparent, oklch(9% 0.018 55 / 0.9))`,
        display: "flex", alignItems: "flex-end", justifyContent: "space-between",
        transform: `translateY(${hovered ? 0 : 4}px)`, transition: "transform 0.3s",
      }}>
        <div>
          <div style={{ fontFamily: "var(--fg-head)", fontSize: 24, fontWeight: 800, letterSpacing: "-0.01em", color: "oklch(93% 0.016 85)" }}>{project.title}</div>
          <div style={{ fontFamily: "var(--fg-body)", fontSize: 13, color: "oklch(93% 0.016 85 / 0.5)", marginTop: 2 }}>{project.client}</div>
        </div>
        <div style={{ textAlign: "right" }}>
          <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.08em", textTransform: "uppercase", color: colors.accent, marginBottom: 2 }}>{project.type}</div>
          <div style={{ fontFamily: "monospace", fontSize: 11, color: "oklch(93% 0.016 85 / 0.3)" }}>{project.year}</div>
        </div>
      </div>
    </div>
  );
}

// ── Project lightbox — video-first, then a swipeable stills gallery ──
function VideoLightbox({ project, colors, onClose }) {
  const hasVideo = !!(project.video && String(project.video).trim());
  const cover = (project.cover && typeof project.cover === "object") ? project.cover : { url: "", focalX: 0.5, focalY: 0.5 };
  const coverUrl = (cover.url || "").trim();
  const focalPos = `${(typeof cover.focalX === "number" ? cover.focalX : 0.5) * 100}% ${(typeof cover.focalY === "number" ? cover.focalY : 0.5) * 100}%`;
  const details = Array.isArray(project.details) ? project.details.filter((d) => d && (d.item || d.value)) : [];
  const gallery = Array.isArray(project.gallery) ? project.gallery.filter((g) => g && (g.url || "").trim()) : [];

  // The flick-through: the video leads (if present), then every still.
  // The flick-through: the optional feature video leads, then the media reel in
  // authored order — each item an image OR a video (legacy items default to image).
  const slides = [];
  if (hasVideo) slides.push({ type: "video", url: project.video, poster: coverUrl });
  gallery.forEach((g) => slides.push(g.type === "video"
    ? { type: "video", url: g.url, caption: g.caption || "" }
    : { type: "image", url: g.url, caption: g.caption || "" }));
  const count = slides.length;

  const [idx, setIdx] = useState(0);
  const clamp = (n) => Math.max(0, Math.min(count - 1, n));
  const go = (n) => setIdx((c) => clamp(typeof n === "function" ? n(c) : n));
  const videoRefs = useRef({});
  const drag = useRef(null);

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      else if (e.key === "ArrowRight") go((c) => c + 1);
      else if (e.key === "ArrowLeft") go((c) => c - 1);
    };
    window.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = prev; };
  }, [onClose, count]);

  // Pause any video that isn't the current slide.
  useEffect(() => {
    Object.entries(videoRefs.current).forEach(([k, v]) => {
      if (v && Number(k) !== idx) { try { v.pause(); } catch (e) {} }
    });
  }, [idx]);

  const onDown = (e) => { drag.current = { x: e.clientX, moved: 0 }; };
  const onMove = (e) => { if (drag.current) drag.current.moved = e.clientX - drag.current.x; };
  const onUp = () => {
    const d = drag.current; drag.current = null;
    if (!d) return;
    if (d.moved < -50) go((c) => c + 1);
    else if (d.moved > 50) go((c) => c - 1);
  };
  const noGrab = (e) => e.preventDefault(); // download/drag deterrent

  const arrowStyle = (side, disabled) => ({
    position: "absolute", top: "50%", [side]: 12, transform: "translateY(-50%)",
    width: 44, height: 44, borderRadius: "50%", zIndex: 3,
    display: count > 1 ? "flex" : "none", alignItems: "center", justifyContent: "center",
    border: "1px solid oklch(93% 0.016 85 / 0.25)", background: "oklch(9% 0.018 55 / 0.55)",
    backdropFilter: "blur(6px)", color: "oklch(96% 0.016 85)", fontSize: 24, lineHeight: 1,
    cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.3 : 1,
    transition: "opacity 0.2s, border-color 0.2s",
  });

  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, zIndex: 1000,
        display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
        padding: "clamp(16px, 5vw, 64px)", gap: 18,
        background: "oklch(6% 0.012 55 / 0.86)", backdropFilter: "blur(14px)",
        animation: "fgFade 0.25s ease",
      }}
    >
      <style>{`@keyframes fgFade { from { opacity: 0 } to { opacity: 1 } }`}</style>

      <div style={{ width: "min(1100px, 100%)", display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 16 }}>
        <div>
          <div style={{ fontFamily: "var(--fg-head)", fontSize: "clamp(22px,3vw,34px)", fontWeight: 700, letterSpacing: "-0.01em", color: "oklch(96% 0.016 85)" }}>{project.title}</div>
          <div style={{ fontFamily: "monospace", fontSize: 12, letterSpacing: "0.08em", color: colors.accent, marginTop: 4 }}>
            {project.type}{project.client ? ` · ${project.client}` : ""}{project.year ? ` · ${project.year}` : ""}
          </div>
        </div>
        <button
          onClick={onClose}
          aria-label="Close"
          style={{
            flexShrink: 0, width: 40, height: 40, borderRadius: "50%",
            border: "1px solid oklch(93% 0.016 85 / 0.25)", background: "transparent",
            color: "oklch(93% 0.016 85 / 0.8)", fontSize: 18, cursor: "pointer",
            display: "flex", alignItems: "center", justifyContent: "center", lineHeight: 1,
            transition: "border-color 0.2s, color 0.2s",
          }}
          onMouseEnter={(e) => { e.currentTarget.style.borderColor = colors.accent; e.currentTarget.style.color = colors.accent; }}
          onMouseLeave={(e) => { e.currentTarget.style.borderColor = "oklch(93% 0.016 85 / 0.25)"; e.currentTarget.style.color = "oklch(93% 0.016 85 / 0.8)"; }}
        >✕</button>
      </div>

      <div onClick={(e) => e.stopPropagation()} style={{ width: "min(1100px, 100%)", display: "flex", flexDirection: "column", gap: 16, paddingBottom: "clamp(12px, 5vh, 48px)" }}>
        {/* stage */}
        <div
          onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp}
          onContextMenu={noGrab}
          style={{
            aspectRatio: "16 / 9", background: "#000", borderRadius: 4, overflow: "hidden", position: "relative",
            border: "1px solid oklch(93% 0.016 85 / 0.1)", boxShadow: "0 40px 120px oklch(0% 0 0 / 0.6)",
            touchAction: "pan-y", userSelect: "none",
          }}>
          {count === 0 ? (
            coverUrl ? (
              <img src={coverUrl} alt={project.title} draggable={false} onContextMenu={noGrab}
                style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: focalPos }} />
            ) : (
              <div style={{
                position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center",
                fontFamily: "monospace", fontSize: 12, letterSpacing: "0.1em", textTransform: "uppercase",
                color: "oklch(93% 0.016 85 / 0.3)",
              }}>No media yet</div>
            )
          ) : (
            <div style={{
              display: "flex", height: "100%", width: "100%",
              transform: `translateX(-${idx * 100}%)`,
              transition: "transform 0.45s cubic-bezier(0.16,1,0.3,1)",
            }}>
              {slides.map((s, i) => (
                <div key={i} style={{ flex: "0 0 100%", width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", background: "#000" }}>
                  {s.type === "video" ? (
                    <video
                      ref={(el) => { videoRefs.current[i] = el; }}
                      src={s.url} poster={s.poster || undefined}
                      controls autoPlay={i === 0} playsInline
                      controlsList="nodownload noremoteplayback" disablePictureInPicture
                      onContextMenu={noGrab}
                      style={{ width: "100%", height: "100%", objectFit: "contain", background: "#000" }}
                    />
                  ) : (
                    <img src={s.url} alt={s.caption || project.title} draggable={false} onContextMenu={noGrab}
                      style={{ maxWidth: "100%", maxHeight: "100%", objectFit: "contain", display: "block", userSelect: "none", pointerEvents: "none" }} />
                  )}
                </div>
              ))}
            </div>
          )}

          {/* arrows + counter */}
          <button onClick={() => go((c) => c - 1)} disabled={idx === 0} aria-label="Previous" style={arrowStyle("left", idx === 0)}>‹</button>
          <button onClick={() => go((c) => c + 1)} disabled={idx === count - 1} aria-label="Next" style={arrowStyle("right", idx === count - 1)}>›</button>
          {count > 1 && (
            <div style={{
              position: "absolute", bottom: 12, right: 14, zIndex: 3,
              fontFamily: "monospace", fontSize: 11, letterSpacing: "0.08em",
              color: "oklch(96% 0.016 85)", background: "oklch(9% 0.018 55 / 0.55)",
              backdropFilter: "blur(6px)", padding: "4px 9px", borderRadius: 999,
            }}>{idx + 1} / {count}</div>
          )}
        </div>

        {/* caption */}
        {count > 0 && (
          <div style={{ minHeight: 19, textAlign: "center", fontFamily: "var(--fg-body)", fontSize: 14, fontStyle: "italic", color: "oklch(93% 0.016 85 / 0.6)", textWrap: "balance" }}>
            {slides[idx] && slides[idx].type === "image" ? slides[idx].caption : ""}
          </div>
        )}

        {/* filmstrip */}
        {count > 1 && (
          <div style={{ display: "flex", gap: 8, justifyContent: "center", flexWrap: "wrap" }}>
            {slides.map((s, i) => {
              const thumb = s.type === "image" ? s.url : s.poster;
              return (
                <button key={i} onClick={() => go(i)} aria-label={`View ${i + 1}`} style={{
                  width: 64, height: 40, borderRadius: 4, overflow: "hidden", padding: 0, cursor: "pointer",
                  border: `2px solid ${i === idx ? colors.accent : "oklch(93% 0.016 85 / 0.15)"}`,
                  background: "#000", position: "relative", opacity: i === idx ? 1 : 0.55,
                  transition: "opacity 0.2s, border-color 0.2s",
                }}>
                  {thumb ? <img src={thumb} alt="" draggable={false} style={{ width: "100%", height: "100%", objectFit: "cover" }} /> : null}
                  {s.type === "video" && (
                    <span style={{
                      position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center",
                      color: "oklch(96% 0.016 85)", fontSize: 13, textShadow: "0 1px 3px rgba(0,0,0,0.6)",
                    }}>▶</span>
                  )}
                </button>
              );
            })}
          </div>
        )}

        {details.length > 0 && (
          <div style={{ display: "grid", gridTemplateColumns: "auto 1fr", columnGap: 28, rowGap: 11, width: "100%", maxWidth: 760, marginTop: 4 }}>
            {details.map((d, i) => (
              <React.Fragment key={i}>
                <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", color: "oklch(93% 0.016 85 / 0.45)", paddingTop: 3, whiteSpace: "nowrap" }}>{d.item}</div>
                <div style={{ fontFamily: "var(--fg-body)", fontSize: 15, lineHeight: 1.5, color: "oklch(93% 0.016 85 / 0.92)" }}>{d.value}</div>
              </React.Fragment>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function Work({ content, theme, colors }) {
  const [active, setActive] = useState(null);
  const gridRef = useRef(null);
  const { narrow } = useViewport();
  const ratioNum = parseRatio(theme && theme.coverRatio);
  const gap = narrow ? 10 : 16;
  // wider aspect ⇒ wider minimum column ⇒ fewer, larger cinematic tiles
  const minCol = Math.round(230 + (ratioNum - 1) * 80);
  const [metrics, setMetrics] = useState({ cols: 4, rowH: 240 });

  useEffect(() => {
    const el = gridRef.current;
    if (!el || typeof ResizeObserver === "undefined") return;
    const measure = () => {
      const W = el.clientWidth;
      if (!W) return;
      const cols = Math.max(1, Math.floor((W + gap) / (minCol + gap)));
      const colW = (W - (cols - 1) * gap) / cols;
      setMetrics({ cols, rowH: Math.max(150, Math.round(colW / ratioNum)) });
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, [minCol, ratioNum]);

  return (
    <section id="work" style={{ padding: secPad(narrow) }}>
      <div style={{ marginBottom: 56 }}>
        <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: colors.accent, marginBottom: 12 }}>{content.eyebrow}</div>
        <h2 style={{ fontFamily: "var(--fg-head)", fontSize: "clamp(40px,5vw,64px)", fontWeight: 300, letterSpacing: "-0.02em", lineHeight: 1 }}>{content.heading}</h2>
      </div>
      <div ref={gridRef} style={{
        display: "grid",
        gridTemplateColumns: `repeat(${metrics.cols}, 1fr)`,
        gridAutoRows: `${metrics.rowH}px`,
        gridAutoFlow: "dense", gap,
      }}>
        {(content.projects || []).map((p, i) => <WorkCard key={i} project={p} index={i} colors={colors} onOpen={setActive} cols={metrics.cols} />)}
      </div>
      {active && <VideoLightbox project={active} colors={colors} onClose={() => setActive(null)} />}
    </section>
  );
}

// ── About + Team ───────────────────────────────────────────────────
function initialsOf(name) {
  return String(name || "").trim().split(/\s+/).map((w) => w[0]).slice(0, 2).join("").toUpperCase() || "?";
}

function TeamMember({ member, colors, onOpen }) {
  const [open, setOpen] = useState(false);
  const hs = (member.headshot && typeof member.headshot === "object") ? member.headshot : { url: "", focalX: 0.5, focalY: 0.5 };
  const url = (hs.url || "").trim();
  const focal = `${(typeof hs.focalX === "number" ? hs.focalX : 0.5) * 100}% ${(typeof hs.focalY === "number" ? hs.focalY : 0.5) * 100}%`;
  const size = "clamp(74px, 8vw, 98px)";

  return (
    <div style={{ position: "relative" }}
      onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
      <div
        onClick={onOpen}
        role="button" tabIndex={0} aria-label={`${member.name} — see profile`}
        onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpen(); } }}
        style={{
          width: size, height: size, borderRadius: "50%", overflow: "hidden", cursor: "pointer",
          border: `1px solid ${open ? colors.accent : "oklch(93% 0.016 85 / 0.16)"}`,
          background: "oklch(16% 0.018 55)", display: "flex", alignItems: "center", justifyContent: "center",
          transition: "border-color 0.25s, transform 0.25s", transform: open ? "translateY(-2px)" : "none",
        }}
      >
        {url
          ? <img src={url} alt={member.name} style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: focal }} />
          : <span style={{ fontFamily: "var(--fg-head)", fontWeight: 600, fontSize: 26, color: "oklch(93% 0.016 85 / 0.55)" }}>{initialsOf(member.name)}</span>}
      </div>

      {open && (
        <div style={{
          position: "absolute", bottom: "calc(100% - 14px)", left: "50%", transform: "translateX(-50%)",
          zIndex: 5, paddingBottom: 12, width: "max-content", maxWidth: 220, pointerEvents: "none",
        }}>
          <div style={{
            position: "relative", padding: "11px 16px",
            background: "oklch(15% 0.018 55)", border: "1px solid oklch(93% 0.016 85 / 0.12)",
            borderRadius: 10, boxShadow: "0 18px 50px oklch(0% 0 0 / 0.5)", textAlign: "center",
          }}>
            <div style={{ fontFamily: "var(--fg-head)", fontWeight: 600, fontSize: 17, color: "oklch(96% 0.016 85)" }}>{member.name}</div>
            {member.title && <div style={{ fontFamily: "monospace", fontSize: 10.5, letterSpacing: "0.08em", color: colors.accent, marginTop: 3 }}>{member.title}</div>}
            <div style={{
              position: "absolute", top: "100%", left: "50%", transform: "translateX(-50%)",
              width: 0, height: 0, borderLeft: "6px solid transparent", borderRight: "6px solid transparent",
              borderTop: "6px solid oklch(15% 0.018 55)",
            }} />
          </div>
        </div>
      )}
    </div>
  );
}

function ProfileCard({ member, colors, onClose }) {
  const hs = (member.headshot && typeof member.headshot === "object") ? member.headshot : { url: "", focalX: 0.5, focalY: 0.5 };
  const url = (hs.url || "").trim();
  const focal = `${(typeof hs.focalX === "number" ? hs.focalX : 0.5) * 100}% ${(typeof hs.focalY === "number" ? hs.focalY : 0.5) * 100}%`;
  const facts = Array.isArray(member.stats) ? member.stats.filter((s) => s && (s.item || s.value)) : [];

  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow; document.body.style.overflow = "hidden";
    return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = prev; };
  }, [onClose]);

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 1100, display: "flex", alignItems: "center", justifyContent: "center",
      padding: "clamp(16px, 5vw, 48px)", background: "oklch(6% 0.012 55 / 0.86)", backdropFilter: "blur(14px)",
      animation: "fgFade 0.25s ease",
    }}>
      <style>{`@keyframes fgFade { from { opacity: 0 } to { opacity: 1 } }`}</style>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: "min(660px, 100%)", display: "flex", flexWrap: "wrap", gap: 0,
        background: "oklch(12% 0.018 55)", border: "1px solid oklch(93% 0.016 85 / 0.1)",
        borderRadius: 12, overflow: "hidden", position: "relative", boxShadow: "0 40px 120px oklch(0% 0 0 / 0.6)",
      }}>
        <button onClick={onClose} aria-label="Close" style={{
          position: "absolute", top: 12, right: 12, zIndex: 2, width: 34, height: 34, borderRadius: "50%",
          border: "1px solid oklch(93% 0.016 85 / 0.25)", background: "oklch(9% 0.018 55 / 0.5)",
          color: "oklch(93% 0.016 85 / 0.8)", fontSize: 15, cursor: "pointer",
          display: "flex", alignItems: "center", justifyContent: "center", lineHeight: 1,
        }}>✕</button>

        <div style={{ flex: "1 1 200px", minWidth: 180, aspectRatio: "3 / 4", background: "oklch(16% 0.018 55)", position: "relative" }}>
          {url
            ? <img src={url} alt={member.name} style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: focal }} />
            : <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "var(--fg-head)", fontWeight: 600, fontSize: 64, color: "oklch(93% 0.016 85 / 0.3)" }}>{initialsOf(member.name)}</div>}
        </div>

        <div style={{ flex: "1 1 300px", minWidth: 260, padding: "32px 30px" }}>
          <div style={{ fontFamily: "var(--fg-head)", fontWeight: 700, fontSize: "clamp(26px,3.4vw,36px)", letterSpacing: "-0.01em", color: "oklch(96% 0.016 85)", lineHeight: 1.05 }}>{member.name}</div>
          {member.title && <div style={{ fontFamily: "monospace", fontSize: 11.5, letterSpacing: "0.1em", textTransform: "uppercase", color: colors.accent, marginTop: 6 }}>{member.title}</div>}
          {member.bio && <p style={{ fontFamily: "var(--fg-body)", fontSize: 14.5, fontWeight: 300, lineHeight: 1.7, color: "oklch(93% 0.016 85 / 0.7)", marginTop: 18, textWrap: "pretty" }}>{member.bio}</p>}
          {facts.length > 0 && (
            <div style={{ marginTop: 22, borderTop: "1px solid oklch(93% 0.016 85 / 0.1)", paddingTop: 18, display: "grid", gridTemplateColumns: "auto 1fr", columnGap: 18, rowGap: 9 }}>
              {facts.map((f, i) => (
                <React.Fragment key={i}>
                  <div style={{ fontFamily: "monospace", fontSize: 10.5, letterSpacing: "0.1em", textTransform: "uppercase", color: "oklch(93% 0.016 85 / 0.4)", paddingTop: 2, whiteSpace: "nowrap" }}>{f.item}</div>
                  <div style={{ fontFamily: "var(--fg-body)", fontSize: 14, color: "oklch(93% 0.016 85 / 0.85)" }}>{f.value}</div>
                </React.Fragment>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function About({ content, colors }) {
  const [profile, setProfile] = useState(null);
  const { narrow } = useViewport();
  const team = Array.isArray(content.team) ? content.team.filter((m) => m && (m.name || (m.headshot && m.headshot.url))) : [];
  return (
    <section id="about" style={{
      padding: secPad(narrow), background: "oklch(11% 0.018 55)",
      borderTop: "1px solid oklch(93% 0.016 85 / 0.06)", position: "relative", overflow: "hidden",
    }}>
      <div style={{
        position: "absolute", top: 0, right: 0, width: "60vw", height: "100%", pointerEvents: "none",
        background: `radial-gradient(ellipse at 80% 50%, ${colors.accentGlow} 0%, transparent 60%)`,
      }} />
      <div style={{ maxWidth: 900, position: "relative" }}>
        <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: colors.accent, marginBottom: 48 }}>{content.eyebrow}</div>
        <p style={{
          fontFamily: "var(--fg-head)", fontWeight: 300,
          fontSize: "clamp(32px, 4.5vw, 58px)", lineHeight: 1.15, letterSpacing: "-0.01em",
          color: "oklch(93% 0.016 85 / 0.9)", marginBottom: 48, textWrap: "pretty",
        }}>
          {renderAccented(content.lead, colors.accent, 600)}
        </p>
        <p style={{
          fontFamily: "var(--fg-body)", fontSize: 17, fontWeight: 300,
          color: "oklch(93% 0.016 85 / 0.55)", lineHeight: 1.75, maxWidth: 600, textWrap: "pretty",
        }}>
          {content.body}
        </p>

        {team.length > 0 && (
          <div style={{ marginTop: 80 }}>
            <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "oklch(93% 0.016 85 / 0.4)", marginBottom: 26 }}>The team</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: "clamp(20px, 3vw, 38px)" }}>
              {team.map((m, i) => <TeamMember key={i} member={m} colors={colors} onOpen={() => setProfile(m)} />)}
            </div>
          </div>
        )}

        <div style={{ display: "flex", gap: narrow ? 36 : 64, marginTop: 80, flexWrap: "wrap" }}>
          {(content.stats || []).map((s, i) => (
            <div key={i}>
              <div style={{ fontFamily: "var(--fg-head)", fontSize: 52, fontWeight: 300, lineHeight: 1, color: colors.accent }}>{s.num}</div>
              <div style={{ fontFamily: "var(--fg-body)", fontSize: 13, color: "oklch(93% 0.016 85 / 0.45)", marginTop: 6, letterSpacing: "0.02em" }}>{s.label}</div>
            </div>
          ))}
        </div>
      </div>
      {profile && <ProfileCard member={profile} colors={colors} onClose={() => setProfile(null)} />}
    </section>
  );
}

// ── Services ───────────────────────────────────────────────────────
// Curated line-icon set (stroke = currentColor). Keeps the section visual even
// before real imagery is added, and reads as on-brand iconography alongside it.
const SVC_ICONS = {
  film:       <g><rect x="2" y="3" width="20" height="18" rx="2" /><path d="M7 3v18M17 3v18M2 8h5M2 16h5M17 8h5M17 16h5M7 12h10" /></g>,
  camera:     <g><path d="M2 7a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2z" /><path d="M15 10l5-3v10l-5-3" /></g>,
  clapper:    <g><path d="M3 8h18v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z" /><path d="M3 8l1.5-3.5 4 1.2 1-2.4 4 1.2 1-2.4 4 1.2L20 4" /></g>,
  megaphone:  <g><path d="M3 11v2a1 1 0 0 0 1 1h2l9 5V5L6 10H4a1 1 0 0 0-1 1z" /><path d="M18 8a4 4 0 0 1 0 8" /></g>,
  scissors:   <g><circle cx="6" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M8.1 8.1L20 18M8.1 15.9L20 6" /></g>,
  sparkle:    <g><path d="M12 3l2 6 6 2-6 2-2 6-2-6-6-2 6-2z" /></g>,
  globe:      <g><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3c2.5 2.5 2.5 15 0 18M12 3c-2.5 2.5-2.5 15 0 18" /></g>,
  play:       <g><circle cx="12" cy="12" r="9" /><path d="M10 8l6 4-6 4z" /></g>,
};
function SvcIcon({ name, size = 22 }) {
  const g = SVC_ICONS[name] || SVC_ICONS.film;
  return (
    <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
      strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ display: "block" }}>{g}</svg>
  );
}

function ServiceCard({ item, index, colors }) {
  const [hovered, setHovered] = useState(false);
  const [imgFailed, setImgFailed] = useState(false);
  const pid = `psvc${index}`;
  const img = (item.image && typeof item.image === "object") ? item.image : { url: "", focalX: 0.5, focalY: 0.5 };
  const imgUrl = (img.url || "").trim();
  const showImg = !!imgUrl && !imgFailed;
  const focal = `${(typeof img.focalX === "number" ? img.focalX : 0.5) * 100}% ${(typeof img.focalY === "number" ? img.focalY : 0.5) * 100}%`;

  return (
    <div
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      style={{
        position: "relative", overflow: "hidden", background: "oklch(13% 0.018 55)",
        border: `1px solid oklch(93% 0.016 85 / ${hovered ? "0.14" : "0.07"})`,
        transform: hovered ? "translateY(-3px)" : "none",
        transition: "border-color 0.3s, transform 0.35s cubic-bezier(0.16,1,0.3,1)",
      }}
    >
      {/* visual area */}
      <div style={{ position: "relative", aspectRatio: "4 / 3", overflow: "hidden" }}>
        {showImg ? (
          <>
            <img src={imgUrl} alt={item.label} onError={() => setImgFailed(true)}
              style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: focal,
                transform: hovered ? "scale(1.06)" : "scale(1)", transition: "transform 0.6s cubic-bezier(0.16,1,0.3,1)" }} />
            <div style={{ position: "absolute", inset: 0, pointerEvents: "none",
              background: `linear-gradient(180deg, transparent 40%, oklch(13% 0.018 55 / 0.5) 100%)` }} />
          </>
        ) : (
          <>
            {/* branded placeholder: accent wash + stripe texture + ghost icon */}
            <div style={{ position: "absolute", inset: 0,
              background: `radial-gradient(130% 120% at 28% 18%, ${colors.accentGlow} 0%, transparent 58%), oklch(13% 0.018 55)` }} />
            <svg width="100%" height="100%" style={{ position: "absolute", inset: 0 }}>
              <defs>
                <pattern id={pid} width="22" height="22" patternUnits="userSpaceOnUse" patternTransform="rotate(30)">
                  <line x1="0" y1="0" x2="0" y2="22" stroke="oklch(93% 0.016 85 / 0.04)" strokeWidth="1" />
                </pattern>
              </defs>
              <rect width="100%" height="100%" fill={`url(#${pid})`} />
            </svg>
            <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center",
              color: colors.accent, opacity: hovered ? 0.7 : 0.42,
              transform: hovered ? "scale(1.06)" : "scale(1)", transition: "opacity 0.4s, transform 0.5s cubic-bezier(0.16,1,0.3,1)" }}>
              <SvcIcon name={item.icon} size={64} />
            </div>
          </>
        )}
      </div>

      {/* body */}
      <div style={{ padding: "22px 22px 26px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 11, marginBottom: 12,
          color: hovered ? colors.accent : "oklch(93% 0.016 85 / 0.55)", transition: "color 0.3s" }}>
          <SvcIcon name={item.icon} size={18} />
          <div style={{ flex: 1, height: 1, background: "oklch(93% 0.016 85 / 0.1)" }} />
        </div>
        <div style={{ fontFamily: "var(--fg-head)", fontSize: 27, fontWeight: 400, letterSpacing: "-0.01em", lineHeight: 1.05,
          color: "oklch(95% 0.016 85)", marginBottom: 10 }}>{item.label}</div>
        <p style={{ fontFamily: "var(--fg-body)", fontSize: 14.5, fontWeight: 300, lineHeight: 1.65,
          color: "oklch(93% 0.016 85 / 0.55)", textWrap: "pretty" }}>{item.desc}</p>
      </div>
    </div>
  );
}

function Services({ content, colors }) {
  const items = content.items || [];
  const { narrow } = useViewport();
  return (
    <section style={{ padding: secPad(narrow), borderTop: "1px solid oklch(93% 0.016 85 / 0.06)" }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 16, marginBottom: 48 }}>
        <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: colors.accent }}>{content.eyebrow}</div>
        <div style={{ flex: 1, height: 1, background: "oklch(93% 0.016 85 / 0.08)" }} />
      </div>
      <div style={{
        display: "grid", gridTemplateColumns: `repeat(auto-fit, minmax(${narrow ? "100%" : "min(100%, 270px)"}, 1fr))`,
        gap: narrow ? 12 : 16,
      }}>
        {items.map((item, i) => <ServiceCard key={i} item={item} index={i} colors={colors} />)}
      </div>
    </section>
  );
}

// ── Values ─────────────────────────────────────────────────────────
function ValueRow({ value, colors, last }) {
  const [hovered, setHovered] = useState(false);
  const { mobile } = useViewport();
  return (
    <div
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      style={{
        display: "grid", gridTemplateColumns: mobile ? "1fr" : "minmax(0, 0.9fr) minmax(0, 1.1fr)",
        gap: mobile ? 16 : "clamp(24px, 6vw, 96px)", alignItems: "start", padding: mobile ? "32px 0" : "48px 0",
        borderBottom: last ? "none" : "1px solid oklch(93% 0.016 85 / 0.08)",
        transition: "padding-left 0.35s cubic-bezier(0.16,1,0.3,1)", paddingLeft: hovered ? 12 : 0,
      }}
    >
      <div>
        <div style={{ display: "flex", alignItems: "baseline", gap: 16, marginBottom: 18 }}>
          <span style={{ fontFamily: "monospace", fontSize: 12, letterSpacing: "0.14em", color: colors.accent }}>{value.index}</span>
          <span style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase", color: "oklch(93% 0.016 85 / 0.45)" }}>{value.label}</span>
        </div>
        <h3 style={{
          fontFamily: "var(--fg-head)", fontWeight: 300,
          fontSize: "clamp(28px, 3vw, 44px)", lineHeight: 1.04, letterSpacing: "-0.01em",
          color: "oklch(93% 0.016 85)", textWrap: "balance",
        }}>{value.title}</h3>
      </div>
      <p style={{
        fontFamily: "var(--fg-body)", fontSize: 16, fontWeight: 300, lineHeight: 1.75,
        color: "oklch(93% 0.016 85 / 0.6)", maxWidth: 540, textWrap: "pretty", paddingTop: "clamp(0px, 1vw, 14px)",
      }}>{value.body}</p>
    </div>
  );
}

function Values({ content, colors }) {
  const items = content.items || [];
  const { narrow } = useViewport();
  return (
    <section id="values" style={{ padding: secPad(narrow), borderTop: "1px solid oklch(93% 0.016 85 / 0.06)" }}>
      <div style={{ marginBottom: 40 }}>
        <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: colors.accent, marginBottom: 12 }}>{content.eyebrow}</div>
        <h2 style={{ fontFamily: "var(--fg-head)", fontSize: "clamp(40px,5vw,64px)", fontWeight: 300, letterSpacing: "-0.02em", lineHeight: 1 }}>{content.heading}</h2>
      </div>
      <div>
        {items.map((v, i) => <ValueRow key={i} value={v} colors={colors} last={i === items.length - 1} />)}
      </div>
    </section>
  );
}

// ── Obfuscated email ───────────────────────────────────────────────
// Bot protection without hurting humans: the page is client-rendered (so the
// address is absent from served HTML), the "@" and "." are injected via CSS
// (so the DOM text never forms a matchable address), and the mailto: is only
// attached on first hover / focus / tap — never present at rest.
function ObfuscatedEmail({ email, colors, narrow }) {
  const safe = String(email || "");
  const at = safe.indexOf("@");
  const dom = at >= 0 ? safe.slice(at + 1) : "";
  const dot = dom.lastIndexOf(".");
  const user = at >= 0 ? safe.slice(0, at) : safe;
  const host = dot >= 0 ? dom.slice(0, dot) : dom;
  const tld = dot >= 0 ? dom.slice(dot + 1) : "";
  const reveal = (e) => { const a = e.currentTarget; if (!a.getAttribute("href")) a.setAttribute("href", "mailto:" + safe); };
  return (
    <a
      tabIndex={0} role="link" aria-label="Email us"
      onMouseEnter={(e) => { reveal(e); e.currentTarget.style.opacity = "0.85"; e.currentTarget.style.transform = "translateY(-2px)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.opacity = "1"; e.currentTarget.style.transform = "translateY(0)"; }}
      onFocus={reveal} onTouchStart={reveal}
      onKeyDown={(e) => { if (e.key === "Enter") reveal(e); }}
      style={{
        display: "inline-block", padding: narrow ? "16px 32px" : "18px 48px",
        background: colors.accent, color: "oklch(9% 0.018 55)",
        fontFamily: "var(--fg-head)", fontWeight: 700,
        fontSize: 18, letterSpacing: "0.08em", textDecoration: "none",
        transition: "opacity 0.2s, transform 0.2s", cursor: "pointer",
      }}
    >
      <span>{user}</span><span className="fg-at" aria-hidden="true"></span><span>{host}</span><span className="fg-dot" aria-hidden="true"></span><span>{tld}</span>
    </a>
  );
}

// ── Contact ────────────────────────────────────────────────────────
function Contact({ content, colors }) {
  const { narrow } = useViewport();
  return (
    <section id="contact" style={{
      padding: secPad(narrow), background: "oklch(11% 0.018 55)",
      borderTop: "1px solid oklch(93% 0.016 85 / 0.06)", textAlign: "center",
      position: "relative", overflow: "hidden",
    }}>
      <div style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        background: `radial-gradient(circle at 50% 60%, ${colors.accentGlow} 0%, transparent 60%)`,
      }} />
      <div style={{ position: "relative" }}>
        <div style={{ fontFamily: "monospace", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: colors.accent, marginBottom: 32 }}>{content.eyebrow}</div>
        <h2 style={{ fontFamily: "var(--fg-head)", fontSize: "clamp(52px, 8vw, 108px)", fontWeight: 300, letterSpacing: "-0.03em", lineHeight: 0.95, marginBottom: 40 }}>
          {renderLines(content.heading)}
        </h2>
        <p style={{ fontFamily: "var(--fg-body)", fontSize: 17, fontWeight: 300, color: "oklch(93% 0.016 85 / 0.5)", marginBottom: 52, lineHeight: 1.6 }}>
          {renderLines(content.body)}
        </p>
        <ObfuscatedEmail email={content.email} colors={colors} narrow={narrow} />
      </div>
    </section>
  );
}

// ── Footer ─────────────────────────────────────────────────────────
function Footer({ content }) {
  const year = new Date().getFullYear();
  const { narrow } = useViewport();
  return (
    <footer style={{
      padding: narrow ? "24px 20px" : "32px 48px", borderTop: "1px solid oklch(93% 0.016 85 / 0.06)",
      display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, flexWrap: "wrap",
    }}>
      <WordmarkSVG size="sm" />
      <span style={{ fontFamily: "monospace", fontSize: 11, color: "oklch(93% 0.016 85 / 0.25)", letterSpacing: "0.06em" }}>
        © {year} {content.name || "flairground"}
      </span>
    </footer>
  );
}

// ── Coming-soon splash ─────────────────────────────────────────────
// Deliberately generic: no wordmark, no business name unless the copy supplies
// it. Sets the browser tab title to the chosen heading too, so nothing in the
// page advertises the new name while it's gated.
function ComingSoon({ cfg, colors }) {
  const c = cfg || {};
  const eyebrow = c.eyebrow || "";
  const title = c.title || "Coming soon";
  const message = c.message || "";
  const email = (c.email || "").trim();
  const accent = (colors && colors.accent) || "#E08E3C";
  const glow = (colors && colors.accentGlow) || "oklch(73% 0.19 58 / 0.06)";

  // Hold the text hidden until fonts are ready, THEN fade/rise it in. This both
  // adds a graceful entrance and masks the web-font swap (no jarring glyph pop):
  // the pre-swap fallback never shows because nothing is visible until ready.
  const [ready, setReady] = useState(false);
  useEffect(() => {
    let done = false;
    const reveal = () => { if (!done) { done = true; setReady(true); } };
    const fonts = (typeof document !== "undefined" && document.fonts) ? document.fonts.ready : null;
    if (fonts && fonts.then) fonts.then(() => setTimeout(reveal, 120));
    const safety = setTimeout(reveal, 900); // never wait forever
    return () => { done = true; clearTimeout(safety); };
  }, []);

  useEffect(() => {
    const prev = document.title;
    document.title = title;
    return () => { document.title = prev; };
  }, [title]);

  const rise = (delay) => ({
    opacity: ready ? 1 : 0,
    transform: ready ? "translateY(0)" : "translateY(16px)",
    transition: "opacity 0.9s ease, transform 1s cubic-bezier(0.16,1,0.3,1)",
    transitionDelay: `${delay}ms`,
  });

  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 1, overflow: "hidden",
      display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
      textAlign: "center", padding: "clamp(24px, 6vw, 80px)",
      background: "oklch(9% 0.018 55)", color: "oklch(93% 0.016 85)",
    }}>
      <style>{`
        @keyframes csDrift { 0% { transform: translate3d(0,0,0) scale(1.04) } 100% { transform: translate3d(-2%,-1%,0) scale(1.1) } }
        @keyframes csPulse { 0%,100% { opacity: 0.9 } 50% { opacity: 0.3 } }
        @keyframes csGrad1 { 0% { transform: translate(-12%,-8%) scale(1) } 50% { transform: translate(10%,6%) scale(1.18) } 100% { transform: translate(-12%,-8%) scale(1) } }
        @keyframes csGrad2 { 0% { transform: translate(10%,8%) scale(1.1) } 50% { transform: translate(-8%,-6%) scale(1) } 100% { transform: translate(10%,8%) scale(1.1) } }
        @keyframes csHue { 0%,100% { opacity: 0.5 } 50% { opacity: 0.85 } }
        @media (prefers-reduced-motion: reduce) {
          .cs-grad-a, .cs-grad-b { animation: none !important; }
        }
      `}</style>

      {/* Slow animated accent gradient field — two large, drifting accent blooms */}
      <div style={{ position: "absolute", inset: 0, pointerEvents: "none", overflow: "hidden" }}>
        <div className="cs-grad-a" style={{
          position: "absolute", top: "-30%", left: "-20%", width: "80vw", height: "80vw",
          maxWidth: 1100, maxHeight: 1100, borderRadius: "50%",
          background: `radial-gradient(circle at 50% 50%, color-mix(in oklab, ${accent} 42%, transparent) 0%, transparent 62%)`,
          filter: "blur(40px)", mixBlendMode: "screen",
          animation: "csGrad1 26s ease-in-out infinite, csHue 13s ease-in-out infinite",
        }} />
        <div className="cs-grad-b" style={{
          position: "absolute", bottom: "-30%", right: "-20%", width: "70vw", height: "70vw",
          maxWidth: 950, maxHeight: 950, borderRadius: "50%",
          background: `radial-gradient(circle at 50% 50%, color-mix(in oklab, ${accent} 32%, transparent) 0%, transparent 60%)`,
          filter: "blur(50px)", mixBlendMode: "screen",
          animation: "csGrad2 34s ease-in-out infinite, csHue 17s ease-in-out infinite 2s",
        }} />
      </div>

      <div style={{ position: "absolute", inset: "-4%", animation: "csDrift 40s ease-in-out infinite alternate", pointerEvents: "none" }}>
        <svg width="100%" height="100%" style={{ display: "block" }}>
          <defs>
            <pattern id="csBed" width="46" height="46" patternUnits="userSpaceOnUse" patternTransform="rotate(22)">
              <line x1="0" y1="0" x2="0" y2="46" stroke="oklch(93% 0.016 85 / 0.04)" strokeWidth="1" />
            </pattern>
            <radialGradient id="csVig" cx="50%" cy="45%" r="70%">
              <stop offset="0%" stopColor={accent} stopOpacity="0.05" />
              <stop offset="100%" stopColor="oklch(9% 0.018 55)" stopOpacity="1" />
            </radialGradient>
          </defs>
          <rect width="100%" height="100%" fill="url(#csVig)" />
          <rect width="100%" height="100%" fill="url(#csBed)" />
        </svg>
      </div>

      <div style={{ position: "relative", maxWidth: "min(680px, 90vw)" }}>
        {eyebrow ? (
          <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 12, marginBottom: 26, ...rise(0) }}>
            <span style={{ width: 6, height: 6, borderRadius: "50%", background: accent, animation: "csPulse 2.4s ease-in-out infinite" }} />
            <span style={{ fontFamily: "monospace", fontSize: 12, letterSpacing: "0.22em", textTransform: "uppercase", color: accent }}>{eyebrow}</span>
          </div>
        ) : null}
        <h1 style={{
          fontFamily: "var(--fg-head)", fontWeight: 300, letterSpacing: "-0.02em", lineHeight: 1.02,
          fontSize: "clamp(40px, 8vw, 96px)", color: "oklch(96% 0.016 85)", textWrap: "balance",
          ...rise(120),
        }}>{title}</h1>
        {message ? (
          <p style={{
            fontFamily: "var(--fg-body)", fontWeight: 300, fontSize: "clamp(15px, 2vw, 19px)", lineHeight: 1.65,
            color: "oklch(93% 0.016 85 / 0.6)", marginTop: 26, maxWidth: 540, marginInline: "auto", textWrap: "pretty",
            ...rise(260),
          }}>{message}</p>
        ) : null}
        {email ? (
          <a href={`mailto:${email}`} style={{
            display: "inline-block", marginTop: 36, padding: "12px 28px", border: `1px solid ${accent}`,
            color: accent, textDecoration: "none", fontFamily: "var(--fg-body)", fontSize: 14, letterSpacing: "0.04em",
            ...rise(380),
          }}>{email}</a>
        ) : null}
      </div>
    </div>
  );
}

window.ComingSoon = ComingSoon;

// ── Register this skin ─────────────────────────────────────────────
// A skin is just a name + a type→component map (+ optional palettes). Every
// component honours the ({ content, theme, colors }) contract, so a second
// skin is a new file that calls CMS.defineSkin with its own component set.
window.CMS.defineSkin("editorial", {
  label: "Editorial Noir",
  components: { nav: Nav, hero: Hero, ticker: Ticker, work: Work, about: About, services: Services, values: Values, contact: Contact, footer: Footer },
});
