/* Visual post templates.
   Each template exports: { id, name, description, width, height, schema, Thumbnail, Render }.
   Registry is published as window.TEMPLATES. */

const TPL_TABLER_ICON_HINT = `Use only Tabler icon names from this list (omit the "ti-" prefix in your JSON, just the name):
cpu, brain, database, server, cloud, network, code, terminal, key, lock, shield, eye, chart-bar, chart-line, gauge,
flask, microscope, bulb, bolt, sparkles, rocket, target, flag, trophy, star, heart, users, user, message-circle,
mail, phone, calendar, clock, map-pin, settings, tool, wand, refresh, repeat, robot, microphone, headphones,
file-text, file-code, folder, search, link, plus, check, x, alert-triangle, info-circle, help, book, school,
briefcase, building, currency-dollar, credit-card, shopping-cart, package, truck, plane, car, bike,
arrow-up, arrow-down, arrow-right, arrow-left, chevron-right, trending-up, trending-down, scale, balance,
puzzle, layers, stack, hierarchy, sitemap, route, road-sign, compass, anchor, ship, world, palette`;

const LAYER_COLORS = {
  purple:  "#8b5cf6",
  indigo:  "#6366f1",
  blue:    "#3b82f6",
  cyan:    "#06b6d4",
  teal:    "#14b8a6",
  green:   "#22c55e",
  lime:    "#84cc16",
  amber:   "#f59e0b",
  orange:  "#f97316",
  red:     "#ef4444",
  pink:    "#ec4899",
  violet:  "#a855f7",
};
function colorFor(name, fallback = "#3b82f6") {
  return LAYER_COLORS[name] || fallback;
}

function withAnimation(template, opts = {}) {
  return Object.assign({}, template, {
    animated: true,
    gifDelay: opts.gifDelay || template.gifDelay || 900,
    getFrameCount: opts.getFrameCount || template.getFrameCount,
    RenderFrame: opts.RenderFrame || template.RenderFrame,
  });
}

/* ========================================================================
   1. WHITEBOARD TIMELINE
   ======================================================================== */
const whiteboardTemplate = {
  id: "whiteboard-timeline",
  name: "Whiteboard timeline",
  description: "Hand-drawn marker style. A year + an underlined headline + a parenthetical for each step.",
  width: 1080,
  height: 1350,
  schema: `{
  "title": "Topic title — 1 to 4 words, Title Case (this becomes the headline written across the top of the whiteboard)",
  "entries": [
    {
      "year": "2010",       // a year, version label, or sequential step (short: 2-5 chars)
      "headline": "Learn X, Y & Z fundamentals",  // 4-8 words, will be underlined in marker
      "subtext": "Short parenthetical detail of what that step covers"  // 5-12 words, fits inside parentheses
    }
    // ... produce EXACTLY 9 entries that progress logically from beginning to frontier
  ]
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect x="2" y="2" width="76" height="96" rx="3" fill="#f8f8f6" stroke="#9b9b95" strokeWidth="1.4"/>
        <line x1="18" y1="14" x2="62" y2="14" stroke="#1a1a17" strokeWidth="1.2" strokeLinecap="round"/>
        {[0,1,2,3,4,5].map(i => (
          <g key={i} transform={`translate(0, ${24 + i * 11})`}>
            <text x="8" y="0" fontSize="6" fill="#2563eb" fontFamily="Caveat" fontWeight="700">'2{i}</text>
            <line x1="22" y1="-1.5" x2="68" y2="-1.5" stroke="#1a1a17" strokeWidth="0.6"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    return (
      <div style={whiteboardStyles.frame}>
        <div style={whiteboardStyles.aluminumCorner.tl}></div>
        <div style={whiteboardStyles.aluminumCorner.tr}></div>
        <div style={whiteboardStyles.aluminumCorner.bl}></div>
        <div style={whiteboardStyles.aluminumCorner.br}></div>

        <div style={whiteboardStyles.title}>
          <span>{data.title || "Topic"}</span>
          <svg width="640" height="38" viewBox="0 0 640 38" style={{display:"block", margin:"-6px auto 0"}}>
            <path d="M 8 12 Q 200 4, 400 14 T 632 10" stroke="#2563eb" strokeWidth="5" fill="none" strokeLinecap="round"/>
            <path d="M 28 26 Q 220 18, 410 28 T 612 22" stroke="#2563eb" strokeWidth="4" fill="none" strokeLinecap="round" opacity="0.85"/>
          </svg>
        </div>

        <div style={whiteboardStyles.list}>
          {(data.entries || []).slice(0, 9).map((e, i) => (
            <div key={i} style={whiteboardStyles.row}>
              <div style={whiteboardStyles.year}>{e.year}</div>
              <svg width="58" height="36" viewBox="0 0 58 36" style={{flexShrink:0, marginTop: 8}}>
                <path d="M 4 18 L 48 18" stroke="#1a1a17" strokeWidth="3" strokeLinecap="round" fill="none"/>
                <path d="M 40 9 L 50 18 L 40 27" stroke="#1a1a17" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
              </svg>
              <div style={whiteboardStyles.text}>
                <div style={whiteboardStyles.headlineWrap}>
                  <span style={whiteboardStyles.headline}>{e.headline}</span>
                  <svg width="100%" height="10" viewBox="0 0 600 10" preserveAspectRatio="none" style={{display:"block", marginTop:-4}}>
                    <path d="M 4 4 Q 150 1, 300 5 T 596 3" stroke="#1a1a17" strokeWidth="2.4" fill="none" strokeLinecap="round"/>
                  </svg>
                </div>
                <div style={whiteboardStyles.subtext}>({e.subtext})</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  },
};

const whiteboardStyles = {
  frame: {
    width: 1080, height: 1350,
    background:
      "radial-gradient(ellipse at top left, #fdfdfb 0%, #f5f5f1 100%)",
    border: "16px solid #c8c8c2",
    borderRadius: 8,
    position: "relative",
    padding: "80px 80px 60px",
    fontFamily: "'Caveat', cursive",
    boxSizing: "border-box",
    overflow: "hidden",
  },
  aluminumCorner: {
    tl: { position:"absolute", top:-2, left:-2, width:32, height:32, background:"#9b9b95", borderTopLeftRadius:6 },
    tr: { position:"absolute", top:-2, right:-2, width:32, height:32, background:"#9b9b95", borderTopRightRadius:6 },
    bl: { position:"absolute", bottom:-2, left:-2, width:32, height:32, background:"#9b9b95", borderBottomLeftRadius:6 },
    br: { position:"absolute", bottom:-2, right:-2, width:32, height:32, background:"#9b9b95", borderBottomRightRadius:6 },
  },
  title: {
    textAlign: "center",
    fontSize: 96,
    color: "#1a1a17",
    fontWeight: 700,
    lineHeight: 1.05,
    marginBottom: 30,
  },
  list: {
    display: "flex",
    flexDirection: "column",
    gap: 14,
  },
  row: {
    display: "flex",
    alignItems: "flex-start",
    gap: 18,
  },
  year: {
    color: "#2563eb",
    fontSize: 52,
    fontWeight: 700,
    minWidth: 130,
    lineHeight: 1,
    paddingTop: 2,
  },
  text: { flex: 1, minWidth: 0 },
  headlineWrap: { display:"inline-block", maxWidth:"100%" },
  headline: {
    fontSize: 38,
    color: "#1a1a17",
    fontWeight: 600,
    lineHeight: 1.15,
  },
  subtext: {
    fontSize: 26,
    color: "#1a1a17",
    marginTop: 4,
    lineHeight: 1.2,
    opacity: 0.85,
  },
};

/* ========================================================================
   2. STACKED LAYERS CHECKLIST
   ======================================================================== */
const stackedTemplate = {
  id: "stacked-layers",
  name: "Stacked layers",
  description: "10 numbered horizontal rows. Each row has a colored dot, a numbered label, an italic tagline, and 3–4 bullet points.",
  width: 1080,
  height: 1500,
  schema: `{
  "title": "PRIMARY HEADLINE",                    // ALL CAPS, short and punchy (3-6 words). Will be displayed huge.
  "titleAccent": "ACCENT PART OF HEADLINE.",      // Optional second line of headline, gets accent color. Can be "".
  "subtitle": "Single subtitle line that explains the headline",
  "byline": "by Author Name",                     // optional, can be ""
  "layers": [
    {
      "number": "01",
      "label": "TOPIC LABEL",                     // 1-2 words, ALL CAPS
      "color": "purple",                          // one of: purple, indigo, blue, cyan, teal, green, lime, amber, orange, red, pink, violet
      "tagline": "One-sentence italic tagline summarizing this layer.",
      "items": [
        { "icon": "cpu", "text": "Category: specific items, examples, more examples" }
        // produce 3-4 items per layer
      ]
    }
    // produce EXACTLY 10 layers. Each layer must use a DIFFERENT color from the list above.
  ],
  "footer": "Closing punchline (1-2 short lines, separated by \\n)"
}`,

  Thumbnail() {
    const colors = ["#8b5cf6","#6366f1","#3b82f6","#06b6d4","#14b8a6","#22c55e","#f59e0b","#f97316","#ef4444","#ec4899"];
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#ffffff"/>
        <text x="40" y="12" textAnchor="middle" fontSize="7" fontWeight="800" fill="#1a1a17">HEADLINE</text>
        <line x1="6" y1="20" x2="6" y2="92" stroke="#e6e6e1" strokeWidth="0.5" strokeDasharray="1 1"/>
        {colors.map((c, i) => (
          <g key={i} transform={`translate(0, ${22 + i * 7})`}>
            <circle cx="6" cy="2" r="1.8" fill={c}/>
            <rect x="11" y="0" width="62" height="4.5" rx="1" fill="none" stroke={c} strokeWidth="0.4" opacity="0.6"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    return (
      <div style={stackedStyles.frame}>
        <div style={stackedStyles.titleBlock}>
          <h1 style={stackedStyles.title}>
            <span style={stackedStyles.titleA}>{data.title}</span>
            {data.titleAccent && <><br/><span style={stackedStyles.titleB}>{data.titleAccent}</span></>}
          </h1>
          {data.subtitle && <div style={stackedStyles.subtitle}>{data.subtitle}</div>}
          {data.byline && <div style={stackedStyles.byline}>{data.byline}</div>}
        </div>

        <div style={stackedStyles.layersWrap}>
          <div style={stackedStyles.timeline}></div>
          {(data.layers || []).slice(0, 10).map((layer, i) => {
            const c = colorFor(layer.color);
            return (
              <div key={i} style={stackedStyles.layer}>
                <div style={{...stackedStyles.dot, background: c}}></div>
                <div style={{...stackedStyles.layerCard, borderColor: c}}>
                  <div style={stackedStyles.layerHeader}>
                    <div style={{...stackedStyles.number, color: c}}>{layer.number}</div>
                    <div style={stackedStyles.label}>{layer.label}</div>
                  </div>
                  <div style={{...stackedStyles.tagline, color: c}}>{layer.tagline}</div>
                  <div style={stackedStyles.itemList}>
                    {(layer.items || []).map((it, j) => (
                      <div key={j} style={stackedStyles.item}>
                        <i className={"ti ti-" + (it.icon || "circle")} style={{...stackedStyles.itemIcon, color: c}}></i>
                        <span style={stackedStyles.itemText}>{it.text}</span>
                      </div>
                    ))}
                  </div>
                </div>
              </div>
            );
          })}
        </div>

        {data.footer && (
          <div style={stackedStyles.footer}>
            {data.footer.split("\n").map((l, i) => <div key={i}>{l}</div>)}
          </div>
        )}
      </div>
    );
  },
};

const stackedStyles = {
  frame: {
    width: 1080, height: 1500,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "56px 56px 40px",
    boxSizing: "border-box",
    color: "#1a1a17",
    position: "relative",
  },
  titleBlock: { textAlign: "center", marginBottom: 28 },
  title: { fontSize: 60, lineHeight: 1.0, fontWeight: 900, margin: 0, letterSpacing: -1.5 },
  titleA: { color: "#1a1a17" },
  titleB: { color: "#22c55e" },
  subtitle: { fontSize: 22, color: "#5b5b56", marginTop: 14, lineHeight: 1.3, fontWeight: 400 },
  byline: { fontSize: 15, color: "#8a8a83", marginTop: 8 },
  layersWrap: { position:"relative", display:"flex", flexDirection:"column", gap: 10 },
  timeline: {
    position:"absolute", top: 8, bottom: 8, left: 14,
    borderLeft: "2px dashed #d6d6d1",
  },
  layer: {
    display: "flex",
    alignItems: "stretch",
    gap: 18,
    position: "relative",
  },
  dot: {
    width: 16, height: 16, borderRadius: "50%",
    marginTop: 26,
    flexShrink: 0,
    marginLeft: 6,
    boxShadow: "0 0 0 4px #ffffff",
    zIndex: 1,
  },
  layerCard: {
    flex: 1,
    border: "1.5px solid",
    borderRadius: 10,
    padding: "12px 18px 14px",
    background: "#ffffff",
    minWidth: 0,
  },
  layerHeader: { display:"flex", alignItems:"baseline", gap: 12 },
  number: { fontWeight: 900, fontSize: 28, letterSpacing: -1, lineHeight: 1 },
  label: { fontWeight: 800, fontSize: 22, letterSpacing: 0.5, color: "#1a1a17" },
  tagline: { fontSize: 14, fontStyle: "italic", marginTop: 2, marginBottom: 8, fontWeight: 500 },
  itemList: { display:"flex", flexDirection:"column", gap: 4 },
  item: { display:"flex", gap: 10, alignItems:"flex-start", fontSize: 14, lineHeight: 1.35, color: "#1a1a17" },
  itemIcon: { fontSize: 16, marginTop: 1, flexShrink: 0, width: 18, textAlign:"center" },
  itemText: { flex: 1 },
  footer: {
    marginTop: 22,
    textAlign: "center",
    fontWeight: 700,
    fontSize: 22,
    lineHeight: 1.3,
    color: "#1a1a17",
  },
};

/* ========================================================================
   3. ANATOMY / HUB AND SPOKE
   ======================================================================== */
const anatomyTemplate = {
  id: "anatomy-cards",
  name: "Anatomy diagram",
  description: "Central hub with 10 satellite cards connected by lines. Great for 'the X components of Y'.",
  width: 1080,
  height: 1500,
  schema: `{
  "eyebrow": "SHORT EYEBROW LABEL",         // ALL CAPS, 3-6 words
  "title": "Anatomy of an X",               // Catchy headline (3-6 words). Will be displayed huge.
  "subtitle": "The N components every X needs",  // single-line subtitle
  "byline": "By Author Name",               // optional, can be ""
  "center": {
    "title": "THE THING",                   // 1-3 words, ALL CAPS, name of the central element
    "items": [                              // 1-3 items shown inside the central box
      { "name": "Item", "icon": "robot" }
    ]
  },
  "cards": [                                // EXACTLY 10 cards. Index 0-4 will appear on the LEFT, 5-9 on the RIGHT.
    {
      "number": "01",
      "title": "COMPONENT NAME",            // 1-3 words, ALL CAPS
      "subtitle": "Short description (3-7 words)",
      "icon": "file-text"
    }
  ],
  "footer": "Pull quote in italics (one sentence)"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#f5f7fb"/>
        <text x="40" y="14" textAnchor="middle" fontSize="6" fontWeight="800" fill="#1a1a17">Anatomy</text>
        <rect x="32" y="40" width="16" height="14" rx="2" fill="#ffffff" stroke="#3b82f6" strokeWidth="0.6"/>
        {[0,1,2,3,4].map(i => (
          <g key={"L"+i}>
            <rect x="4" y={22 + i*12} width="18" height="9" rx="1.5" fill="#ffffff" stroke="#cfd8e3" strokeWidth="0.4"/>
            <line x1="22" y1={26 + i*12} x2="32" y2={47} stroke="#3b82f6" strokeWidth="0.4"/>
            <circle cx="5.5" cy={23.5 + i*12} r="1.4" fill="#3b82f6"/>
          </g>
        ))}
        {[0,1,2,3,4].map(i => (
          <g key={"R"+i}>
            <rect x="58" y={22 + i*12} width="18" height="9" rx="1.5" fill="#ffffff" stroke="#cfd8e3" strokeWidth="0.4"/>
            <line x1="58" y1={26 + i*12} x2="48" y2={47} stroke="#3b82f6" strokeWidth="0.4"/>
            <circle cx="74.5" cy={23.5 + i*12} r="1.4" fill="#3b82f6"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const cards = (data.cards || []).slice(0, 10);
    const left  = cards.slice(0, 5);
    const right = cards.slice(5, 10);

    // Positions: cards are stacked vertically in two columns.
    // Hub is centered horizontally and vertically.
    const W = 1080, H = 1500;
    const cardW = 360, cardH = 130;
    const topOfCards = 360;
    const gap = 26;
    const colLeftX = 60;
    const colRightX = W - 60 - cardW;
    const hubX = W / 2;
    const hubY = topOfCards + ( (cardH * 5 + gap * 4) / 2 );

    const cardCenters = [];
    left.forEach((_, i) => {
      cardCenters.push({
        side: "L",
        x: colLeftX + cardW,        // right edge of left card
        y: topOfCards + i * (cardH + gap) + cardH / 2,
      });
    });
    right.forEach((_, i) => {
      cardCenters.push({
        side: "R",
        x: colRightX,               // left edge of right card
        y: topOfCards + i * (cardH + gap) + cardH / 2,
      });
    });

    return (
      <div style={anatomyStyles.frame}>
        <div style={anatomyStyles.eyebrow}>{data.eyebrow}</div>
        <h1 style={anatomyStyles.title}>{data.title}</h1>
        {data.subtitle && <div style={anatomyStyles.subtitle}>{data.subtitle}</div>}
        {data.byline && <div style={anatomyStyles.byline}>{data.byline}</div>}

        {/* Connector lines */}
        <svg width={W} height={H} style={anatomyStyles.connectors}>
          {cardCenters.map((c, i) => {
            const startX = c.x;
            const startY = c.y;
            const endX   = c.side === "L" ? hubX - 200 : hubX + 200;
            const endY   = hubY;
            const midX   = c.side === "L" ? startX + 60 : startX - 60;
            const path = `M ${startX} ${startY} L ${midX} ${startY} L ${endX} ${endY}`;
            return (
              <g key={i}>
                <path d={path} stroke="#3b82f6" strokeWidth="2" fill="none" />
                <circle cx={endX} cy={endY} r="3" fill="#3b82f6"/>
              </g>
            );
          })}
        </svg>

        {/* Left column */}
        {left.map((card, i) => (
          <div key={"L"+i} style={{
            ...anatomyStyles.card,
            left: colLeftX,
            top:  topOfCards + i * (cardH + gap),
            width: cardW, height: cardH,
          }}>
            <AnatomyCard {...card} side="L"/>
          </div>
        ))}
        {/* Right column */}
        {right.map((card, i) => (
          <div key={"R"+i} style={{
            ...anatomyStyles.card,
            left: colRightX,
            top:  topOfCards + i * (cardH + gap),
            width: cardW, height: cardH,
          }}>
            <AnatomyCard {...card} side="R"/>
          </div>
        ))}

        {/* Central hub */}
        <div style={{
          ...anatomyStyles.hub,
          left: hubX - 200, top: hubY - 110,
          width: 400, height: 220,
        }}>
          <div style={anatomyStyles.hubTitle}>{data.center?.title || "CENTER"}</div>
          <div style={anatomyStyles.hubItems}>
            {(data.center?.items || []).slice(0, 3).map((it, i) => (
              <div key={i} style={anatomyStyles.hubItem}>
                <div style={anatomyStyles.hubIconWrap}>
                  <i className={"ti ti-" + (it.icon || "circle")} style={anatomyStyles.hubIcon}></i>
                </div>
                <div style={anatomyStyles.hubItemName}>{it.name}</div>
              </div>
            ))}
          </div>
        </div>

        {data.footer && (
          <div style={anatomyStyles.footer}>"{data.footer}"</div>
        )}
      </div>
    );
  },
};

function AnatomyCard({ number, title, subtitle, icon, side }) {
  return (
    <>
      <div style={{...anatomyStyles.cardNumber, [side === "L" ? "right" : "left"]: -16, top: -16 }}>{number}</div>
      <div style={anatomyStyles.cardInner}>
        <div style={anatomyStyles.cardIconWrap}>
          <i className={"ti ti-" + (icon || "circle")} style={anatomyStyles.cardIcon}></i>
        </div>
        <div style={anatomyStyles.cardText}>
          <div style={anatomyStyles.cardTitle}>{title}</div>
          <div style={anatomyStyles.cardSubtitle}>{subtitle}</div>
        </div>
      </div>
    </>
  );
}

const anatomyStyles = {
  frame: {
    width: 1080, height: 1500,
    background: "linear-gradient(180deg, #f4f7fb 0%, #eef2f8 100%)",
    fontFamily: "'Inter', sans-serif",
    padding: "60px 0 0",
    boxSizing: "border-box",
    color: "#0f172a",
    position: "relative",
    overflow: "hidden",
  },
  eyebrow: { textAlign:"center", fontSize: 14, fontWeight: 700, color: "#3b82f6", letterSpacing: 2, textTransform:"uppercase" },
  title: { textAlign:"center", fontSize: 64, fontWeight: 900, lineHeight: 1.05, margin: "14px 0 8px", letterSpacing: -1.5 },
  subtitle: { textAlign:"center", fontSize: 22, color: "#475569", lineHeight: 1.3, marginBottom: 6 },
  byline: { textAlign:"center", fontSize: 14, color: "#64748b" },
  connectors: { position:"absolute", top:0, left:0, pointerEvents:"none" },
  card: { position:"absolute", zIndex: 2 },
  cardInner: {
    width:"100%", height:"100%",
    background:"#ffffff",
    border:"1.5px solid #cfd8e3",
    borderRadius: 10,
    padding: "16px 18px",
    display:"flex",
    gap: 16,
    alignItems:"center",
    boxSizing:"border-box",
  },
  cardNumber: {
    position:"absolute",
    width: 36, height: 36, borderRadius: "50%",
    background:"#3b82f6", color:"#ffffff",
    display:"grid", placeItems:"center",
    fontWeight: 700, fontSize: 13,
    zIndex: 3,
    border:"3px solid #ffffff",
  },
  cardIconWrap: {
    width: 56, height: 56, flexShrink: 0,
    background:"#eef4ff", borderRadius: 8,
    display:"grid", placeItems:"center",
    color:"#3b82f6",
  },
  cardIcon: { fontSize: 30 },
  cardText: { flex: 1, minWidth: 0 },
  cardTitle: { fontWeight: 800, fontSize: 18, letterSpacing: 0.5, color:"#0f172a", lineHeight: 1.15 },
  cardSubtitle: { fontSize: 14, color:"#475569", marginTop: 4, lineHeight: 1.3 },
  hub: {
    position:"absolute",
    background:"#ffffff",
    border:"1.5px solid #cfd8e3",
    borderRadius: 14,
    boxShadow:"0 0 0 6px #ffffff",
    padding: "20px 20px",
    boxSizing:"border-box",
    zIndex: 4,
    display:"flex", flexDirection:"column",
  },
  hubTitle: { textAlign:"center", fontSize: 20, fontWeight: 800, letterSpacing: 1.5, color: "#0f172a" },
  hubItems: { display:"flex", justifyContent:"space-around", alignItems:"center", flex: 1, marginTop: 12 },
  hubItem: { display:"flex", flexDirection:"column", alignItems:"center", gap: 6 },
  hubIconWrap: { width: 56, height: 56, background:"#eef4ff", color:"#3b82f6", borderRadius: 10, display:"grid", placeItems:"center" },
  hubIcon: { fontSize: 28 },
  hubItemName: { fontSize: 14, fontWeight: 600, color:"#0f172a" },
  footer: {
    position:"absolute",
    left: 60, right: 60, bottom: 40,
    textAlign:"center", fontSize: 18, fontStyle:"italic", color:"#475569",
    paddingTop: 18, borderTop: "1px solid #cfd8e3",
  },
};

/* ========================================================================
   4. VS COMPARISON TABLE  (800 × 1000 — most common static layout)
   ======================================================================== */
const vsTemplate = {
  id: "vs-comparison",
  name: "Comparison table",
  category: "comparison",
  description: "Two-column side-by-side table. Left vs Right, row by row. Great for comparing tools, approaches, or concepts.",
  width: 800,
  height: 1000,
  schema: `{
  "eyebrow": "THE COMPARISON",               // e.g. "AI MODELS · 2025"  — short ALL-CAPS label
  "leftLabel": "OPTION A",                   // left column header, 1-3 words ALL CAPS
  "rightLabel": "OPTION B",                  // right column header, 1-3 words ALL CAPS
  "leftColor": "blue",                       // accent for left column — one of LAYER_COLORS
  "rightColor": "orange",                    // accent for right column — one of LAYER_COLORS
  "rows": [
    {
      "aspect": "Aspect label",              // 1-3 words, the dimension being compared (e.g. "Definition", "Speed")
      "left": "Left side answer (10-15 words max)",
      "right": "Right side answer (10-15 words max)"
    }
    // produce EXACTLY 7 rows
  ],
  "tagline": "One punchy closing line summarising the key takeaway."
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#f8faff"/>
        <text x="40" y="10" textAnchor="middle" fontSize="5.5" fontWeight="800" fill="#1a1a17">A vs B</text>
        <rect x="4" y="14" width="34" height="7" rx="2" fill="#3b82f6" opacity="0.15"/>
        <text x="21" y="20" textAnchor="middle" fontSize="5" fontWeight="700" fill="#3b82f6">OPTION A</text>
        <rect x="42" y="14" width="34" height="7" rx="2" fill="#f97316" opacity="0.15"/>
        <text x="59" y="20" textAnchor="middle" fontSize="5" fontWeight="700" fill="#f97316">OPTION B</text>
        {[0,1,2,3,4,5,6].map(i => (
          <g key={i} transform={`translate(0,${26 + i*10})`}>
            <rect x="4" y="0" width="72" height="8" rx="1.5" fill={i%2===0 ? "#f1f5f9" : "#ffffff"}/>
            <rect x="6" y="1.5" width="32" height="5" rx="1" fill="#3b82f6" opacity="0.2"/>
            <rect x="42" y="1.5" width="32" height="5" rx="1" fill="#f97316" opacity="0.2"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const lc = colorFor(data.leftColor, "#3b82f6");
    const rc = colorFor(data.rightColor, "#f97316");
    const rows = (data.rows || []).slice(0, 7);
    return (
      <div style={vsStyles.frame}>
        <div style={vsStyles.eyebrow}>{data.eyebrow}</div>
        <div style={vsStyles.header}>
          <div style={{...vsStyles.colHeader, background: lc}}>{data.leftLabel}</div>
          <div style={vsStyles.vsBadge}>VS</div>
          <div style={{...vsStyles.colHeader, background: rc}}>{data.rightLabel}</div>
        </div>
        <div style={vsStyles.table}>
          {rows.map((row, i) => (
            <div key={i} style={{...vsStyles.row, background: i % 2 === 0 ? "#f8fafc" : "#ffffff"}}>
              <div style={{...vsStyles.cell, borderRight: `3px solid ${lc}22`}}>
                <span style={vsStyles.cellText}>{row.left}</span>
              </div>
              <div style={vsStyles.aspectCell}>
                <span style={vsStyles.aspectText}>{row.aspect}</span>
              </div>
              <div style={{...vsStyles.cell, borderLeft: `3px solid ${rc}22`}}>
                <span style={vsStyles.cellText}>{row.right}</span>
              </div>
            </div>
          ))}
        </div>
        {data.tagline && (
          <div style={vsStyles.tagline}>{data.tagline}</div>
        )}
      </div>
    );
  },
};

const vsStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "48px 40px 36px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  eyebrow: {
    textAlign: "center",
    fontSize: 13,
    fontWeight: 700,
    letterSpacing: 3,
    textTransform: "uppercase",
    color: "#94a3b8",
    marginBottom: 20,
  },
  header: {
    display: "flex",
    alignItems: "center",
    gap: 12,
    marginBottom: 20,
  },
  colHeader: {
    flex: 1,
    color: "#ffffff",
    fontWeight: 800,
    fontSize: 22,
    letterSpacing: 1.5,
    textTransform: "uppercase",
    textAlign: "center",
    padding: "14px 10px",
    borderRadius: 10,
  },
  vsBadge: {
    width: 52, height: 52,
    borderRadius: "50%",
    background: "#1a1a17",
    color: "#ffffff",
    display: "grid",
    placeItems: "center",
    fontWeight: 900,
    fontSize: 15,
    letterSpacing: 1,
    flexShrink: 0,
  },
  table: {
    flex: 1,
    display: "flex",
    flexDirection: "column",
    borderRadius: 12,
    overflow: "hidden",
    border: "1.5px solid #e2e8f0",
  },
  row: {
    display: "flex",
    alignItems: "stretch",
    flex: 1,
  },
  cell: {
    flex: 1,
    padding: "12px 16px",
    display: "flex",
    alignItems: "center",
  },
  cellText: {
    fontSize: 15,
    color: "#1e293b",
    lineHeight: 1.4,
    fontWeight: 450,
  },
  aspectCell: {
    width: 110,
    flexShrink: 0,
    background: "#1a1a17",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    padding: "8px 6px",
  },
  aspectText: {
    fontSize: 11,
    fontWeight: 700,
    color: "#ffffff",
    textTransform: "uppercase",
    letterSpacing: 0.8,
    textAlign: "center",
    lineHeight: 1.3,
  },
  tagline: {
    marginTop: 20,
    textAlign: "center",
    fontSize: 16,
    fontWeight: 600,
    color: "#475569",
    fontStyle: "italic",
    lineHeight: 1.4,
  },
};

/* ========================================================================
   5. NUMBERED TIP CARDS  (800 × 1071 — second most common static)
   ======================================================================== */
const tipCardsTemplate = {
  id: "tip-cards",
  name: "Tip cards",
  description: "Bold headline + 6 numbered cards in a 2-column grid. Each card has an icon, a short title, and one sentence.",
  width: 800,
  height: 1071,
  schema: `{
  "eyebrow": "QUICK GUIDE",                  // short ALL-CAPS label
  "title": "6 Things Every X Must Know",     // punchy headline, 5-9 words
  "subtitle": "One sentence framing the value of these tips.",
  "accent": "blue",                          // main accent color — one of LAYER_COLORS
  "tips": [
    {
      "number": "01",
      "icon": "bulb",
      "headline": "Short tip title",         // 3-6 words
      "body": "One clear sentence expanding the tip.",
      "color": "purple"                      // card accent — each card must use a DIFFERENT color
    }
    // produce EXACTLY 6 tips
  ],
  "footer": "Closing punchline or CTA (1 short sentence)"
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444"];
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#ffffff"/>
        <rect x="10" y="6" width="60" height="7" rx="2" fill="#1a1a17" opacity="0.08"/>
        <text x="40" y="12" textAnchor="middle" fontSize="6" fontWeight="800" fill="#1a1a17">6 TIP CARDS</text>
        {[0,1,2].map(row => [0,1].map(col => {
          const i = row*2+col;
          return (
            <g key={i} transform={`translate(${5 + col*38}, ${22 + row*24})`}>
              <rect width="35" height="20" rx="3" fill={cols[i]} opacity="0.1" stroke={cols[i]} strokeWidth="0.5"/>
              <circle cx="6" cy="6" r="3" fill={cols[i]} opacity="0.7"/>
              <rect x="12" y="4" width="18" height="3" rx="1" fill={cols[i]} opacity="0.5"/>
              <rect x="4" y="12" width="27" height="2.5" rx="1" fill="#94a3b8" opacity="0.5"/>
            </g>
          );
        }))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const tips = (data.tips || []).slice(0, 6);
    return (
      <div style={tipStyles.frame}>
        <div style={{...tipStyles.topBar, background: ac}}></div>
        <div style={tipStyles.header}>
          <div style={{...tipStyles.eyebrow, color: ac}}>{data.eyebrow}</div>
          <h1 style={tipStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={tipStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={tipStyles.grid}>
          {tips.map((tip, i) => {
            const c = colorFor(tip.color, ac);
            return (
              <div key={i} style={{...tipStyles.card, borderTop: `4px solid ${c}`}}>
                <div style={tipStyles.cardTop}>
                  <div style={{...tipStyles.num, color: c}}>{tip.number}</div>
                  <div style={{...tipStyles.iconWrap, background: c + "18", color: c}}>
                    <i className={"ti ti-" + (tip.icon || "bulb")} style={{fontSize: 22}}></i>
                  </div>
                </div>
                <div style={{...tipStyles.cardHeadline, color: c}}>{tip.headline}</div>
                <div style={tipStyles.cardBody}>{tip.body}</div>
              </div>
            );
          })}
        </div>
        {data.footer && (
          <div style={{...tipStyles.footer, borderTop: `2px solid ${ac}33`, color: ac}}>
            {data.footer}
          </div>
        )}
      </div>
    );
  },
};

const tipStyles = {
  frame: {
    width: 800, height: 1071,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    overflow: "hidden",
  },
  topBar: { height: 6, flexShrink: 0 },
  header: {
    padding: "28px 40px 18px",
    textAlign: "center",
  },
  eyebrow: {
    fontSize: 12,
    fontWeight: 700,
    letterSpacing: 3,
    textTransform: "uppercase",
    marginBottom: 10,
  },
  title: {
    fontSize: 38,
    fontWeight: 900,
    lineHeight: 1.1,
    color: "#0f172a",
    margin: "0 0 10px",
    letterSpacing: -0.5,
  },
  subtitle: {
    fontSize: 15,
    color: "#64748b",
    lineHeight: 1.4,
    fontWeight: 400,
  },
  grid: {
    flex: 1,
    display: "grid",
    gridTemplateColumns: "1fr 1fr",
    gap: 14,
    padding: "0 32px",
  },
  card: {
    background: "#f8fafc",
    borderRadius: 12,
    padding: "18px 18px 16px",
    display: "flex",
    flexDirection: "column",
    gap: 8,
  },
  cardTop: {
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
  },
  num: {
    fontSize: 26,
    fontWeight: 900,
    letterSpacing: -1,
    lineHeight: 1,
  },
  iconWrap: {
    width: 42, height: 42,
    borderRadius: 10,
    display: "grid",
    placeItems: "center",
  },
  cardHeadline: {
    fontSize: 16,
    fontWeight: 800,
    lineHeight: 1.2,
  },
  cardBody: {
    fontSize: 13,
    color: "#475569",
    lineHeight: 1.5,
    flex: 1,
  },
  footer: {
    margin: "10px 32px 18px",
    padding: "14px 0 0",
    textAlign: "center",
    fontSize: 14,
    fontWeight: 700,
    letterSpacing: 0.3,
  },
};

/* ========================================================================
   6. PROCESS FLOW  (1080 × 1350 — Instagram/LinkedIn format)
   ======================================================================== */
const processFlowTemplate = {
  id: "process-flow",
  name: "Process flow",
  description: "A vertical numbered flow of 5–6 steps connected by arrows. Each step has a title, description, and icon.",
  width: 1080,
  height: 1350,
  schema: `{
  "eyebrow": "STEP BY STEP",                 // ALL-CAPS short label
  "title": "How X Works",                    // 3-6 word headline
  "subtitle": "Single sentence framing the process.",
  "accent": "blue",                          // primary accent color — one of LAYER_COLORS
  "steps": [
    {
      "number": "01",
      "icon": "code",
      "title": "Step title (3-5 words)",
      "description": "One concise sentence explaining what happens at this step.",
      "color": "purple"                      // each step gets a DIFFERENT color
    }
    // produce EXACTLY 5 steps
  ],
  "insight": "Key insight or takeaway from this process (1 sentence)"
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b"];
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#f8faff"/>
        <text x="40" y="11" textAnchor="middle" fontSize="6" fontWeight="800" fill="#1a1a17">PROCESS FLOW</text>
        {cols.map((c, i) => (
          <g key={i} transform={`translate(8, ${18 + i * 16})`}>
            <circle cx="5" cy="5" r="5" fill={c}/>
            <text x="5" y="7.5" textAnchor="middle" fontSize="5" fontWeight="800" fill="#ffffff">{i+1}</text>
            <rect x="14" y="1" width="52" height="9" rx="2" fill={c} opacity="0.12"/>
            <rect x="16" y="3" width="28" height="3" rx="1" fill={c} opacity="0.5"/>
            {i < 4 && <line x1="5" y1="10" x2="5" y2="18" stroke={c} strokeWidth="1" strokeDasharray="2 1"/>}
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const steps = (data.steps || []).slice(0, 5);
    return (
      <div style={processStyles.frame}>
        <div style={{...processStyles.headerBar, background: `linear-gradient(135deg, ${ac} 0%, ${ac}99 100%)`}}>
          <div style={processStyles.eyebrow}>{data.eyebrow}</div>
          <h1 style={processStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={processStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={processStyles.stepsWrap}>
          {steps.map((step, i) => {
            const c = colorFor(step.color, ac);
            return (
              <div key={i} style={processStyles.stepRow}>
                <div style={processStyles.spine}>
                  <div style={{...processStyles.circle, background: c}}>
                    <i className={"ti ti-" + (step.icon || "star")} style={{fontSize: 26, color: "#ffffff"}}></i>
                  </div>
                  {i < steps.length - 1 && <div style={{...processStyles.connector, background: `linear-gradient(180deg, ${c} 0%, ${colorFor(steps[i+1]?.color, ac)} 100%)`}}></div>}
                </div>
                <div style={{...processStyles.stepCard, borderLeft: `4px solid ${c}`}}>
                  <div style={processStyles.stepMeta}>
                    <span style={{...processStyles.stepNum, color: c}}>{step.number}</span>
                    <span style={processStyles.stepTitle}>{step.title}</span>
                  </div>
                  <div style={processStyles.stepDesc}>{step.description}</div>
                </div>
              </div>
            );
          })}
        </div>
        {data.insight && (
          <div style={{...processStyles.insight, borderLeft: `5px solid ${ac}`, borderRight: `5px solid ${ac}`}}>
            <div style={{...processStyles.insightLabel, color: ac}}>KEY INSIGHT</div>
            <div style={processStyles.insightText}>{data.insight}</div>
          </div>
        )}
      </div>
    );
  },
};

const processStyles = {
  frame: {
    width: 1080, height: 1350,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    overflow: "hidden",
  },
  headerBar: {
    padding: "50px 64px 40px",
    color: "#ffffff",
  },
  eyebrow: {
    fontSize: 13,
    fontWeight: 700,
    letterSpacing: 3,
    textTransform: "uppercase",
    opacity: 0.8,
    marginBottom: 10,
  },
  title: {
    fontSize: 62,
    fontWeight: 900,
    lineHeight: 1.05,
    margin: "0 0 12px",
    letterSpacing: -1,
    color: "#ffffff",
  },
  subtitle: {
    fontSize: 20,
    opacity: 0.88,
    lineHeight: 1.4,
    fontWeight: 400,
  },
  stepsWrap: {
    flex: 1,
    display: "flex",
    flexDirection: "column",
    padding: "36px 56px 20px",
    gap: 0,
  },
  stepRow: {
    display: "flex",
    gap: 24,
    alignItems: "stretch",
  },
  spine: {
    display: "flex",
    flexDirection: "column",
    alignItems: "center",
    flexShrink: 0,
    width: 72,
  },
  circle: {
    width: 72, height: 72,
    borderRadius: "50%",
    display: "grid",
    placeItems: "center",
    flexShrink: 0,
    zIndex: 1,
  },
  connector: {
    width: 4,
    flex: 1,
    minHeight: 20,
    borderRadius: 2,
    margin: "0 auto",
    opacity: 0.35,
  },
  stepCard: {
    flex: 1,
    padding: "14px 20px 14px",
    marginBottom: 20,
    background: "#f8fafc",
    borderRadius: "0 12px 12px 0",
  },
  stepMeta: {
    display: "flex",
    alignItems: "baseline",
    gap: 10,
    marginBottom: 6,
  },
  stepNum: {
    fontSize: 22,
    fontWeight: 900,
    letterSpacing: -0.5,
    lineHeight: 1,
    flexShrink: 0,
  },
  stepTitle: {
    fontSize: 22,
    fontWeight: 800,
    color: "#0f172a",
    lineHeight: 1.2,
  },
  stepDesc: {
    fontSize: 15,
    color: "#475569",
    lineHeight: 1.55,
  },
  insight: {
    margin: "8px 40px 32px",
    padding: "18px 28px",
    background: "#f8fafc",
    borderRadius: 4,
    textAlign: "center",
  },
  insightLabel: {
    fontSize: 11,
    fontWeight: 700,
    letterSpacing: 2.5,
    textTransform: "uppercase",
    marginBottom: 8,
  },
  insightText: {
    fontSize: 18,
    fontWeight: 600,
    color: "#1e293b",
    lineHeight: 1.45,
    fontStyle: "italic",
  },
};

/* ========================================================================
   7. CONCEPT GRID  (800 × 1140 — 4th most common)
   ======================================================================== */
const conceptGridTemplate = {
  id: "concept-grid",
  name: "Concept grid",
  description: "Bold header + 6 concept cards in a 2×3 grid. Each card has an icon, a label, and a two-line body.",
  width: 800,
  height: 1140,
  schema: `{
  "headline": "6 MUST-KNOW CONCEPTS",        // ALL-CAPS, 3-6 words
  "subtitle": "subtitle framing sentence",
  "byline": "by Author",                     // optional, can be ""
  "cards": [
    {
      "icon": "brain",
      "label": "CONCEPT NAME",               // 1-3 words ALL CAPS
      "body": "Two concise sentences explaining the concept. Keep it dense and specific.",
      "color": "purple"                      // each card must use a DIFFERENT color
    }
    // produce EXACTLY 6 cards
  ],
  "footer": "Closing one-liner"
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444"];
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#ffffff"/>
        <rect x="4" y="4" width="72" height="12" rx="3" fill="#1a1a17" opacity="0.06"/>
        <text x="40" y="12" textAnchor="middle" fontSize="6" fontWeight="800" fill="#1a1a17">CONCEPT GRID</text>
        {[0,1,2].map(row => [0,1].map(col => {
          const c = cols[row*2+col];
          return (
            <g key={row*2+col} transform={`translate(${5 + col*38}, ${20 + row*26})`}>
              <rect width="35" height="23" rx="3" fill={c} opacity="0.1"/>
              <rect x="0" y="0" width="35" height="4" rx="3" fill={c} opacity="0.6"/>
              <circle cx="5" cy="10" r="3.5" fill={c} opacity="0.5"/>
              <rect x="11" y="8" width="20" height="3" rx="1" fill="#1a1a17" opacity="0.25"/>
              <rect x="4" y="15" width="27" height="2.5" rx="1" fill="#94a3b8" opacity="0.4"/>
              <rect x="4" y="19" width="21" height="2.5" rx="1" fill="#94a3b8" opacity="0.3"/>
            </g>
          );
        }))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const cards = (data.cards || []).slice(0, 6);
    return (
      <div style={gridStyles.frame}>
        <div style={gridStyles.headerWrap}>
          <h1 style={gridStyles.headline}>{data.headline}</h1>
          {data.subtitle && <div style={gridStyles.subtitle}>{data.subtitle}</div>}
          {data.byline && <div style={gridStyles.byline}>{data.byline}</div>}
        </div>
        <div style={gridStyles.grid}>
          {cards.map((card, i) => {
            const c = colorFor(card.color, "#3b82f6");
            return (
              <div key={i} style={{...gridStyles.card, borderTop: `5px solid ${c}`}}>
                <div style={{...gridStyles.iconCircle, background: c + "18", color: c}}>
                  <i className={"ti ti-" + (card.icon || "star")} style={{fontSize: 28}}></i>
                </div>
                <div style={{...gridStyles.cardLabel, color: c}}>{card.label}</div>
                <div style={gridStyles.cardBody}>{card.body}</div>
              </div>
            );
          })}
        </div>
        {data.footer && (
          <div style={gridStyles.footer}>{data.footer}</div>
        )}
      </div>
    );
  },
};

const gridStyles = {
  frame: {
    width: 800, height: 1140,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "44px 36px 28px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  headerWrap: {
    textAlign: "center",
    marginBottom: 28,
  },
  headline: {
    fontSize: 40,
    fontWeight: 900,
    letterSpacing: -0.5,
    lineHeight: 1.1,
    color: "#0f172a",
    margin: "0 0 10px",
  },
  subtitle: {
    fontSize: 16,
    color: "#64748b",
    lineHeight: 1.4,
    marginBottom: 6,
  },
  byline: {
    fontSize: 13,
    color: "#94a3b8",
    fontWeight: 500,
  },
  grid: {
    flex: 1,
    display: "grid",
    gridTemplateColumns: "1fr 1fr",
    gap: 16,
  },
  card: {
    background: "#f8fafc",
    borderRadius: "0 0 12px 12px",
    padding: "20px 18px 18px",
    display: "flex",
    flexDirection: "column",
    gap: 10,
  },
  iconCircle: {
    width: 52, height: 52,
    borderRadius: 12,
    display: "grid",
    placeItems: "center",
    flexShrink: 0,
  },
  cardLabel: {
    fontSize: 14,
    fontWeight: 800,
    letterSpacing: 1,
    textTransform: "uppercase",
    lineHeight: 1.2,
  },
  cardBody: {
    fontSize: 14,
    color: "#334155",
    lineHeight: 1.6,
    flex: 1,
  },
  footer: {
    marginTop: 18,
    textAlign: "center",
    fontSize: 15,
    fontWeight: 600,
    color: "#475569",
    fontStyle: "italic",
  },
};

/* ========================================================================
   8. BIG LIST  (1228 × 1536 — tall portrait)
   ======================================================================== */
const bigListTemplate = {
  id: "big-list",
  name: "Big numbered list",
  description: "Full-bleed dark header + 8 bold numbered rows. Clean, high-impact, easy to scan.",
  width: 1080,
  height: 1350,
  schema: `{
  "kicker": "THE DEFINITIVE GUIDE",          // short ALL-CAPS label above title
  "title": "8 Rules Every X Must Follow",   // punchy headline, 5-9 words
  "accent": "blue",                          // color for numbers — one of LAYER_COLORS
  "items": [
    {
      "number": "01",
      "headline": "Bold three-to-six word statement",
      "detail": "One supporting sentence with a specific example or reason."
    }
    // produce EXACTLY 8 items
  ],
  "source": "by Author · @handle"           // optional footer credit, can be ""
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#0f172a"/>
        <rect x="6" y="6" width="68" height="16" rx="3" fill="#1e293b"/>
        <text x="40" y="13" textAnchor="middle" fontSize="5" fontWeight="700" fill="#94a3b8" letterSpacing="1">THE LIST</text>
        <text x="40" y="19" textAnchor="middle" fontSize="6.5" fontWeight="900" fill="#f8fafc">8 RULES</text>
        {[0,1,2,3,4,5,6,7].map(i => (
          <g key={i} transform={`translate(6, ${28 + i * 9})`}>
            <rect width="68" height="7.5" rx="2" fill="#1e293b"/>
            <text x="5" y="5.5" fontSize="5.5" fontWeight="900" fill="#3b82f6">{String(i+1).padStart(2,"0")}</text>
            <rect x="14" y="2" width="30" height="3" rx="1" fill="#f8fafc" opacity="0.6"/>
            <rect x="14" y="5" width="22" height="2" rx="1" fill="#94a3b8" opacity="0.5"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const items = (data.items || []).slice(0, 8);
    return (
      <div style={bigListStyles.frame}>
        <div style={bigListStyles.header}>
          <div style={bigListStyles.kicker}>{data.kicker}</div>
          <h1 style={bigListStyles.title}>{data.title}</h1>
        </div>
        <div style={bigListStyles.list}>
          {items.map((item, i) => (
            <div key={i} style={{...bigListStyles.row, borderBottom: i < items.length - 1 ? "1px solid #1e293b" : "none"}}>
              <div style={{...bigListStyles.num, color: ac}}>{item.number}</div>
              <div style={bigListStyles.content}>
                <div style={bigListStyles.rowHeadline}>{item.headline}</div>
                {item.detail && <div style={bigListStyles.rowDetail}>{item.detail}</div>}
              </div>
            </div>
          ))}
        </div>
        {data.source && (
          <div style={{...bigListStyles.source, borderTop: `2px solid ${ac}44`}}>{data.source}</div>
        )}
      </div>
    );
  },
};

const bigListStyles = {
  frame: {
    width: 1080, height: 1350,
    background: "#0f172a",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    overflow: "hidden",
  },
  header: {
    padding: "48px 60px 32px",
  },
  kicker: {
    fontSize: 12,
    fontWeight: 700,
    letterSpacing: 3.5,
    textTransform: "uppercase",
    color: "#64748b",
    marginBottom: 12,
  },
  title: {
    fontSize: 52,
    fontWeight: 900,
    lineHeight: 1.08,
    color: "#f8fafc",
    margin: 0,
    letterSpacing: -1,
  },
  list: {
    flex: 1,
    display: "flex",
    flexDirection: "column",
    padding: "8px 40px 0",
  },
  row: {
    display: "flex",
    alignItems: "flex-start",
    gap: 20,
    padding: "16px 16px",
    flex: 1,
  },
  num: {
    fontSize: 28,
    fontWeight: 900,
    letterSpacing: -1,
    lineHeight: 1,
    flexShrink: 0,
    minWidth: 48,
    paddingTop: 2,
  },
  content: {
    flex: 1,
    minWidth: 0,
  },
  rowHeadline: {
    fontSize: 20,
    fontWeight: 800,
    color: "#f1f5f9",
    lineHeight: 1.25,
    marginBottom: 4,
  },
  rowDetail: {
    fontSize: 14,
    color: "#94a3b8",
    lineHeight: 1.5,
  },
  source: {
    padding: "14px 60px",
    fontSize: 13,
    color: "#475569",
    fontWeight: 500,
  },
};

/* ========================================================================
   9. WIDE BANNER  (landscape ~1.5–1.8 ratio, e.g. 800×434)
   ======================================================================== */
const wideBannerTemplate = {
  id: "wide-banner",
  name: "Wide banner",
  description: "Horizontal landscape strip. Bold headline + 5 icon feature columns in a row. Best for wide feed banners.",
  width: 1280,
  height: 720,
  schema: `{
  "eyebrow": "OVERVIEW",                     // short ALL-CAPS label
  "headline": "Punchy 4-6 word headline",
  "subtitle": "One supporting sentence.",
  "accent": "blue",                          // one of LAYER_COLORS
  "features": [
    {
      "icon": "cpu",
      "label": "FEATURE",                    // 1-2 words ALL CAPS
      "detail": "Short 6-10 word description"
    }
    // produce EXACTLY 5 features
  ],
  "cta": "Closing one-liner or call to action"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 100 56" xmlns="http://www.w3.org/2000/svg">
        <rect width="100" height="56" fill="#0f172a"/>
        <text x="50" y="12" textAnchor="middle" fontSize="5" fontWeight="800" fill="#f8fafc">WIDE BANNER</text>
        {[0,1,2,3,4].map(i => (
          <g key={i} transform={`translate(${6 + i*18}, 18)`}>
            <circle cx="7" cy="7" r="6" fill="#3b82f6" opacity="0.3"/>
            <rect x="2" y="16" width="12" height="2" rx="1" fill="#94a3b8"/>
            <rect x="1" y="20" width="14" height="2" rx="1" fill="#64748b" opacity="0.6"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const features = (data.features || []).slice(0, 5);
    return (
      <div style={bannerStyles.frame}>
        <div style={{...bannerStyles.header, borderBottom: `4px solid ${ac}`}}>
          <div style={{...bannerStyles.eyebrow, color: ac}}>{data.eyebrow}</div>
          <h1 style={bannerStyles.headline}>{data.headline}</h1>
          {data.subtitle && <div style={bannerStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={bannerStyles.features}>
          {features.map((f, i) => (
            <div key={i} style={bannerStyles.featureCol}>
              <div style={{...bannerStyles.iconCircle, background: ac + "20", color: ac}}>
                <i className={"ti ti-" + (f.icon || "star")} style={{fontSize: 36}}></i>
              </div>
              <div style={{...bannerStyles.featureLabel, color: ac}}>{f.label}</div>
              <div style={bannerStyles.featureDetail}>{f.detail}</div>
            </div>
          ))}
        </div>
        {data.cta && <div style={{...bannerStyles.cta, color: ac}}>{data.cta}</div>}
      </div>
    );
  },
};

const bannerStyles = {
  frame: {
    width: 1280, height: 720,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    padding: "40px 48px 32px",
  },
  header: { textAlign: "center", paddingBottom: 28, marginBottom: 8 },
  eyebrow: { fontSize: 12, fontWeight: 700, letterSpacing: 3, textTransform: "uppercase", marginBottom: 8 },
  headline: { fontSize: 48, fontWeight: 900, color: "#0f172a", margin: "0 0 8px", lineHeight: 1.1, letterSpacing: -0.5 },
  subtitle: { fontSize: 18, color: "#64748b", lineHeight: 1.4 },
  features: { flex: 1, display: "flex", gap: 20, alignItems: "flex-start" },
  featureCol: { flex: 1, textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center", gap: 10 },
  iconCircle: { width: 72, height: 72, borderRadius: "50%", display: "grid", placeItems: "center" },
  featureLabel: { fontSize: 13, fontWeight: 800, letterSpacing: 1.2, textTransform: "uppercase" },
  featureDetail: { fontSize: 13, color: "#475569", lineHeight: 1.45 },
  cta: { textAlign: "center", fontSize: 15, fontWeight: 700, marginTop: 16, fontStyle: "italic" },
};

/* ========================================================================
   10. HORIZONTAL TIMELINE  (landscape ~1.1–1.4 ratio)
   ======================================================================== */
const horizontalTimelineTemplate = {
  id: "horizontal-timeline",
  name: "Horizontal timeline",
  description: "Left-to-right timeline with 6 milestones connected by a line. Great for evolution, history, or roadmaps.",
  width: 1080,
  height: 920,
  schema: `{
  "title": "Evolution of X",                 // 3-6 words
  "subtitle": "One framing sentence",
  "accent": "blue",
  "milestones": [
    {
      "year": "2020",
      "label": "Milestone title",            // 3-5 words
      "detail": "One sentence about this milestone.",
      "icon": "rocket",
      "color": "purple"                      // each milestone a DIFFERENT color
    }
    // produce EXACTLY 6 milestones in chronological order
  ],
  "footer": "Takeaway one-liner"
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444"];
    return (
      <svg viewBox="0 0 80 68" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="68" fill="#f8fafc"/>
        <text x="40" y="10" textAnchor="middle" fontSize="5.5" fontWeight="800" fill="#0f172a">TIMELINE</text>
        <line x1="6" y1="38" x2="74" y2="38" stroke="#cbd5e1" strokeWidth="1.5"/>
        {cols.map((c, i) => (
          <g key={i} transform={`translate(${8 + i*11}, 0)`}>
            <circle cx="0" cy="38" r="4" fill={c}/>
            <rect x="-4" y="18" width="10" height="14" rx="2" fill={c} opacity="0.15"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const milestones = (data.milestones || []).slice(0, 6);
    return (
      <div style={hTimelineStyles.frame}>
        <div style={hTimelineStyles.header}>
          <h1 style={hTimelineStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={hTimelineStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={hTimelineStyles.trackWrap}>
          <div style={{...hTimelineStyles.track, background: ac + "33"}}></div>
          <div style={hTimelineStyles.milestones}>
            {milestones.map((m, i) => {
              const c = colorFor(m.color, ac);
              const above = i % 2 === 0;
              return (
                <div key={i} style={hTimelineStyles.milestoneCol}>
                  <div style={{...hTimelineStyles.card, order: above ? 0 : 2, borderTop: `4px solid ${c}`}}>
                    <div style={{...hTimelineStyles.year, color: c}}>{m.year}</div>
                    <div style={hTimelineStyles.mLabel}>{m.label}</div>
                    <div style={hTimelineStyles.mDetail}>{m.detail}</div>
                  </div>
                  <div style={{...hTimelineStyles.dot, background: c, order: 1}}>
                    <i className={"ti ti-" + (m.icon || "star")} style={{fontSize: 18, color: "#fff"}}></i>
                  </div>
                  <div style={{flex: 1, order: above ? 2 : 0}}></div>
                </div>
              );
            })}
          </div>
        </div>
        {data.footer && <div style={{...hTimelineStyles.footer, borderTop: `2px solid ${ac}44`}}>{data.footer}</div>}
      </div>
    );
  },
};

const hTimelineStyles = {
  frame: {
    width: 1080, height: 920,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "44px 40px 32px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  header: { textAlign: "center", marginBottom: 24 },
  title: { fontSize: 44, fontWeight: 900, color: "#0f172a", margin: "0 0 8px", letterSpacing: -0.5 },
  subtitle: { fontSize: 17, color: "#64748b" },
  trackWrap: { flex: 1, position: "relative", display: "flex", flexDirection: "column", justifyContent: "center" },
  track: { position: "absolute", left: 40, right: 40, top: "50%", height: 4, borderRadius: 2, transform: "translateY(-50%)" },
  milestones: { display: "flex", gap: 8, alignItems: "stretch", height: 520, position: "relative", zIndex: 1 },
  milestoneCol: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 8 },
  card: {
    background: "#f8fafc",
    borderRadius: "12px 12px 0 0",
    padding: "14px 10px",
    textAlign: "center",
    width: "100%",
    boxSizing: "border-box",
  },
  year: { fontSize: 18, fontWeight: 900, letterSpacing: -0.5, marginBottom: 4 },
  mLabel: { fontSize: 13, fontWeight: 800, color: "#0f172a", lineHeight: 1.2, marginBottom: 4 },
  mDetail: { fontSize: 11, color: "#64748b", lineHeight: 1.4 },
  dot: {
    width: 40, height: 40, borderRadius: "50%",
    display: "grid", placeItems: "center",
    flexShrink: 0,
    boxShadow: "0 0 0 4px #ffffff",
  },
  footer: { textAlign: "center", fontSize: 15, fontWeight: 600, color: "#475569", fontStyle: "italic", paddingTop: 16, marginTop: 8 },
};

/* ========================================================================
   11. SQUARE SPOTLIGHT  (800 × 800)
   ======================================================================== */
const squareSpotlightTemplate = {
  id: "square-spotlight",
  name: "Square spotlight",
  description: "Square canvas with a bold center title and 4 corner spotlight cards. Perfect for 1:1 feed posts.",
  width: 800,
  height: 800,
  schema: `{
  "headline": "4 PILLARS OF X",              // ALL-CAPS, 3-5 words — shown in center
  "subtitle": "One framing sentence",
  "accent": "blue",
  "corners": [
    {
      "position": "top-left",                // top-left | top-right | bottom-left | bottom-right
      "icon": "brain",
      "label": "PILLAR NAME",                // 1-3 words ALL CAPS
      "body": "Two concise sentences.",
      "color": "purple"                      // each corner a DIFFERENT color
    }
    // produce EXACTLY 4 corners, one per position
  ],
  "centerStat": "One bold stat or quote in the center below the headline"
}`,

  Thumbnail() {
    const pos = [[8,8],[52,8],[8,52],[52,52]];
    const cols = ["#8b5cf6","#3b82f6","#22c55e","#f59e0b"];
    return (
      <svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="80" fill="#0f172a"/>
        {pos.map(([x,y], i) => (
          <rect key={i} x={x} y={y} width="20" height="20" rx="3" fill={cols[i]} opacity="0.25"/>
        ))}
        <text x="40" y="42" textAnchor="middle" fontSize="6" fontWeight="900" fill="#f8fafc">4×4</text>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const corners = (data.corners || []).slice(0, 4);
    const posMap = {
      "top-left": { top: 24, left: 24 },
      "top-right": { top: 24, right: 24 },
      "bottom-left": { bottom: 24, left: 24 },
      "bottom-right": { bottom: 24, right: 24 },
    };
    return (
      <div style={squareStyles.frame}>
        <div style={squareStyles.centerBlock}>
          <h1 style={{...squareStyles.headline, color: ac}}>{data.headline}</h1>
          {data.subtitle && <div style={squareStyles.subtitle}>{data.subtitle}</div>}
          {data.centerStat && <div style={{...squareStyles.centerStat, borderColor: ac + "55"}}>{data.centerStat}</div>}
        </div>
        {corners.map((c, i) => {
          const pos = posMap[c.position] || posMap[["top-left","top-right","bottom-left","bottom-right"][i]];
          const col = colorFor(c.color, ac);
          return (
            <div key={i} style={{...squareStyles.corner, ...pos, borderTop: `4px solid ${col}`}}>
              <div style={{...squareStyles.cornerIcon, background: col + "20", color: col}}>
                <i className={"ti ti-" + (c.icon || "star")} style={{fontSize: 22}}></i>
              </div>
              <div style={{...squareStyles.cornerLabel, color: col}}>{c.label}</div>
              <div style={squareStyles.cornerBody}>{c.body}</div>
            </div>
          );
        })}
      </div>
    );
  },
};

const squareStyles = {
  frame: {
    width: 800, height: 800,
    background: "#0f172a",
    fontFamily: "'Inter', sans-serif",
    position: "relative",
    boxSizing: "border-box",
    overflow: "hidden",
  },
  centerBlock: {
    position: "absolute",
    top: "50%", left: "50%",
    transform: "translate(-50%, -50%)",
    textAlign: "center",
    width: 320,
    zIndex: 2,
  },
  headline: { fontSize: 32, fontWeight: 900, letterSpacing: 1, lineHeight: 1.15, margin: "0 0 8px" },
  subtitle: { fontSize: 14, color: "#94a3b8", lineHeight: 1.4, marginBottom: 12 },
  centerStat: {
    fontSize: 15, fontWeight: 700, color: "#e2e8f0",
    fontStyle: "italic", padding: "10px 16px",
    borderTop: "1px solid", borderBottom: "1px solid",
  },
  corner: {
    position: "absolute",
    width: 220, height: 220,
    background: "#1e293b",
    borderRadius: 12,
    padding: "16px 14px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    gap: 8,
  },
  cornerIcon: { width: 40, height: 40, borderRadius: 8, display: "grid", placeItems: "center" },
  cornerLabel: { fontSize: 12, fontWeight: 800, letterSpacing: 1, textTransform: "uppercase" },
  cornerBody: { fontSize: 12, color: "#94a3b8", lineHeight: 1.5, flex: 1 },
};

/* ========================================================================
   12. CHEAT SHEET  (800 × 1200 / 1024 × 1536 — very tall)
   ======================================================================== */
const cheatSheetTemplate = {
  id: "cheat-sheet",
  name: "Cheat sheet",
  description: "Dense reference layout with 4 sections and 4 bullet rows each. Monospace feel, high information density.",
  width: 800,
  height: 1200,
  schema: `{
  "title": "X CHEAT SHEET",                  // ALL-CAPS headline
  "subtitle": "Quick reference for Y",
  "accent": "cyan",
  "sections": [
    {
      "heading": "SECTION NAME",             // 1-3 words ALL CAPS
      "icon": "terminal",
      "color": "cyan",
      "rows": [
        { "key": "Term or command", "value": "Short definition or usage (8-15 words)" }
        // produce EXACTLY 4 rows per section
      ]
    }
    // produce EXACTLY 4 sections
  ],
  "footer": "Pro tip one-liner"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 90" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="90" fill="#0f172a"/>
        <text x="30" y="10" textAnchor="middle" fontSize="5" fontWeight="800" fill="#06b6d4">CHEAT SHEET</text>
        {[0,1,2,3].map(i => (
          <g key={i} transform={`translate(4, ${16 + i*18})`}>
            <rect width="52" height="15" rx="2" fill="#1e293b"/>
            <rect x="3" y="3" width="20" height="2.5" rx="1" fill="#06b6d4" opacity="0.7"/>
            <rect x="3" y="7" width="46" height="1.5" rx="0.5" fill="#64748b" opacity="0.5"/>
            <rect x="3" y="10" width="40" height="1.5" rx="0.5" fill="#64748b" opacity="0.4"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#06b6d4");
    const sections = (data.sections || []).slice(0, 4);
    return (
      <div style={cheatStyles.frame}>
        <div style={{...cheatStyles.header, borderBottom: `3px solid ${ac}`}}>
          <h1 style={{...cheatStyles.title, color: ac}}>{data.title}</h1>
          {data.subtitle && <div style={cheatStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={cheatStyles.sections}>
          {sections.map((sec, i) => {
            const c = colorFor(sec.color, ac);
            return (
              <div key={i} style={cheatStyles.section}>
                <div style={cheatStyles.secHead}>
                  <i className={"ti ti-" + (sec.icon || "terminal")} style={{fontSize: 18, color: c}}></i>
                  <span style={{...cheatStyles.secHeading, color: c}}>{sec.heading}</span>
                </div>
                {(sec.rows || []).slice(0, 4).map((row, j) => (
                  <div key={j} style={cheatStyles.row}>
                    <span style={{...cheatStyles.key, color: c}}>{row.key}</span>
                    <span style={cheatStyles.value}>{row.value}</span>
                  </div>
                ))}
              </div>
            );
          })}
        </div>
        {data.footer && <div style={{...cheatStyles.footer, color: ac}}>{data.footer}</div>}
      </div>
    );
  },
};

const cheatStyles = {
  frame: {
    width: 800, height: 1200,
    background: "#0f172a",
    fontFamily: "'Inter', sans-serif",
    padding: "36px 32px 28px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    color: "#e2e8f0",
  },
  header: { paddingBottom: 20, marginBottom: 16 },
  title: { fontSize: 36, fontWeight: 900, letterSpacing: 2, margin: "0 0 6px", fontFamily: "'Inter', monospace" },
  subtitle: { fontSize: 14, color: "#64748b" },
  sections: { flex: 1, display: "flex", flexDirection: "column", gap: 20 },
  section: { background: "#1e293b", borderRadius: 10, padding: "14px 16px" },
  secHead: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10 },
  secHeading: { fontSize: 13, fontWeight: 800, letterSpacing: 1.5, textTransform: "uppercase" },
  row: { display: "flex", gap: 12, padding: "6px 0", borderTop: "1px solid #334155", alignItems: "flex-start" },
  key: { fontSize: 13, fontWeight: 700, minWidth: 140, flexShrink: 0, fontFamily: "monospace" },
  value: { fontSize: 13, color: "#94a3b8", lineHeight: 1.45, flex: 1 },
  footer: { marginTop: 16, textAlign: "center", fontSize: 13, fontWeight: 600, fontStyle: "italic" },
};

/* ========================================================================
   13. MYTH VS FACT  (portrait-mid ~800×880)
   ======================================================================== */
const mythFactTemplate = {
  id: "myth-fact",
  name: "Myth vs fact",
  description: "6 alternating myth/fact rows. Red myths crossed out, green facts confirmed. Great for debunking misconceptions.",
  width: 800,
  height: 880,
  schema: `{
  "title": "5 Myths About X",                // catchy headline
  "subtitle": "What people get wrong — and what's actually true",
  "pairs": [
    {
      "myth": "Common misconception stated clearly (8-14 words)",
      "fact": "The corrected truth (8-14 words)"
    }
    // produce EXACTLY 6 pairs
  ],
  "footer": "Save this for later one-liner"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 66" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="66" fill="#ffffff"/>
        <text x="30" y="9" textAnchor="middle" fontSize="5" fontWeight="800" fill="#0f172a">MYTH vs FACT</text>
        {[0,1,2,3,4,5].map(i => (
          <g key={i} transform={`translate(4, ${14 + i*8.5})`}>
            <rect width="52" height="7" rx="2" fill={i%2===0 ? "#fef2f2" : "#f0fdf4"}/>
            <text x="4" y="5" fontSize="4" fill={i%2===0 ? "#ef4444" : "#22c55e"} fontWeight="700">{i%2===0 ? "✗" : "✓"}</text>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const pairs = (data.pairs || []).slice(0, 6);
    return (
      <div style={mythStyles.frame}>
        <div style={mythStyles.header}>
          <h1 style={mythStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={mythStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={mythStyles.pairs}>
          {pairs.map((p, i) => (
            <div key={i} style={mythStyles.pairBlock}>
              <div style={mythStyles.mythRow}>
                <span style={mythStyles.mythBadge}>MYTH</span>
                <span style={mythStyles.mythText}>{p.myth}</span>
              </div>
              <div style={mythStyles.factRow}>
                <span style={mythStyles.factBadge}>FACT</span>
                <span style={mythStyles.factText}>{p.fact}</span>
              </div>
            </div>
          ))}
        </div>
        {data.footer && <div style={mythStyles.footer}>{data.footer}</div>}
      </div>
    );
  },
};

const mythStyles = {
  frame: {
    width: 800, height: 880,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "36px 36px 24px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  header: { textAlign: "center", marginBottom: 24 },
  title: { fontSize: 36, fontWeight: 900, color: "#0f172a", margin: "0 0 8px", lineHeight: 1.1 },
  subtitle: { fontSize: 15, color: "#64748b", lineHeight: 1.4 },
  pairs: { flex: 1, display: "flex", flexDirection: "column", gap: 10 },
  pairBlock: { borderRadius: 10, overflow: "hidden", border: "1px solid #e2e8f0" },
  mythRow: { display: "flex", gap: 12, alignItems: "flex-start", padding: "10px 14px", background: "#fef2f2" },
  mythBadge: { fontSize: 10, fontWeight: 800, letterSpacing: 1, color: "#ffffff", background: "#ef4444", padding: "3px 7px", borderRadius: 4, flexShrink: 0 },
  mythText: { fontSize: 14, color: "#991b1b", lineHeight: 1.4, textDecoration: "line-through", opacity: 0.85 },
  factRow: { display: "flex", gap: 12, alignItems: "flex-start", padding: "10px 14px", background: "#f0fdf4" },
  factBadge: { fontSize: 10, fontWeight: 800, letterSpacing: 1, color: "#ffffff", background: "#22c55e", padding: "3px 7px", borderRadius: 4, flexShrink: 0 },
  factText: { fontSize: 14, color: "#166534", lineHeight: 1.4, fontWeight: 500 },
  footer: { marginTop: 16, textAlign: "center", fontSize: 14, fontWeight: 600, color: "#475569", fontStyle: "italic" },
};

/* ========================================================================
   14. STAT DASHBOARD  (1024 × 1536 — tall data layout)
   ======================================================================== */
const statDashboardTemplate = {
  id: "stat-dashboard",
  name: "Stat dashboard",
  description: "Large headline + 6 bold stat blocks in a 2×3 grid. Each stat has a number, label, and one-line context.",
  width: 1024,
  height: 1536,
  schema: `{
  "eyebrow": "BY THE NUMBERS",
  "title": "X in 2025",                      // 3-5 words
  "subtitle": "Key metrics that matter",
  "accent": "blue",
  "stats": [
    {
      "value": "73%",                        // bold number or stat
      "label": "STAT LABEL",                 // 2-4 words ALL CAPS
      "context": "One sentence explaining why this stat matters.",
      "icon": "chart-bar",
      "color": "blue"                        // each stat a DIFFERENT color
    }
    // produce EXACTLY 6 stats
  ],
  "insight": "One bold takeaway sentence"
}`,

  Thumbnail() {
    const cols = ["#3b82f6","#8b5cf6","#06b6d4","#22c55e","#f59e0b","#ef4444"];
    return (
      <svg viewBox="0 0 60 90" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="90" fill="#f8fafc"/>
        <text x="30" y="10" textAnchor="middle" fontSize="5" fontWeight="800" fill="#0f172a">STATS</text>
        {[0,1,2].map(row => [0,1].map(col => {
          const i = row*2+col;
          return (
            <g key={i} transform={`translate(${4+col*28}, ${16+row*24})`}>
              <rect width="26" height="20" rx="3" fill={cols[i]} opacity="0.12"/>
              <text x="13" y="10" textAnchor="middle" fontSize="7" fontWeight="900" fill={cols[i]}>##</text>
            </g>
          );
        }))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const stats = (data.stats || []).slice(0, 6);
    return (
      <div style={statStyles.frame}>
        <div style={statStyles.header}>
          <div style={{...statStyles.eyebrow, color: ac}}>{data.eyebrow}</div>
          <h1 style={statStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={statStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={statStyles.grid}>
          {stats.map((s, i) => {
            const c = colorFor(s.color, ac);
            return (
              <div key={i} style={{...statStyles.statCard, borderTop: `5px solid ${c}`}}>
                <div style={{...statStyles.statIcon, background: c + "18", color: c}}>
                  <i className={"ti ti-" + (s.icon || "chart-bar")} style={{fontSize: 28}}></i>
                </div>
                <div style={{...statStyles.statValue, color: c}}>{s.value}</div>
                <div style={statStyles.statLabel}>{s.label}</div>
                <div style={statStyles.statContext}>{s.context}</div>
              </div>
            );
          })}
        </div>
        {data.insight && (
          <div style={{...statStyles.insight, borderLeft: `5px solid ${ac}`}}>{data.insight}</div>
        )}
      </div>
    );
  },
};

const statStyles = {
  frame: {
    width: 1024, height: 1536,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "56px 48px 40px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  header: { textAlign: "center", marginBottom: 40 },
  eyebrow: { fontSize: 13, fontWeight: 700, letterSpacing: 3, textTransform: "uppercase", marginBottom: 12 },
  title: { fontSize: 56, fontWeight: 900, color: "#0f172a", margin: "0 0 10px", letterSpacing: -1, lineHeight: 1.05 },
  subtitle: { fontSize: 20, color: "#64748b" },
  grid: { flex: 1, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24 },
  statCard: {
    background: "#f8fafc",
    borderRadius: "0 0 16px 16px",
    padding: "28px 24px",
    display: "flex",
    flexDirection: "column",
    gap: 8,
  },
  statIcon: { width: 52, height: 52, borderRadius: 12, display: "grid", placeItems: "center", marginBottom: 4 },
  statValue: { fontSize: 52, fontWeight: 900, letterSpacing: -2, lineHeight: 1 },
  statLabel: { fontSize: 14, fontWeight: 800, letterSpacing: 1.2, textTransform: "uppercase", color: "#0f172a" },
  statContext: { fontSize: 14, color: "#64748b", lineHeight: 1.5, flex: 1 },
  insight: {
    marginTop: 32,
    padding: "20px 28px",
    background: "#f8fafc",
    fontSize: 20,
    fontWeight: 700,
    color: "#1e293b",
    fontStyle: "italic",
    lineHeight: 1.4,
  },
};

/* ========================================================================
   15. STORY SLIDES  (animated tall GIF — 1080×1350, many frames)
   ======================================================================== */
const storySlidesTemplate = withAnimation({
  id: "story-slides",
  name: "Story slides",
  category: "animated",
  description: "8 sequential slides — one per GIF frame. Exports as animated GIF.",
  width: 1080,
  height: 1350,
  exportBackground: "#ffffff",
  schema: `{
  "seriesTitle": "Understanding X",          // overall topic
  "accent": "blue",
  "slides": [
    {
      "slideNumber": "01",
      "headline": "Slide headline (4-7 words)",
      "body": "2-3 sentences of content for this slide beat.",
      "icon": "bulb",
      "color": "purple"                      // each slide a DIFFERENT color
    }
    // produce EXACTLY 8 slides — each is one animation frame beat
  ],
  "cta": "Follow for more breakdowns"
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444","#ec4899","#6366f1"];
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#f8fafc"/>
        {cols.map((c, i) => (
          <rect key={i} x="4" y={4 + i*8.5} width="52" height="7" rx="1.5" fill={c} opacity="0.2"/>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const slides = (data.slides || []).slice(0, 8);
    return (
      <div style={storyStyles.frame}>
        <div style={{...storyStyles.seriesBar, background: ac}}>
          <span style={storyStyles.seriesTitle}>{data.seriesTitle}</span>
        </div>
        <div style={storyStyles.slides}>
          {slides.map((sl, i) => {
            const c = colorFor(sl.color, ac);
            return (
              <div key={i} style={{...storyStyles.slide, borderLeft: `6px solid ${c}`}}>
                <div style={storyStyles.slideTop}>
                  <span style={{...storyStyles.slideNum, color: c}}>{sl.slideNumber}</span>
                  <i className={"ti ti-" + (sl.icon || "star")} style={{fontSize: 22, color: c}}></i>
                </div>
                <div style={storyStyles.slideHeadline}>{sl.headline}</div>
                <div style={storyStyles.slideBody}>{sl.body}</div>
              </div>
            );
          })}
        </div>
        {data.cta && <div style={{...storyStyles.cta, color: ac}}>{data.cta}</div>}
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const n = (data.slides || []).slice(0, 8).length || 1;
    if (opts.maxFrames && opts.maxFrames >= n) return opts.maxFrames;
    return n;
  },

  RenderFrame({ data, frameIndex }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const slides = (data.slides || []).slice(0, 8);
    const slide = slides[frameIndex];
    if (!slide) return null;
    const c = colorFor(slide.color, ac);
    return (
      <div style={{...storyStyles.frame, justifyContent: "center"}}>
        <div style={{...storyStyles.seriesBar, background: ac}}>
          <span style={storyStyles.seriesTitle}>{data.seriesTitle}</span>
        </div>
        <div style={{ flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", padding: "48px 64px" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }}>
            <span style={{ fontSize: 48, fontWeight: 900, color: c, letterSpacing: -1 }}>{slide.slideNumber}</span>
            <div style={{ width: 80, height: 80, borderRadius: 16, background: c + "18", color: c, display: "grid", placeItems: "center" }}>
              <i className={"ti ti-" + (slide.icon || "star")} style={{ fontSize: 40 }}></i>
            </div>
          </div>
          <h2 style={{ fontSize: 52, fontWeight: 900, color: "#0f172a", lineHeight: 1.1, margin: "0 0 24px", letterSpacing: -0.5 }}>{slide.headline}</h2>
          <p style={{ fontSize: 24, color: "#475569", lineHeight: 1.55, margin: 0 }}>{slide.body}</p>
        </div>
        {data.cta && <div style={{...storyStyles.cta, color: ac}}>{data.cta}</div>}
      </div>
    );
  },
}, { gifDelay: 1200 });

const storyStyles = {
  frame: {
    width: 1080, height: 1350,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    overflow: "hidden",
  },
  seriesBar: { padding: "16px 40px", flexShrink: 0 },
  seriesTitle: { fontSize: 16, fontWeight: 800, color: "#ffffff", letterSpacing: 1, textTransform: "uppercase" },
  slides: { flex: 1, display: "flex", flexDirection: "column", gap: 0, overflow: "hidden" },
  slide: {
    flex: 1,
    padding: "14px 36px 12px",
    background: "#f8fafc",
    borderBottom: "1px solid #e2e8f0",
    display: "flex",
    flexDirection: "column",
    gap: 4,
    minHeight: 0,
  },
  slideTop: { display: "flex", justifyContent: "space-between", alignItems: "center" },
  slideNum: { fontSize: 20, fontWeight: 900, letterSpacing: -0.5 },
  slideHeadline: { fontSize: 17, fontWeight: 800, color: "#0f172a", lineHeight: 1.2 },
  slideBody: { fontSize: 13, color: "#64748b", lineHeight: 1.45, flex: 1, overflow: "hidden" },
  cta: { padding: "14px 40px", textAlign: "center", fontSize: 14, fontWeight: 700, flexShrink: 0 },
};

/* ========================================================================
   16. SCROLL STEPS  (animated portrait GIF — 800×1100+)
   ======================================================================== */
const scrollStepsTemplate = withAnimation({
  id: "scroll-steps",
  name: "Scroll steps",
  category: "animated",
  description: "7 scroll panels — one per GIF frame. Exports as animated GIF.",
  width: 800,
  height: 1120,
  exportBackground: "#ffffff",
  schema: `{
  "title": "How to Master X",                // overall topic
  "accent": "indigo",
  "steps": [
    {
      "number": "01",
      "headline": "Step headline (4-6 words)",
      "body": "One or two sentences for this scroll panel.",
      "highlight": "Key phrase in bold (3-5 words)",
      "color": "indigo"                      // each step a DIFFERENT color
    }
    // produce EXACTLY 7 steps
  ]
}`,

  Thumbnail() {
    const cols = ["#6366f1","#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444"];
    return (
      <svg viewBox="0 0 60 84" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="84" fill="#ffffff"/>
        {cols.map((c, i) => (
          <rect key={i} x="3" y={3 + i*11.5} width="54" height="10" rx="2" fill={c} opacity="0.15"/>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#6366f1");
    const steps = (data.steps || []).slice(0, 7);
    return (
      <div style={scrollStyles.frame}>
        <div style={{...scrollStyles.titleBar, background: `linear-gradient(135deg, ${ac}, ${ac}99)`}}>
          <h1 style={scrollStyles.title}>{data.title}</h1>
        </div>
        {steps.map((step, i) => {
          const c = colorFor(step.color, ac);
          return (
            <div key={i} style={{...scrollStyles.panel, background: i % 2 === 0 ? "#ffffff" : "#f8fafc", borderLeft: `6px solid ${c}`}}>
              <div style={{...scrollStyles.panelNum, color: c}}>{step.number}</div>
              <div style={scrollStyles.panelContent}>
                <div style={scrollStyles.panelHeadline}>{step.headline}</div>
                <div style={scrollStyles.panelBody}>{step.body}</div>
                {step.highlight && <div style={{...scrollStyles.highlight, color: c}}>{step.highlight}</div>}
              </div>
            </div>
          );
        })}
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const n = (data.steps || []).slice(0, 7).length || 1;
    if (opts.maxFrames && opts.maxFrames >= n) return opts.maxFrames;
    return n;
  },

  RenderFrame({ data, frameIndex }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#6366f1");
    const steps = (data.steps || []).slice(0, 7);
    const step = steps[frameIndex];
    if (!step) return null;
    const c = colorFor(step.color, ac);
    return (
      <div style={{...scrollStyles.frame, justifyContent: "stretch"}}>
        <div style={{...scrollStyles.titleBar, background: `linear-gradient(135deg, ${ac}, ${ac}99)`}}>
          <h1 style={scrollStyles.title}>{data.title}</h1>
        </div>
        <div style={{ flex: 1, display: "flex", alignItems: "center", padding: "40px 48px", borderLeft: `8px solid ${c}` }}>
          <div style={{...scrollStyles.panelNum, color: c, fontSize: 72, minWidth: 100}}>{step.number}</div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 36, fontWeight: 900, color: "#0f172a", lineHeight: 1.15, marginBottom: 16 }}>{step.headline}</div>
            <div style={{ fontSize: 22, color: "#64748b", lineHeight: 1.5, marginBottom: 12 }}>{step.body}</div>
            {step.highlight && <div style={{ fontSize: 20, fontWeight: 700, color: c }}>{step.highlight}</div>}
          </div>
        </div>
      </div>
    );
  },
}, { gifDelay: 1000 });

const scrollStyles = {
  frame: {
    width: 800, height: 1120,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    overflow: "hidden",
  },
  titleBar: { padding: "28px 32px", flexShrink: 0 },
  title: { fontSize: 32, fontWeight: 900, color: "#ffffff", margin: 0, lineHeight: 1.15, letterSpacing: -0.5 },
  panel: {
    flex: 1,
    display: "flex",
    gap: 16,
    padding: "12px 24px",
    alignItems: "center",
    borderBottom: "1px solid #e2e8f0",
    minHeight: 0,
  },
  panelNum: { fontSize: 28, fontWeight: 900, letterSpacing: -1, flexShrink: 0, minWidth: 44 },
  panelContent: { flex: 1, minWidth: 0 },
  panelHeadline: { fontSize: 16, fontWeight: 800, color: "#0f172a", lineHeight: 1.2, marginBottom: 4 },
  panelBody: { fontSize: 13, color: "#64748b", lineHeight: 1.45 },
  highlight: { fontSize: 13, fontWeight: 700, marginTop: 4 },
};

/* ========================================================================
   17. FRAME SEQUENCE  (animated mid GIF — 800×1000)
   ======================================================================== */
const frameSequenceTemplate = withAnimation({
  id: "frame-sequence",
  name: "Frame sequence",
  category: "animated",
  description: "6 frames — one per GIF beat. Exports as animated GIF.",
  width: 800,
  height: 1000,
  exportBackground: "#0f172a",
  schema: `{
  "headline": "X Explained in 6 Frames",
  "subtitle": "Watch it unfold step by step",
  "accent": "blue",
  "frames": [
    {
      "frameNumber": "01",
      "title": "Frame title (3-5 words)",
      "caption": "One sentence describing this animation frame.",
      "icon": "play",
      "color": "blue"                        // each frame a DIFFERENT color
    }
    // produce EXACTLY 6 frames
  ],
  "footer": "Save & share one-liner"
}`,

  Thumbnail() {
    const cols = ["#3b82f6","#8b5cf6","#06b6d4","#22c55e","#f59e0b","#ef4444"];
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#0f172a"/>
        {[0,1,2].map(row => [0,1].map(col => {
          const i = row*2+col;
          return (
            <rect key={i} x={3+col*28} y={12+row*20} width="26" height="17" rx="2" fill={cols[i]} opacity="0.3"/>
          );
        }))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const frames = (data.frames || []).slice(0, 6);
    return (
      <div style={frameSeqStyles.frame}>
        <div style={frameSeqStyles.header}>
          <h1 style={frameSeqStyles.headline}>{data.headline}</h1>
          {data.subtitle && <div style={frameSeqStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={frameSeqStyles.grid}>
          {frames.map((fr, i) => {
            const c = colorFor(fr.color, ac);
            return (
              <div key={i} style={{...frameSeqStyles.frameCard, borderTop: `4px solid ${c}`}}>
                <div style={frameSeqStyles.frameTop}>
                  <span style={{...frameSeqStyles.frameNum, color: c}}>{fr.frameNumber}</span>
                  <i className={"ti ti-" + (fr.icon || "play")} style={{fontSize: 20, color: c}}></i>
                </div>
                <div style={{...frameSeqStyles.frameTitle, color: c}}>{fr.title}</div>
                <div style={frameSeqStyles.frameCaption}>{fr.caption}</div>
              </div>
            );
          })}
        </div>
        {data.footer && <div style={{...frameSeqStyles.footer, color: ac}}>{data.footer}</div>}
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const n = (data.frames || []).slice(0, 6).length || 1;
    if (opts.maxFrames && opts.maxFrames >= n) return opts.maxFrames;
    return n;
  },

  RenderFrame({ data, frameIndex }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const frames = (data.frames || []).slice(0, 6);
    const fr = frames[frameIndex];
    if (!fr) return null;
    const c = colorFor(fr.color, ac);
    return (
      <div style={{...frameSeqStyles.frame, justifyContent: "center", padding: "48px"}}>
        <div style={{ textAlign: "center", marginBottom: 32 }}>
          <div style={{ fontSize: 14, color: "#64748b", marginBottom: 8 }}>{data.headline}</div>
          <div style={{ fontSize: 12, color: "#475569" }}>Frame {fr.frameNumber}</div>
        </div>
        <div style={{ background: "#1e293b", borderRadius: 16, borderTop: `6px solid ${c}`, padding: "48px 40px", textAlign: "center" }}>
          <div style={{ width: 80, height: 80, borderRadius: "50%", background: c, color: "#fff", display: "grid", placeItems: "center", margin: "0 auto 24px" }}>
            <i className={"ti ti-" + (fr.icon || "play")} style={{ fontSize: 36 }}></i>
          </div>
          <div style={{ fontSize: 36, fontWeight: 900, color: c, marginBottom: 16, lineHeight: 1.15 }}>{fr.title}</div>
          <div style={{ fontSize: 20, color: "#94a3b8", lineHeight: 1.5 }}>{fr.caption}</div>
        </div>
        {data.footer && <div style={{...frameSeqStyles.footer, color: ac, marginTop: 32}}>{data.footer}</div>}
      </div>
    );
  },
}, { gifDelay: 900 });

const frameSeqStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#0f172a",
    fontFamily: "'Inter', sans-serif",
    padding: "36px 32px 24px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  header: { textAlign: "center", marginBottom: 24 },
  headline: { fontSize: 32, fontWeight: 900, color: "#f8fafc", margin: "0 0 8px", lineHeight: 1.15 },
  subtitle: { fontSize: 14, color: "#64748b" },
  grid: { flex: 1, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 },
  frameCard: {
    background: "#1e293b",
    borderRadius: "0 0 12px 12px",
    padding: "16px 14px",
    display: "flex",
    flexDirection: "column",
    gap: 6,
  },
  frameTop: { display: "flex", justifyContent: "space-between", alignItems: "center" },
  frameNum: { fontSize: 22, fontWeight: 900, letterSpacing: -0.5 },
  frameTitle: { fontSize: 15, fontWeight: 800, lineHeight: 1.2 },
  frameCaption: { fontSize: 12, color: "#94a3b8", lineHeight: 1.45, flex: 1 },
  footer: { marginTop: 16, textAlign: "center", fontSize: 13, fontWeight: 700, fontStyle: "italic" },
};

/* ========================================================================
   18. FLIP QUADRANT  (animated square GIF — 800×800)
   ======================================================================== */
const flipQuadrantTemplate = withAnimation({
  id: "flip-quadrant",
  name: "Flip quadrant",
  category: "animated",
  description: "4 quadrants revealed one-by-one — exports as square animated GIF.",
  width: 800,
  height: 800,
  exportBackground: "#0f172a",
  schema: `{
  "centerTitle": "4 SIDES OF X",             // ALL-CAPS, shown in center overlay
  "accent": "violet",
  "quadrants": [
    {
      "position": "top-left",                // top-left | top-right | bottom-left | bottom-right
      "icon": "brain",
      "label": "QUADRANT NAME",              // 1-3 words ALL CAPS
      "body": "Two sentences for this quadrant panel.",
      "color": "violet"                      // each quadrant a DIFFERENT color
    }
    // produce EXACTLY 4 quadrants, one per position
  ]
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#22c55e","#f59e0b"];
    const pos = [[0,0],[30,0],[0,30],[30,30]];
    return (
      <svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="60" fill="#0f172a"/>
        {pos.map(([x,y], i) => (
          <rect key={i} x={x+1} y={y+1} width="28" height="28" fill={cols[i]} opacity="0.35"/>
        ))}
        <circle cx="30" cy="30" r="8" fill="#0f172a" stroke="#f8fafc" strokeWidth="1"/>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#8b5cf6");
    const quads = (data.quadrants || []).slice(0, 4);
    const posStyles = {
      "top-left":     { top: 0, left: 0 },
      "top-right":    { top: 0, right: 0 },
      "bottom-left":  { bottom: 0, left: 0 },
      "bottom-right": { bottom: 0, right: 0 },
    };
    return (
      <div style={flipStyles.frame}>
        {quads.map((q, i) => {
          const c = colorFor(q.color, ac);
          const pos = posStyles[q.position] || posStyles[["top-left","top-right","bottom-left","bottom-right"][i]];
          return (
            <div key={i} style={{...flipStyles.quadrant, ...pos, background: c + "22", borderColor: c}}>
              <div style={{...flipStyles.qIcon, background: c, color: "#fff"}}>
                <i className={"ti ti-" + (q.icon || "star")} style={{fontSize: 32}}></i>
              </div>
              <div style={{...flipStyles.qLabel, color: c}}>{q.label}</div>
              <div style={flipStyles.qBody}>{q.body}</div>
            </div>
          );
        })}
        <div style={flipStyles.centerOverlay}>
          <div style={{...flipStyles.centerTitle, color: ac}}>{data.centerTitle}</div>
        </div>
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const n = ((data.quadrants || []).slice(0, 4).length) + 1;
    if (opts.maxFrames && opts.maxFrames >= n) return opts.maxFrames;
    return n;
  },

  RenderFrame({ data, frameIndex }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#8b5cf6");
    const quads = (data.quadrants || []).slice(0, 4);
    if (frameIndex === 0) {
      return (
        <div style={{...flipStyles.frame, display: "grid", placeItems: "center"}}>
          <div style={{ textAlign: "center", padding: 48 }}>
            <div style={{ fontSize: 14, color: "#64748b", letterSpacing: 3, textTransform: "uppercase", marginBottom: 16 }}>4-part breakdown</div>
            <h1 style={{ fontSize: 48, fontWeight: 900, color: ac, letterSpacing: 1, lineHeight: 1.1, margin: 0 }}>{data.centerTitle}</h1>
          </div>
        </div>
      );
    }
    const q = quads[frameIndex - 1];
    if (!q) return null;
    const c = colorFor(q.color, ac);
    return (
      <div style={{...flipStyles.frame, display: "flex", flexDirection: "column", justifyContent: "center", padding: 56, background: c + "18"}}>
        <div style={{...flipStyles.qIcon, background: c, color: "#fff", width: 88, height: 88, borderRadius: 20, marginBottom: 28}}>
          <i className={"ti ti-" + (q.icon || "star")} style={{fontSize: 44}}></i>
        </div>
        <div style={{...flipStyles.qLabel, color: c, fontSize: 28, marginBottom: 16}}>{q.label}</div>
        <div style={{...flipStyles.qBody, fontSize: 22, color: "#e2e8f0", lineHeight: 1.55}}>{q.body}</div>
      </div>
    );
  },
}, { gifDelay: 1100 });

const flipStyles = {
  frame: {
    width: 800, height: 800,
    background: "#0f172a",
    fontFamily: "'Inter', sans-serif",
    position: "relative",
    boxSizing: "border-box",
    overflow: "hidden",
  },
  quadrant: {
    position: "absolute",
    width: "50%", height: "50%",
    boxSizing: "border-box",
    padding: "28px 24px",
    display: "flex",
    flexDirection: "column",
    gap: 10,
    border: "2px solid",
  },
  qIcon: { width: 56, height: 56, borderRadius: 12, display: "grid", placeItems: "center", flexShrink: 0 },
  qLabel: { fontSize: 16, fontWeight: 900, letterSpacing: 1.2, textTransform: "uppercase", lineHeight: 1.2 },
  qBody: { fontSize: 13, color: "#cbd5e1", lineHeight: 1.55, flex: 1 },
  centerOverlay: {
    position: "absolute",
    top: "50%", left: "50%",
    transform: "translate(-50%, -50%)",
    width: 120, height: 120,
    borderRadius: "50%",
    background: "#0f172a",
    border: "3px solid #334155",
    display: "grid",
    placeItems: "center",
    zIndex: 2,
    padding: 12,
    boxSizing: "border-box",
  },
  centerTitle: { fontSize: 11, fontWeight: 900, letterSpacing: 1, textTransform: "uppercase", textAlign: "center", lineHeight: 1.3 },
};

/* ========================================================================
   19. SIDE-BY-SIDE COMPARISON  (split columns — common LinkedIn style)
   ======================================================================== */
const splitComparisonTemplate = {
  id: "split-comparison",
  name: "Side-by-side comparison",
  category: "comparison",
  description: "Two tall columns with icon bullets. Clear A vs B split — ideal for tools, models, or approaches.",
  width: 800,
  height: 1000,
  schema: `{
  "title": "X vs Y",                         // punchy headline, 3-6 words
  "subtitle": "One sentence framing the comparison",
  "leftLabel": "OPTION A",                   // 1-3 words ALL CAPS
  "rightLabel": "OPTION B",
  "leftColor": "blue",
  "rightColor": "orange",
  "leftPoints": [
    { "icon": "check", "text": "Benefit or trait (8-14 words)" }
    // produce EXACTLY 6 points for the left column
  ],
  "rightPoints": [
    { "icon": "check", "text": "Benefit or trait (8-14 words)" }
    // produce EXACTLY 6 points for the right column
  ],
  "verdict": "One-line takeaway on when to pick each option"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#fff"/>
        <text x="40" y="11" textAnchor="middle" fontSize="5.5" fontWeight="800" fill="#0f172a">A vs B</text>
        <rect x="4" y="16" width="34" height="78" rx="4" fill="#3b82f6" opacity="0.12"/>
        <rect x="42" y="16" width="34" height="78" rx="4" fill="#f97316" opacity="0.12"/>
        <text x="21" y="24" textAnchor="middle" fontSize="4.5" fontWeight="700" fill="#3b82f6">A</text>
        <text x="59" y="24" textAnchor="middle" fontSize="4.5" fontWeight="700" fill="#f97316">B</text>
        {[0,1,2,3,4].map(i => (
          <g key={i}>
            <circle cx="10" cy={32 + i*10} r="2" fill="#3b82f6" opacity="0.5"/>
            <circle cx="48" cy={32 + i*10} r="2" fill="#f97316" opacity="0.5"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const lc = colorFor(data.leftColor, "#3b82f6");
    const rc = colorFor(data.rightColor, "#f97316");
    const left = (data.leftPoints || []).slice(0, 6);
    const right = (data.rightPoints || []).slice(0, 6);
    return (
      <div style={splitCmpStyles.frame}>
        <div style={splitCmpStyles.header}>
          <h1 style={splitCmpStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={splitCmpStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={splitCmpStyles.columns}>
          <div style={{...splitCmpStyles.col, borderColor: lc}}>
            <div style={{...splitCmpStyles.colHead, background: lc}}>{data.leftLabel}</div>
            <div style={splitCmpStyles.colBody}>
              {left.map((p, i) => (
                <div key={i} style={splitCmpStyles.point}>
                  <i className={"ti ti-" + (p.icon || "check")} style={{...splitCmpStyles.pointIcon, color: lc}}></i>
                  <span style={splitCmpStyles.pointText}>{p.text}</span>
                </div>
              ))}
            </div>
          </div>
          <div style={splitCmpStyles.vsCol}>
            <div style={splitCmpStyles.vsCircle}>VS</div>
          </div>
          <div style={{...splitCmpStyles.col, borderColor: rc}}>
            <div style={{...splitCmpStyles.colHead, background: rc}}>{data.rightLabel}</div>
            <div style={splitCmpStyles.colBody}>
              {right.map((p, i) => (
                <div key={i} style={splitCmpStyles.point}>
                  <i className={"ti ti-" + (p.icon || "check")} style={{...splitCmpStyles.pointIcon, color: rc}}></i>
                  <span style={splitCmpStyles.pointText}>{p.text}</span>
                </div>
              ))}
            </div>
          </div>
        </div>
        {data.verdict && <div style={splitCmpStyles.verdict}>{data.verdict}</div>}
      </div>
    );
  },
};

const splitCmpStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "40px 32px 32px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  header: { textAlign: "center", marginBottom: 24 },
  title: { fontSize: 38, fontWeight: 900, color: "#0f172a", margin: "0 0 8px", lineHeight: 1.1, letterSpacing: -0.5 },
  subtitle: { fontSize: 15, color: "#64748b", lineHeight: 1.4 },
  columns: { flex: 1, display: "flex", gap: 0, alignItems: "stretch", minHeight: 0 },
  col: {
    flex: 1,
    border: "2px solid",
    borderRadius: 12,
    overflow: "hidden",
    display: "flex",
    flexDirection: "column",
  },
  colHead: {
    color: "#fff",
    fontWeight: 800,
    fontSize: 16,
    letterSpacing: 1.2,
    textTransform: "uppercase",
    textAlign: "center",
    padding: "14px 8px",
  },
  colBody: { flex: 1, padding: "14px 12px", display: "flex", flexDirection: "column", gap: 10 },
  point: { display: "flex", gap: 10, alignItems: "flex-start" },
  pointIcon: { fontSize: 18, flexShrink: 0, marginTop: 1 },
  pointText: { fontSize: 13, color: "#334155", lineHeight: 1.45, flex: 1 },
  vsCol: { width: 44, flexShrink: 0, display: "grid", placeItems: "center" },
  vsCircle: {
    width: 36, height: 36, borderRadius: "50%",
    background: "#0f172a", color: "#fff",
    fontWeight: 900, fontSize: 11, letterSpacing: 0.5,
    display: "grid", placeItems: "center",
  },
  verdict: {
    marginTop: 18,
    textAlign: "center",
    fontSize: 14,
    fontWeight: 600,
    color: "#475569",
    fontStyle: "italic",
    lineHeight: 1.4,
  },
};

/* ========================================================================
   20. ANIMATED COMPARISON GIF  (rows reveal frame-by-frame)
   ======================================================================== */
const comparisonRevealBase = {
  id: "comparison-reveal",
  name: "Animated comparison",
  category: "comparison",
  description: "Comparison table that reveals row-by-row — exports as an animated GIF.",
  width: 800,
  height: 1000,
  exportBackground: "#ffffff",
  schema: `{
  "eyebrow": "THE COMPARISON",
  "leftLabel": "OPTION A",
  "rightLabel": "OPTION B",
  "leftColor": "blue",
  "rightColor": "orange",
  "rows": [
    {
      "aspect": "Dimension label",
      "left": "Left answer (10-15 words max)",
      "right": "Right answer (10-15 words max)"
    }
    // produce EXACTLY 6 rows — each becomes one GIF frame
  ],
  "tagline": "Closing takeaway line"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect width="80" height="100" fill="#f8faff"/>
        <text x="40" y="10" textAnchor="middle" fontSize="5" fontWeight="800" fill="#1a1a17">GIF</text>
        <text x="40" y="18" textAnchor="middle" fontSize="4.5" fontWeight="700" fill="#64748b">COMPARE</text>
        <rect x="4" y="22" width="34" height="8" rx="2" fill="#3b82f6" opacity="0.3"/>
        <rect x="42" y="22" width="34" height="8" rx="2" fill="#f97316" opacity="0.3"/>
        {[0,1,2,3,4].map(i => (
          <rect key={i} x="4" y={34 + i*12} width="72" height="9" rx="2" fill={i <= 2 ? "#cbd5e1" : "#f1f5f9"} opacity={i <= 2 ? 0.9 : 0.4}/>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    return comparisonRevealBase.RenderFrame({ data, frameIndex: (data.rows || []).length });
  },

  getFrameCount(data, opts = {}) {
    const n = ((data.rows || []).slice(0, 6).length) + 1;
    if (opts.maxFrames && opts.maxFrames >= n) return opts.maxFrames;
    return n;
  },

  RenderFrame({ data, frameIndex }) {
    if (!data) return null;
    const lc = colorFor(data.leftColor, "#3b82f6");
    const rc = colorFor(data.rightColor, "#f97316");
    const allRows = (data.rows || []).slice(0, 6);
    const visibleRows = frameIndex <= 0 ? [] : allRows.slice(0, frameIndex);
    const showTagline = frameIndex >= allRows.length && data.tagline;
    return (
      <div style={vsStyles.frame}>
        <div style={vsStyles.eyebrow}>{data.eyebrow}</div>
        <div style={vsStyles.header}>
          <div style={{...vsStyles.colHeader, background: lc}}>{data.leftLabel}</div>
          <div style={vsStyles.vsBadge}>VS</div>
          <div style={{...vsStyles.colHeader, background: rc}}>{data.rightLabel}</div>
        </div>
        <div style={{...vsStyles.table, flex: 1}}>
          {visibleRows.length === 0 ? (
            <div style={{ flex: 1, display: "grid", placeItems: "center", color: "#94a3b8", fontSize: 18, fontWeight: 600 }}>
              {frameIndex === 0 ? "Comparing…" : ""}
            </div>
          ) : (
            visibleRows.map((row, i) => (
              <div key={i} style={{...vsStyles.row, background: i % 2 === 0 ? "#f8fafc" : "#ffffff", flex: 1}}>
                <div style={{...vsStyles.cell, borderRight: `3px solid ${lc}22`}}>
                  <span style={vsStyles.cellText}>{row.left}</span>
                </div>
                <div style={vsStyles.aspectCell}>
                  <span style={vsStyles.aspectText}>{row.aspect}</span>
                </div>
                <div style={{...vsStyles.cell, borderLeft: `3px solid ${rc}22`}}>
                  <span style={vsStyles.cellText}>{row.right}</span>
                </div>
              </div>
            ))
          )}
        </div>
        {showTagline && <div style={vsStyles.tagline}>{data.tagline}</div>}
      </div>
    );
  },
};

const comparisonRevealTemplate = withAnimation(comparisonRevealBase, { gifDelay: 1000 });

/* ========================================================================
   21. SCROLL REEL  (long vertical scroll GIF — matches 1080×1350 / 150+ frames)
   ======================================================================== */
const scrollReelTemplate = withAnimation({
  id: "scroll-reel",
  name: "Scroll reel",
  category: "animated",
  description: "Smooth vertical scroll through panels — matches long linkedInresources GIF posts (90–280 frames).",
  width: 1080,
  height: 1350,
  exportBackground: "#ffffff",
  layoutFrames: true,
  schema: `{
  "title": "Complete Guide to X",            // 4-8 words
  "subtitle": "Scroll through the full breakdown",
  "accent": "blue",
  "panels": [
    {
      "headline": "Section headline (4-7 words)",
      "body": "2-3 sentences for this scroll section. Dense and specific.",
      "icon": "bulb",
      "color": "purple"                      // each panel a DIFFERENT color
    }
    // produce EXACTLY 10 panels — the GIF scrolls through all of them
  ],
  "footer": "Save this reel one-liner"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#f8fafc"/>
        <clipPath id="scr"><rect x="4" y="4" width="52" height="67" rx="3"/></clipPath>
        <g clipPath="url(#scr)">
          {[0,1,2,3,4,5].map(i => (
            <rect key={i} x="6" y={8 + i*14 - 10} width="48" height="12" rx="2" fill="#3b82f6" opacity={0.15 + i * 0.05}/>
          ))}
        </g>
        <text x="30" y="72" textAnchor="middle" fontSize="4" fill="#64748b">SCROLL</text>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const panels = (data.panels || []).slice(0, 10);
    const headerH = 200;
    const panelH = 420;
    const footerH = data.footer ? 80 : 0;
    const totalH = headerH + panels.length * panelH + footerH;
    const scrollY = Math.max(0, totalH - 1350);
    return (
      <div style={{ width: 1080, height: 1350, overflow: "hidden", background: "#fff", fontFamily: "'Inter', sans-serif", position: "relative" }}>
        <div style={{ transform: `translateY(${-scrollY}px)` }}>
          <div style={{ padding: "48px 56px 32px", background: `linear-gradient(135deg, ${ac}, ${ac}cc)`, color: "#fff" }}>
            <h1 style={{ fontSize: 52, fontWeight: 900, margin: "0 0 10px", lineHeight: 1.08 }}>{data.title}</h1>
            {data.subtitle && <p style={{ fontSize: 20, margin: 0, opacity: 0.9 }}>{data.subtitle}</p>}
          </div>
          {panels.map((p, i) => {
            const c = colorFor(p.color, ac);
            return (
              <div key={i} style={{ minHeight: panelH, padding: "36px 56px", borderBottom: "1px solid #e2e8f0", borderLeft: `8px solid ${c}`, boxSizing: "border-box" }}>
                <div style={{ display: "flex", gap: 20, alignItems: "flex-start" }}>
                  <div style={{ width: 64, height: 64, borderRadius: 14, background: c + "18", color: c, display: "grid", placeItems: "center", flexShrink: 0 }}>
                    <i className={"ti ti-" + (p.icon || "star")} style={{ fontSize: 32 }}></i>
                  </div>
                  <div>
                    <h2 style={{ fontSize: 32, fontWeight: 800, color: "#0f172a", margin: "0 0 12px", lineHeight: 1.15 }}>{p.headline}</h2>
                    <p style={{ fontSize: 20, color: "#475569", margin: 0, lineHeight: 1.55 }}>{p.body}</p>
                  </div>
                </div>
              </div>
            );
          })}
          {data.footer && (
            <div style={{ padding: "28px 56px", textAlign: "center", fontSize: 18, fontWeight: 700, color: ac, fontStyle: "italic" }}>{data.footer}</div>
          )}
        </div>
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const panels = (data.panels || []).slice(0, 10);
    const minFrames = Math.max(30, panels.length * 8);
    if (opts.maxFrames && opts.maxFrames >= minFrames) return opts.maxFrames;
    return Math.max(minFrames, 90);
  },

  RenderFrame({ data, frameIndex, frameCount }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const panels = (data.panels || []).slice(0, 10);
    const headerH = 200;
    const panelH = 420;
    const footerH = data.footer ? 80 : 0;
    const totalH = headerH + panels.length * panelH + footerH;
    const maxScroll = Math.max(0, totalH - 1350);
    const total = frameCount || 90;
    const t = total <= 1 ? 1 : frameIndex / (total - 1);
    const scrollY = t * maxScroll;

    return (
      <div style={{ width: 1080, height: 1350, overflow: "hidden", background: "#fff", fontFamily: "'Inter', sans-serif", position: "relative" }}>
        <div style={{ transform: `translateY(${-scrollY}px)`, willChange: "transform" }}>
          <div style={{ padding: "48px 56px 32px", background: `linear-gradient(135deg, ${ac}, ${ac}cc)`, color: "#fff" }}>
            <h1 style={{ fontSize: 52, fontWeight: 900, margin: "0 0 10px", lineHeight: 1.08 }}>{data.title}</h1>
            {data.subtitle && <p style={{ fontSize: 20, margin: 0, opacity: 0.9 }}>{data.subtitle}</p>}
          </div>
          {panels.map((p, i) => {
            const c = colorFor(p.color, ac);
            return (
              <div key={i} style={{ minHeight: panelH, padding: "36px 56px", borderBottom: "1px solid #e2e8f0", borderLeft: `8px solid ${c}`, boxSizing: "border-box" }}>
                <div style={{ display: "flex", gap: 20, alignItems: "flex-start" }}>
                  <div style={{ width: 64, height: 64, borderRadius: 14, background: c + "18", color: c, display: "grid", placeItems: "center", flexShrink: 0 }}>
                    <i className={"ti ti-" + (p.icon || "star")} style={{ fontSize: 32 }}></i>
                  </div>
                  <div>
                    <h2 style={{ fontSize: 32, fontWeight: 800, color: "#0f172a", margin: "0 0 12px", lineHeight: 1.15 }}>{p.headline}</h2>
                    <p style={{ fontSize: 20, color: "#475569", margin: 0, lineHeight: 1.55 }}>{p.body}</p>
                  </div>
                </div>
              </div>
            );
          })}
          {data.footer && (
            <div style={{ padding: "28px 56px", textAlign: "center", fontSize: 18, fontWeight: 700, color: ac, fontStyle: "italic" }}>{data.footer}</div>
          )}
        </div>
      </div>
    );
  },
}, { gifDelay: 80 });

/* ========================================================================
   22. TYPEWRITER LIST  (text reveal GIF — common 800×1000 animation)
   ======================================================================== */
const typewriterListTemplate = withAnimation({
  id: "typewriter-list",
  name: "Typewriter list",
  category: "animated",
  description: "Title types in, then bullets appear one-by-one — classic educational GIF style.",
  width: 800,
  height: 1000,
  exportBackground: "#0f172a",
  layoutFrames: true,
  schema: `{
  "title": "5 Things You Must Know About X",   // will type in character-by-character
  "accent": "cyan",
  "bullets": [
    { "text": "First key insight (10-18 words)", "icon": "check" }
    // produce EXACTLY 6 bullets
  ],
  "footer": "Follow for more"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#0f172a"/>
        <text x="8" y="18" fontSize="6" fontWeight="800" fill="#06b6d4">5 Things|</text>
        {[0,1,2,3].map(i => (
          <rect key={i} x="8" y={24 + i*10} width={38 + i * 4} height="4" rx="1" fill="#94a3b8" opacity={0.3 + i * 0.15}/>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#06b6d4");
    const bullets = (data.bullets || []).slice(0, 6);
    return (
      <div style={{ width: 800, height: 1000, background: "#0f172a", fontFamily: "'Inter', sans-serif", padding: "56px 48px", boxSizing: "border-box", display: "flex", flexDirection: "column" }}>
        <h1 style={{ fontSize: 40, fontWeight: 900, color: "#f8fafc", lineHeight: 1.15, margin: "0 0 40px" }}>{data.title}</h1>
        <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 16 }}>
          {bullets.map((b, i) => (
            <div key={i} style={{ display: "flex", gap: 14, alignItems: "flex-start" }}>
              <i className={"ti ti-" + (b.icon || "check")} style={{ fontSize: 22, color: ac, marginTop: 2 }}></i>
              <span style={{ fontSize: 18, color: "#cbd5e1", lineHeight: 1.5 }}>{b.text}</span>
            </div>
          ))}
        </div>
        {data.footer && <div style={{ marginTop: 24, textAlign: "center", fontSize: 15, fontWeight: 700, color: ac }}>{data.footer}</div>}
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const bullets = (data.bullets || []).slice(0, 6);
    const titleLen = (data.title || "").length;
    const logical = titleLen + bullets.length * 8 + 10;
    if (opts.maxFrames && opts.maxFrames > logical) return opts.maxFrames;
    return Math.max(logical, 40);
  },

  RenderFrame({ data, frameIndex, frameCount }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#06b6d4");
    const title = data.title || "";
    const bullets = (data.bullets || []).slice(0, 6);
    const total = frameCount || 40;
    const titleEnd = Math.floor(title.length * Math.min(1, (frameIndex + 1) / (total * 0.35)));
    const typedTitle = title.slice(0, titleEnd);
    const bulletStart = Math.floor(total * 0.35);
    const bulletZone = total - bulletStart;
    const visibleBullets = frameIndex < bulletStart ? 0 : Math.min(bullets.length, Math.ceil(((frameIndex - bulletStart) / bulletZone) * bullets.length));

    return (
      <div style={{ width: 800, height: 1000, background: "#0f172a", fontFamily: "'Inter', sans-serif", padding: "56px 48px", boxSizing: "border-box", display: "flex", flexDirection: "column" }}>
        <h1 style={{ fontSize: 40, fontWeight: 900, color: "#f8fafc", lineHeight: 1.15, margin: "0 0 40px", minHeight: 96 }}>
          {typedTitle}
          <span style={{ color: ac, opacity: frameIndex % 2 === 0 ? 1 : 0 }}>|</span>
        </h1>
        <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 16 }}>
          {bullets.slice(0, visibleBullets).map((b, i) => (
            <div key={i} style={{ display: "flex", gap: 14, alignItems: "flex-start", opacity: i < visibleBullets - 1 ? 1 : 0.85 }}>
              <i className={"ti ti-" + (b.icon || "check")} style={{ fontSize: 22, color: ac, marginTop: 2 }}></i>
              <span style={{ fontSize: 18, color: "#cbd5e1", lineHeight: 1.5 }}>{b.text}</span>
            </div>
          ))}
        </div>
        {data.footer && visibleBullets >= bullets.length && (
          <div style={{ marginTop: 24, textAlign: "center", fontSize: 15, fontWeight: 700, color: ac }}>{data.footer}</div>
        )}
      </div>
    );
  },
}, { gifDelay: 100 });

/* ========================================================================
   23. SPOTLIGHT CYCLE  (rotating highlight GIF — square / portrait)
   ======================================================================== */
const spotlightCycleTemplate = withAnimation({
  id: "spotlight-cycle",
  name: "Spotlight cycle",
  category: "animated",
  description: "Cycles through items one at a time with a spotlight highlight — matches square GIF posts.",
  width: 800,
  height: 800,
  exportBackground: "#0f172a",
  layoutFrames: true,
  schema: `{
  "title": "6 MUST-KNOW FACTS",              // ALL-CAPS short headline
  "accent": "violet",
  "items": [
    {
      "label": "FACT LABEL",                 // 1-3 words ALL CAPS
      "body": "One or two sentences explaining this fact.",
      "icon": "star",
      "color": "violet"                      // each item a DIFFERENT color
    }
    // produce EXACTLY 6 items
  ]
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444"];
    return (
      <svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="60" fill="#0f172a"/>
        {cols.map((c, i) => {
          const a = i === 2 ? 0.9 : 0.2;
          return <circle key={i} cx={10 + (i % 3) * 20} cy={18 + Math.floor(i/3) * 20} r="6" fill={c} opacity={a}/>;
        })}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const items = (data.items || []).slice(0, 6);
    const ac = colorFor(data.accent, "#8b5cf6");
    const item = items[0];
    const c = item ? colorFor(item.color, ac) : ac;
    return (
      <div style={{ width: 800, height: 800, background: "#0f172a", fontFamily: "'Inter', sans-serif", padding: 40, boxSizing: "border-box", display: "flex", flexDirection: "column" }}>
        <h1 style={{ fontSize: 28, fontWeight: 900, color: "#94a3b8", letterSpacing: 2, textAlign: "center", margin: "0 0 24px" }}>{data.title}</h1>
        <div style={{ flex: 1, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          {items.map((it, i) => {
            const ic = colorFor(it.color, ac);
            return (
              <div key={i} style={{ background: i === 0 ? ic + "28" : "#1e293b", border: i === 0 ? `2px solid ${ic}` : "2px solid transparent", borderRadius: 12, padding: 14, opacity: i === 0 ? 1 : 0.35 }}>
                <i className={"ti ti-" + (it.icon || "star")} style={{ fontSize: 20, color: ic }}></i>
                <div style={{ fontSize: 11, fontWeight: 800, color: ic, marginTop: 6 }}>{it.label}</div>
              </div>
            );
          })}
        </div>
        {item && (
          <div style={{ marginTop: 20, textAlign: "center", padding: "24px 20px", background: c + "18", borderRadius: 16, border: `2px solid ${c}` }}>
            <div style={{ fontSize: 22, fontWeight: 900, color: c, marginBottom: 10 }}>{item.label}</div>
            <div style={{ fontSize: 17, color: "#e2e8f0", lineHeight: 1.5 }}>{item.body}</div>
          </div>
        )}
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const items = (data.items || []).slice(0, 6);
    const logical = items.length * 12;
    if (opts.maxFrames && opts.maxFrames >= logical) return opts.maxFrames;
    return Math.max(logical, 48);
  },

  RenderFrame({ data, frameIndex, frameCount }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#8b5cf6");
    const items = (data.items || []).slice(0, 6);
    const hold = Math.max(1, Math.floor((frameCount || 48) / items.length));
    const active = items.length ? Math.floor(frameIndex / hold) % items.length : 0;
    const item = items[active];
    const c = item ? colorFor(item.color, ac) : ac;

    return (
      <div style={{ width: 800, height: 800, background: "#0f172a", fontFamily: "'Inter', sans-serif", padding: 40, boxSizing: "border-box", display: "flex", flexDirection: "column" }}>
        <h1 style={{ fontSize: 28, fontWeight: 900, color: "#94a3b8", letterSpacing: 2, textAlign: "center", margin: "0 0 24px" }}>{data.title}</h1>
        <div style={{ flex: 1, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          {items.map((it, i) => {
            const ic = colorFor(it.color, ac);
            const isActive = i === active;
            return (
              <div key={i} style={{
                background: isActive ? ic + "28" : "#1e293b",
                border: isActive ? `2px solid ${ic}` : "2px solid transparent",
                borderRadius: 12,
                padding: 14,
                opacity: isActive ? 1 : 0.35,
                transform: isActive ? "scale(1.02)" : "scale(1)",
                transition: "none",
              }}>
                <i className={"ti ti-" + (it.icon || "star")} style={{ fontSize: 20, color: ic }}></i>
                <div style={{ fontSize: 11, fontWeight: 800, color: ic, marginTop: 6, letterSpacing: 0.8 }}>{it.label}</div>
              </div>
            );
          })}
        </div>
        {item && (
          <div style={{ marginTop: 20, textAlign: "center", padding: "24px 20px", background: c + "18", borderRadius: 16, border: `2px solid ${c}` }}>
            <div style={{ fontSize: 22, fontWeight: 900, color: c, marginBottom: 10 }}>{item.label}</div>
            <div style={{ fontSize: 17, color: "#e2e8f0", lineHeight: 1.5 }}>{item.body}</div>
          </div>
        )}
      </div>
    );
  },
}, { gifDelay: 120 });

/* ========================================================================
   24. COUNTER SPLASH  (count-up stats GIF)
   ======================================================================== */
const counterSplashTemplate = withAnimation({
  id: "counter-splash",
  name: "Counter splash",
  category: "animated",
  description: "Stats count up with labels appearing — animated metrics splash.",
  width: 800,
  height: 1000,
  exportBackground: "#ffffff",
  layoutFrames: true,
  schema: `{
  "title": "X BY THE NUMBERS",
  "subtitle": "Key metrics that matter",
  "accent": "blue",
  "stats": [
    {
      "value": 73,                           // numeric value to count up to
      "suffix": "%",                         // optional suffix like % or x or K
      "label": "STAT LABEL",                 // ALL CAPS 2-4 words
      "context": "One sentence of context."
    }
    // produce EXACTLY 4 stats
  ]
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#fff"/>
        <text x="30" y="22" textAnchor="middle" fontSize="14" fontWeight="900" fill="#3b82f6">73%</text>
        <text x="30" y="38" textAnchor="middle" fontSize="5" fill="#64748b">COUNT UP</text>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const stats = (data.stats || []).slice(0, 4);
    return (
      <div style={{ width: 800, height: 1000, background: "#fff", fontFamily: "'Inter', sans-serif", padding: "48px 40px", boxSizing: "border-box" }}>
        <div style={{ textAlign: "center", marginBottom: 36 }}>
          <h1 style={{ fontSize: 36, fontWeight: 900, color: "#0f172a", margin: "0 0 8px" }}>{data.title}</h1>
          {data.subtitle && <p style={{ fontSize: 16, color: "#64748b", margin: 0 }}>{data.subtitle}</p>}
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
          {stats.map((s, i) => (
            <div key={i} style={{ background: "#f8fafc", borderRadius: 16, padding: 24, borderTop: `5px solid ${ac}` }}>
              <div style={{ fontSize: 48, fontWeight: 900, color: ac }}>{s.value}{s.suffix || ""}</div>
              <div style={{ fontSize: 13, fontWeight: 800, letterSpacing: 1, color: "#0f172a", marginTop: 8 }}>{s.label}</div>
              <div style={{ fontSize: 13, color: "#64748b", marginTop: 8, lineHeight: 1.4 }}>{s.context}</div>
            </div>
          ))}
        </div>
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const logical = 48;
    if (opts.maxFrames && opts.maxFrames >= logical) return opts.maxFrames;
    return logical;
  },

  RenderFrame({ data, frameIndex, frameCount }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const stats = (data.stats || []).slice(0, 4);
    const total = frameCount || 48;
    const phase = total / (stats.length + 1);

    return (
      <div style={{ width: 800, height: 1000, background: "#fff", fontFamily: "'Inter', sans-serif", padding: "48px 40px", boxSizing: "border-box" }}>
        <div style={{ textAlign: "center", marginBottom: 36 }}>
          <h1 style={{ fontSize: 36, fontWeight: 900, color: "#0f172a", margin: "0 0 8px" }}>{data.title}</h1>
          {data.subtitle && <p style={{ fontSize: 16, color: "#64748b", margin: 0 }}>{data.subtitle}</p>}
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
          {stats.map((s, i) => {
            const start = phase * (i + 1);
            const prog = Math.max(0, Math.min(1, (frameIndex - start) / phase));
            const num = Math.round((Number(s.value) || 0) * prog);
            const visible = frameIndex >= start;
            return (
              <div key={i} style={{ background: "#f8fafc", borderRadius: 16, padding: 24, borderTop: `5px solid ${ac}`, opacity: visible ? 1 : 0.25 }}>
                <div style={{ fontSize: 48, fontWeight: 900, color: ac, letterSpacing: -1 }}>{num}{s.suffix || ""}</div>
                <div style={{ fontSize: 13, fontWeight: 800, letterSpacing: 1, color: "#0f172a", marginTop: 8 }}>{s.label}</div>
                {prog >= 1 && <div style={{ fontSize: 13, color: "#64748b", marginTop: 8, lineHeight: 1.4 }}>{s.context}</div>}
              </div>
            );
          })}
        </div>
      </div>
    );
  },
}, { gifDelay: 100 });

/* ========================================================================
   25. LAYER STACK REVEAL  (stacked layers animate in one-by-one)
   ======================================================================== */
const layerStackRevealTemplate = withAnimation({
  id: "layer-stack-reveal",
  name: "Layer stack reveal",
  category: "animated",
  description: "Stacked layers build up one row at a time — matches layered checklist GIF animations.",
  width: 800,
  height: 1000,
  exportBackground: "#ffffff",
  layoutFrames: true,
  schema: `{
  "title": "THE FULL STACK",                 // ALL CAPS headline
  "subtitle": "Every layer explained",
  "accent": "green",
  "layers": [
    {
      "number": "01",
      "label": "LAYER NAME",                 // 1-2 words ALL CAPS
      "summary": "One sentence for this layer.",
      "color": "purple"                      // each layer a DIFFERENT color
    }
    // produce EXACTLY 8 layers
  ],
  "footer": "Closing line"
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444","#ec4899","#6366f1"];
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#fff"/>
        {cols.map((c, i) => (
          <rect key={i} x="6" y={10 + i*7} width={42 + i * 2} height="5" rx="1.5" fill={c} opacity={0.2 + i * 0.08}/>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const layers = (data.layers || []).slice(0, 8);
    const ac = colorFor(data.accent, "#22c55e");
    return (
      <div style={{ width: 800, height: 1000, background: "#fff", fontFamily: "'Inter', sans-serif", padding: "40px 36px", boxSizing: "border-box", display: "flex", flexDirection: "column" }}>
        <h1 style={{ fontSize: 34, fontWeight: 900, textAlign: "center", margin: "0 0 8px" }}>{data.title}</h1>
        {data.subtitle && <p style={{ textAlign: "center", color: "#64748b", margin: "0 0 20px" }}>{data.subtitle}</p>}
        <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 8 }}>
          {layers.map((layer, i) => {
            const c = colorFor(layer.color, ac);
            return (
              <div key={i} style={{ border: `2px solid ${c}`, borderRadius: 10, padding: "12px 16px", display: "flex", gap: 12, alignItems: "center" }}>
                <span style={{ fontSize: 22, fontWeight: 900, color: c }}>{layer.number}</span>
                <div>
                  <div style={{ fontSize: 14, fontWeight: 800, letterSpacing: 0.8 }}>{layer.label}</div>
                  <div style={{ fontSize: 13, color: "#64748b" }}>{layer.summary}</div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const layers = (data.layers || []).slice(0, 8);
    const logical = layers.length + 2;
    if (opts.maxFrames && opts.maxFrames >= logical) return opts.maxFrames;
    return Math.max(logical, 40);
  },

  RenderFrame({ data, frameIndex, frameCount }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#22c55e");
    const layers = (data.layers || []).slice(0, 8);
    const total = frameCount || 40;
    const titleFrames = Math.floor(total * 0.1);
    const visible = frameIndex <= titleFrames ? 0 : Math.min(layers.length, Math.ceil(((frameIndex - titleFrames) / (total - titleFrames)) * layers.length));

    return (
      <div style={{ width: 800, height: 1000, background: "#fff", fontFamily: "'Inter', sans-serif", padding: "40px 36px", boxSizing: "border-box", display: "flex", flexDirection: "column" }}>
        <h1 style={{ fontSize: 34, fontWeight: 900, textAlign: "center", margin: "0 0 8px", opacity: frameIndex > 0 ? 1 : 0.5 }}>{data.title}</h1>
        {data.subtitle && <p style={{ textAlign: "center", color: "#64748b", margin: "0 0 20px" }}>{data.subtitle}</p>}
        <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 8, justifyContent: "flex-end" }}>
          {layers.slice(0, visible).map((layer, i) => {
            const c = colorFor(layer.color, ac);
            return (
              <div key={i} style={{ border: `2px solid ${c}`, borderRadius: 10, padding: "12px 16px", display: "flex", gap: 12, alignItems: "center", background: c + "08" }}>
                <span style={{ fontSize: 22, fontWeight: 900, color: c }}>{layer.number}</span>
                <div>
                  <div style={{ fontSize: 14, fontWeight: 800, letterSpacing: 0.8 }}>{layer.label}</div>
                  <div style={{ fontSize: 13, color: "#64748b" }}>{layer.summary}</div>
                </div>
              </div>
            );
          })}
        </div>
        {data.footer && visible >= layers.length && (
          <div style={{ marginTop: 16, textAlign: "center", fontWeight: 700, color: ac, fontStyle: "italic" }}>{data.footer}</div>
        )}
      </div>
    );
  },
}, { gifDelay: 110 });

/* ========================================================================
   26. QUOTE SPOTLIGHT  (800×1000 — dominant linkedInresources JPG size)
   ======================================================================== */
const quoteSpotlightTemplate = {
  id: "quote-spotlight",
  name: "Quote spotlight",
  description: "Large pull-quote with attribution. Classic thought-leadership card for feed posts.",
  width: 800,
  height: 1000,
  schema: `{
  "eyebrow": "INSIGHT",                      // short ALL-CAPS label
  "quote": "A memorable one-to-two sentence quote that stands alone (20-40 words max).",
  "attribution": "Author Name",
  "role": "Title or context (e.g. AI researcher · 10 years in ML)",
  "accent": "indigo",                        // one of LAYER_COLORS
  "context": "Optional one-line framing below the quote (8-14 words)",
  "footer": "Save · share · follow one-liner"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#f8fafc"/>
        <text x="8" y="28" fontSize="18" fontWeight="900" fill="#6366f1" opacity="0.4">"</text>
        <rect x="10" y="18" width="40" height="3" rx="1.5" fill="#0f172a" opacity="0.15"/>
        <rect x="10" y="24" width="36" height="3" rx="1.5" fill="#0f172a" opacity="0.12"/>
        <rect x="10" y="30" width="32" height="3" rx="1.5" fill="#0f172a" opacity="0.1"/>
        <circle cx="14" cy="52" r="6" fill="#6366f1" opacity="0.35"/>
        <rect x="24" y="48" width="22" height="3" rx="1" fill="#64748b"/>
        <rect x="24" y="53" width="18" height="2" rx="1" fill="#94a3b8" opacity="0.6"/>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#6366f1");
    return (
      <div style={quoteStyles.frame}>
        <div style={{...quoteStyles.topBar, background: ac}} />
        <div style={quoteStyles.body}>
          <div style={{...quoteStyles.eyebrow, color: ac}}>{data.eyebrow}</div>
          <div style={quoteStyles.quoteMark}>"</div>
          <blockquote style={quoteStyles.quote}>{data.quote}</blockquote>
          {data.context && <p style={quoteStyles.context}>{data.context}</p>}
          <div style={quoteStyles.attribution}>
            <div style={{...quoteStyles.avatar, background: ac + "22", color: ac}}>
              <i className="ti ti-user" style={{ fontSize: 28 }}></i>
            </div>
            <div>
              <div style={quoteStyles.author}>{data.attribution}</div>
              {data.role && <div style={quoteStyles.role}>{data.role}</div>}
            </div>
          </div>
        </div>
        {data.footer && <div style={{...quoteStyles.footer, color: ac}}>{data.footer}</div>}
      </div>
    );
  },
};

const quoteStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    overflow: "hidden",
  },
  topBar: { height: 6, flexShrink: 0 },
  body: { flex: 1, padding: "56px 52px 32px", display: "flex", flexDirection: "column" },
  eyebrow: { fontSize: 12, fontWeight: 700, letterSpacing: 3, textTransform: "uppercase", marginBottom: 28 },
  quoteMark: { fontSize: 96, fontWeight: 900, lineHeight: 0.6, color: "#e2e8f0", marginBottom: 8, fontFamily: "Georgia, serif" },
  quote: {
    margin: "0 0 28px",
    fontSize: 34,
    fontWeight: 700,
    color: "#0f172a",
    lineHeight: 1.35,
    letterSpacing: -0.3,
    border: "none",
    padding: 0,
  },
  context: { fontSize: 17, color: "#64748b", lineHeight: 1.5, margin: "0 0 36px", fontStyle: "italic" },
  attribution: { display: "flex", gap: 16, alignItems: "center", marginTop: "auto" },
  avatar: { width: 56, height: 56, borderRadius: "50%", display: "grid", placeItems: "center", flexShrink: 0 },
  author: { fontSize: 20, fontWeight: 800, color: "#0f172a" },
  role: { fontSize: 14, color: "#64748b", marginTop: 4, lineHeight: 1.4 },
  footer: { padding: "20px 52px 28px", textAlign: "center", fontSize: 14, fontWeight: 600, fontStyle: "italic", borderTop: "1px solid #e2e8f0" },
};

/* ========================================================================
   27. PDF DECK SLIDES  (810×1012 — linkedInresources PDF carousel size)
   ======================================================================== */
const pdfDeckSlidesTemplate = withAnimation({
  id: "pdf-deck-slides",
  name: "PDF deck slides",
  category: "animated",
  description: "7 portrait slides at PDF deck proportions (810×1012). Matches multi-page LinkedIn document carousels.",
  width: 810,
  height: 1012,
  exportBackground: "#ffffff",
  schema: `{
  "deckTitle": "Guide to X",                 // overall deck title on title slide
  "accent": "blue",
  "slides": [
    {
      "slideNumber": "01",
      "headline": "Slide headline (4-8 words)",
      "body": "2-3 sentences for this deck page.",
      "icon": "book",
      "color": "blue"                        // each slide a DIFFERENT color
    }
    // produce EXACTLY 7 slides — one per PDF page / GIF frame
  ],
  "cta": "Save this deck · follow for part 2"
}`,

  Thumbnail() {
    const cols = ["#3b82f6","#8b5cf6","#06b6d4","#22c55e","#f59e0b","#ef4444","#ec4899"];
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#fff" stroke="#e2e8f0" strokeWidth="1"/>
        {cols.map((c, i) => (
          <rect key={i} x="8" y={8 + i * 9} width="44" height="6" rx="1.5" fill={c} opacity="0.2"/>
        ))}
        <text x="30" y="72" textAnchor="middle" fontSize="5" fill="#64748b">7 pg</text>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const slides = (data.slides || []).slice(0, 7);
    return (
      <div style={deckStyles.frame}>
        <div style={{...deckStyles.deckBar, background: ac}}>
          <span style={deckStyles.deckTitle}>{data.deckTitle}</span>
        </div>
        <div style={deckStyles.slideList}>
          {slides.map((sl, i) => {
            const c = colorFor(sl.color, ac);
            return (
              <div key={i} style={{...deckStyles.slideRow, borderLeft: `4px solid ${c}`}}>
                <span style={{...deckStyles.slideNum, color: c}}>{sl.slideNumber}</span>
                <span style={deckStyles.slideHead}>{sl.headline}</span>
              </div>
            );
          })}
        </div>
      </div>
    );
  },

  getFrameCount(data, opts = {}) {
    const n = (data.slides || []).slice(0, 7).length || 1;
    if (opts.maxFrames && opts.maxFrames >= n) return opts.maxFrames;
    return n;
  },

  RenderFrame({ data, frameIndex }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#3b82f6");
    const slides = (data.slides || []).slice(0, 7);
    const slide = slides[frameIndex];
    if (!slide) return null;
    const c = colorFor(slide.color, ac);
    return (
      <div style={deckStyles.frame}>
        <div style={{...deckStyles.deckBar, background: ac}}>
          <span style={deckStyles.deckTitle}>{data.deckTitle}</span>
          <span style={deckStyles.pageBadge}>{slide.slideNumber} / {String(slides.length).padStart(2, "0")}</span>
        </div>
        <div style={deckStyles.slideFull}>
          <div style={{...deckStyles.iconBox, background: c + "18", color: c}}>
            <i className={"ti ti-" + (slide.icon || "book")} style={{ fontSize: 44 }}></i>
          </div>
          <h2 style={deckStyles.fullHeadline}>{slide.headline}</h2>
          <p style={deckStyles.fullBody}>{slide.body}</p>
        </div>
        {frameIndex === slides.length - 1 && data.cta && (
          <div style={{...deckStyles.cta, color: ac}}>{data.cta}</div>
        )}
      </div>
    );
  },
}, { gifDelay: 1300 });

const deckStyles = {
  frame: {
    width: 810, height: 1012,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    overflow: "hidden",
  },
  deckBar: {
    padding: "14px 32px",
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    flexShrink: 0,
  },
  deckTitle: { fontSize: 14, fontWeight: 800, color: "#fff", letterSpacing: 1, textTransform: "uppercase" },
  pageBadge: { fontSize: 13, fontWeight: 700, color: "#ffffffcc" },
  slideList: { flex: 1, display: "flex", flexDirection: "column" },
  slideRow: {
    flex: 1,
    display: "flex",
    alignItems: "center",
    gap: 16,
    padding: "0 32px",
    background: "#f8fafc",
    borderBottom: "1px solid #e2e8f0",
  },
  slideNum: { fontSize: 18, fontWeight: 900, minWidth: 36 },
  slideHead: { fontSize: 15, fontWeight: 700, color: "#0f172a" },
  slideFull: {
    flex: 1,
    padding: "48px 40px",
    display: "flex",
    flexDirection: "column",
    justifyContent: "center",
  },
  iconBox: { width: 80, height: 80, borderRadius: 16, display: "grid", placeItems: "center", marginBottom: 28 },
  fullHeadline: { fontSize: 40, fontWeight: 900, color: "#0f172a", lineHeight: 1.12, margin: "0 0 20px", letterSpacing: -0.5 },
  fullBody: { fontSize: 20, color: "#475569", lineHeight: 1.55, margin: 0 },
  cta: { padding: "16px 32px", textAlign: "center", fontSize: 14, fontWeight: 700, flexShrink: 0, borderTop: "1px solid #e2e8f0" },
};

/* ========================================================================
   28. DO'S & DON'TS  (800×1000 — comparison columns)
   ======================================================================== */
const dosDontsTemplate = {
  id: "dos-donts",
  name: "Do's & don'ts",
  category: "comparison",
  description: "Two columns — green do's and red don'ts with 5 paired tips each. Common coaching carousel layout.",
  width: 800,
  height: 1000,
  schema: `{
  "title": "X Do's and Don'ts",              // punchy headline
  "subtitle": "One sentence framing",
  "pairs": [
    {
      "do": "What to do — clear action (8-14 words)",
      "dont": "What to avoid — clear anti-pattern (8-14 words)"
    }
    // produce EXACTLY 5 pairs
  ],
  "footer": "Closing CTA one-liner"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#fff"/>
        <rect x="4" y="12" width="24" height="58" rx="3" fill="#f0fdf4" stroke="#22c55e" strokeWidth="0.8"/>
        <rect x="32" y="12" width="24" height="58" rx="3" fill="#fef2f2" stroke="#ef4444" strokeWidth="0.8"/>
        <text x="16" y="10" textAnchor="middle" fontSize="5" fontWeight="800" fill="#22c55e">DO</text>
        <text x="44" y="10" textAnchor="middle" fontSize="5" fontWeight="800" fill="#ef4444">DON'T</text>
        {[0,1,2,3,4].map(i => (
          <g key={i}>
            <rect x="8" y={16 + i * 10} width="16" height="6" rx="1" fill="#22c55e" opacity="0.25"/>
            <rect x="36" y={16 + i * 10} width="16" height="6" rx="1" fill="#ef4444" opacity="0.25"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const pairs = (data.pairs || []).slice(0, 5);
    return (
      <div style={dosStyles.frame}>
        <div style={dosStyles.header}>
          <h1 style={dosStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={dosStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={dosStyles.columns}>
          <div style={dosStyles.colHeader}>
            <span style={dosStyles.doLabel}>DO</span>
            <span style={dosStyles.dontLabel}>DON'T</span>
          </div>
          {pairs.map((p, i) => (
            <div key={i} style={dosStyles.pairRow}>
              <div style={dosStyles.doCell}>
                <i className="ti ti-check" style={{ color: "#22c55e", fontSize: 18, flexShrink: 0 }}></i>
                <span>{p.do}</span>
              </div>
              <div style={dosStyles.dontCell}>
                <i className="ti ti-x" style={{ color: "#ef4444", fontSize: 18, flexShrink: 0 }}></i>
                <span>{p.dont}</span>
              </div>
            </div>
          ))}
        </div>
        {data.footer && <div style={dosStyles.footer}>{data.footer}</div>}
      </div>
    );
  },
};

const dosStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "36px 32px 28px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  header: { textAlign: "center", marginBottom: 24 },
  title: { fontSize: 34, fontWeight: 900, color: "#0f172a", margin: "0 0 8px", lineHeight: 1.1 },
  subtitle: { fontSize: 15, color: "#64748b" },
  columns: { flex: 1, display: "flex", flexDirection: "column", gap: 10 },
  colHeader: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 4 },
  doLabel: { fontSize: 12, fontWeight: 800, letterSpacing: 2, color: "#22c55e", textAlign: "center" },
  dontLabel: { fontSize: 12, fontWeight: 800, letterSpacing: 2, color: "#ef4444", textAlign: "center" },
  pairRow: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, flex: 1 },
  doCell: {
    display: "flex", gap: 10, alignItems: "flex-start",
    padding: "14px 12px", background: "#f0fdf4", borderRadius: 10,
    fontSize: 13, color: "#166534", lineHeight: 1.45, fontWeight: 500,
  },
  dontCell: {
    display: "flex", gap: 10, alignItems: "flex-start",
    padding: "14px 12px", background: "#fef2f2", borderRadius: 10,
    fontSize: 13, color: "#991b1b", lineHeight: 1.45, fontWeight: 500,
  },
  footer: { marginTop: 16, textAlign: "center", fontSize: 14, fontWeight: 600, color: "#475569", fontStyle: "italic" },
};

/* ========================================================================
   29. ICON MATRIX  (3×3 grid — nine concepts on 800×1000)
   ======================================================================== */
const iconMatrixTemplate = {
  id: "icon-matrix",
  name: "Icon matrix (3×3)",
  description: "Nine concept cells in a 3×3 grid with icon, label, and micro-description. Fits taxonomy and model-type posts.",
  width: 800,
  height: 1000,
  schema: `{
  "eyebrow": "FRAMEWORK",
  "title": "9 Types of X",                   // 4-7 words
  "subtitle": "One framing sentence",
  "accent": "violet",
  "cells": [
    {
      "icon": "cpu",
      "label": "CELL LABEL",                 // 1-3 words ALL CAPS
      "detail": "One short sentence (10-16 words).",
      "color": "purple"                      // each cell a DIFFERENT color
    }
    // produce EXACTLY 9 cells
  ],
  "footer": "Takeaway one-liner"
}`,

  Thumbnail() {
    const cols = ["#8b5cf6","#3b82f6","#06b6d4","#22c55e","#f59e0b","#ef4444","#ec4899","#6366f1","#14b8a6"];
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#f8fafc"/>
        {[0,1,2].map(row => [0,1,2].map(col => {
          const i = row * 3 + col;
          return (
            <rect key={i} x={4 + col * 18} y={14 + row * 18} width="16" height="15" rx="2" fill={cols[i]} opacity="0.25"/>
          );
        }))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#8b5cf6");
    const cells = (data.cells || []).slice(0, 9);
    return (
      <div style={matrixStyles.frame}>
        <div style={matrixStyles.header}>
          <div style={{...matrixStyles.eyebrow, color: ac}}>{data.eyebrow}</div>
          <h1 style={matrixStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={matrixStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={matrixStyles.grid}>
          {cells.map((cell, i) => {
            const c = colorFor(cell.color, ac);
            return (
              <div key={i} style={{...matrixStyles.cell, borderTop: `3px solid ${c}`}}>
                <div style={{...matrixStyles.cellIcon, background: c + "18", color: c}}>
                  <i className={"ti ti-" + (cell.icon || "star")} style={{ fontSize: 22 }}></i>
                </div>
                <div style={{...matrixStyles.cellLabel, color: c}}>{cell.label}</div>
                <div style={matrixStyles.cellDetail}>{cell.detail}</div>
              </div>
            );
          })}
        </div>
        {data.footer && <div style={{...matrixStyles.footer, color: ac}}>{data.footer}</div>}
      </div>
    );
  },
};

const matrixStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "32px 28px 24px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  header: { textAlign: "center", marginBottom: 20 },
  eyebrow: { fontSize: 11, fontWeight: 700, letterSpacing: 3, textTransform: "uppercase", marginBottom: 8 },
  title: { fontSize: 32, fontWeight: 900, color: "#0f172a", margin: "0 0 6px", lineHeight: 1.1 },
  subtitle: { fontSize: 14, color: "#64748b" },
  grid: { flex: 1, display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10 },
  cell: {
    background: "#f8fafc",
    borderRadius: 10,
    padding: "12px 10px",
    display: "flex",
    flexDirection: "column",
    gap: 6,
    minHeight: 0,
  },
  cellIcon: { width: 40, height: 40, borderRadius: 8, display: "grid", placeItems: "center" },
  cellLabel: { fontSize: 11, fontWeight: 800, letterSpacing: 0.8, lineHeight: 1.2 },
  cellDetail: { fontSize: 11, color: "#64748b", lineHeight: 1.35, flex: 1 },
  footer: { marginTop: 14, textAlign: "center", fontSize: 13, fontWeight: 600, fontStyle: "italic" },
};

/* ========================================================================
   30. TOP RANKING  (800×1000 — leaderboard style)
   ======================================================================== */
const topRankingTemplate = {
  id: "top-ranking",
  name: "Top 5 ranking",
  description: "Ranked list with medal-style badges for #1–#5. Great for tools, tactics, or model comparisons.",
  width: 800,
  height: 1000,
  schema: `{
  "eyebrow": "RANKED LIST",
  "title": "Top 5 X for Y",                  // punchy headline
  "subtitle": "One sentence criteria",
  "accent": "amber",
  "items": [
    {
      "rank": 1,                             // integer 1-5
      "name": "Item name",                   // 2-5 words
      "score": "9.5/10",                     // optional score label
      "reason": "One sentence why it ranks here (12-20 words).",
      "icon": "trophy",
      "color": "amber"                       // top ranks use gold/silver/bronze tones
    }
    // produce EXACTLY 5 items, rank 1 (best) through 5
  ],
  "footer": "Agree? Comment your pick."
}`,

  Thumbnail() {
    const medals = ["#f59e0b", "#94a3b8", "#cd7f32", "#64748b", "#64748b"];
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#0f172a"/>
        {[0,1,2,3,4].map(i => (
          <g key={i} transform={`translate(6, ${12 + i * 12})`}>
            <circle cx="8" cy="5" r="5" fill={medals[i]}/>
            <rect x="18" y="2" width="32" height="4" rx="1" fill="#f8fafc" opacity="0.7"/>
            <rect x="18" y="7" width="24" height="2.5" rx="1" fill="#94a3b8" opacity="0.5"/>
          </g>
        ))}
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const ac = colorFor(data.accent, "#f59e0b");
    const rankColors = { 1: "#f59e0b", 2: "#94a3b8", 3: "#cd7f32", 4: "#64748b", 5: "#475569" };
    const items = (data.items || []).slice(0, 5).sort((a, b) => (a.rank || 99) - (b.rank || 99));
    return (
      <div style={rankStyles.frame}>
        <div style={rankStyles.header}>
          <div style={{...rankStyles.eyebrow, color: ac}}>{data.eyebrow}</div>
          <h1 style={rankStyles.title}>{data.title}</h1>
          {data.subtitle && <div style={rankStyles.subtitle}>{data.subtitle}</div>}
        </div>
        <div style={rankStyles.list}>
          {items.map((item, i) => {
            const r = item.rank || i + 1;
            const medal = rankColors[r] || colorFor(item.color, ac);
            return (
              <div key={i} style={{...rankStyles.row, borderLeft: `5px solid ${medal}`}}>
                <div style={{...rankStyles.badge, background: medal, color: r <= 3 ? "#0f172a" : "#fff"}}>
                  #{r}
                </div>
                <div style={rankStyles.content}>
                  <div style={rankStyles.rowTop}>
                    <span style={rankStyles.name}>{item.name}</span>
                    {item.score && <span style={{...rankStyles.score, color: medal}}>{item.score}</span>}
                    <i className={"ti ti-" + (item.icon || "star")} style={{ fontSize: 20, color: medal, marginLeft: "auto" }}></i>
                  </div>
                  <div style={rankStyles.reason}>{item.reason}</div>
                </div>
              </div>
            );
          })}
        </div>
        {data.footer && <div style={{...rankStyles.footer, color: ac}}>{data.footer}</div>}
      </div>
    );
  },
};

const rankStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#0f172a",
    fontFamily: "'Inter', sans-serif",
    padding: "36px 32px 28px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
    color: "#f8fafc",
  },
  header: { textAlign: "center", marginBottom: 24 },
  eyebrow: { fontSize: 11, fontWeight: 700, letterSpacing: 3, textTransform: "uppercase", marginBottom: 8 },
  title: { fontSize: 34, fontWeight: 900, margin: "0 0 8px", lineHeight: 1.1 },
  subtitle: { fontSize: 15, color: "#94a3b8" },
  list: { flex: 1, display: "flex", flexDirection: "column", gap: 12 },
  row: {
    display: "flex",
    gap: 16,
    alignItems: "flex-start",
    padding: "16px 18px",
    background: "#1e293b",
    borderRadius: 12,
  },
  badge: {
    width: 44, height: 44, borderRadius: 10,
    display: "grid", placeItems: "center",
    fontSize: 18, fontWeight: 900, flexShrink: 0,
  },
  content: { flex: 1, minWidth: 0 },
  rowTop: { display: "flex", alignItems: "center", gap: 10, marginBottom: 6 },
  name: { fontSize: 18, fontWeight: 800 },
  score: { fontSize: 13, fontWeight: 700 },
  reason: { fontSize: 13, color: "#94a3b8", lineHeight: 1.45 },
  footer: { marginTop: 16, textAlign: "center", fontSize: 14, fontWeight: 600, fontStyle: "italic" },
};

/* ========================================================================
   31. PROBLEM → SOLUTION  (800×1000 — two-act vertical story)
   ======================================================================== */
const problemSolutionTemplate = {
  id: "problem-solution",
  name: "Problem → solution",
  description: "Red problem block then green solution block with 4 pain points and 4 fixes. Strong before/after narrative.",
  width: 800,
  height: 1000,
  schema: `{
  "title": "Stop X. Start Y.",               // punchy headline bridging both halves
  "problemLabel": "THE PROBLEM",
  "solutionLabel": "THE FIX",
  "problems": [
    { "text": "Pain point stated clearly (8-14 words)" }
    // produce EXACTLY 4 problems
  ],
  "solutions": [
    { "text": "Matching solution (8-14 words)" }
    // produce EXACTLY 4 solutions — each pairs with the problem above by index
  ],
  "footer": "Closing CTA"
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 60 75" xmlns="http://www.w3.org/2000/svg">
        <rect width="60" height="75" fill="#fff"/>
        <rect x="4" y="10" width="52" height="28" rx="3" fill="#fef2f2" stroke="#ef4444" strokeWidth="0.8"/>
        <rect x="4" y="40" width="52" height="28" rx="3" fill="#f0fdf4" stroke="#22c55e" strokeWidth="0.8"/>
        <text x="30" y="8" textAnchor="middle" fontSize="5" fontWeight="800" fill="#0f172a">P → S</text>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const problems = (data.problems || []).slice(0, 4);
    const solutions = (data.solutions || []).slice(0, 4);
    return (
      <div style={psStyles.frame}>
        <h1 style={psStyles.title}>{data.title}</h1>
        <div style={psStyles.halves}>
          <div style={psStyles.problemHalf}>
            <div style={psStyles.halfLabel}>{data.problemLabel || "THE PROBLEM"}</div>
            {problems.map((p, i) => (
              <div key={i} style={psStyles.problemRow}>
                <i className="ti ti-alert-triangle" style={{ color: "#ef4444", fontSize: 18 }}></i>
                <span>{p.text}</span>
              </div>
            ))}
          </div>
          <div style={psStyles.solutionHalf}>
            <div style={{...psStyles.halfLabel, color: "#166534"}}>{data.solutionLabel || "THE FIX"}</div>
            {solutions.map((s, i) => (
              <div key={i} style={psStyles.solutionRow}>
                <i className="ti ti-circle-check" style={{ color: "#22c55e", fontSize: 18 }}></i>
                <span>{s.text}</span>
              </div>
            ))}
          </div>
        </div>
        {data.footer && <div style={psStyles.footer}>{data.footer}</div>}
      </div>
    );
  },
};

const psStyles = {
  frame: {
    width: 800, height: 1000,
    background: "#ffffff",
    fontFamily: "'Inter', sans-serif",
    padding: "32px 28px 24px",
    boxSizing: "border-box",
    display: "flex",
    flexDirection: "column",
  },
  title: { fontSize: 32, fontWeight: 900, color: "#0f172a", textAlign: "center", margin: "0 0 20px", lineHeight: 1.1 },
  halves: { flex: 1, display: "flex", flexDirection: "column", gap: 14 },
  problemHalf: { flex: 1, background: "#fef2f2", borderRadius: 14, padding: "18px 20px", border: "2px solid #fecaca" },
  solutionHalf: { flex: 1, background: "#f0fdf4", borderRadius: 14, padding: "18px 20px", border: "2px solid #bbf7d0" },
  halfLabel: { fontSize: 11, fontWeight: 800, letterSpacing: 2, color: "#991b1b", marginBottom: 12 },
  problemRow: { display: "flex", gap: 10, alignItems: "flex-start", padding: "8px 0", fontSize: 14, color: "#7f1d1d", lineHeight: 1.4, borderTop: "1px solid #fecaca" },
  solutionRow: { display: "flex", gap: 10, alignItems: "flex-start", padding: "8px 0", fontSize: 14, color: "#166534", lineHeight: 1.4, borderTop: "1px solid #bbf7d0" },
  footer: { marginTop: 14, textAlign: "center", fontSize: 14, fontWeight: 600, color: "#475569", fontStyle: "italic" },
};

/* ========================================================================
   BLANK CANVAS — freeform text, no structured layout
   ======================================================================== */
const blankCanvasTemplate = {
  id: "blank-canvas",
  name: "Blank / your draft",
  description: "No fixed layout — paste your draft and the AI polishes it into clean, readable text for export.",
  width: 1080,
  height: 1350,
  schema: `{
  "title": "Hook headline — the first line or title of the post (under 120 chars)",
  "body": "Full post body as plain text with paragraph breaks (use \\n\\n between paragraphs). Preserve numbered sections, bullet points, and checkmarks from the source. May be long (400–900 words)."
}`,

  Thumbnail() {
    return (
      <svg viewBox="0 0 80 100" xmlns="http://www.w3.org/2000/svg">
        <rect x="4" y="4" width="72" height="92" rx="4" fill="#f8fafc" stroke="#94a3b8" strokeWidth="1.5" strokeDasharray="4 3"/>
        <line x1="14" y1="22" x2="66" y2="22" stroke="#64748b" strokeWidth="2" strokeLinecap="round"/>
        <line x1="14" y1="34" x2="58" y2="34" stroke="#cbd5e1" strokeWidth="1.5" strokeLinecap="round"/>
        <line x1="14" y1="44" x2="62" y2="44" stroke="#cbd5e1" strokeWidth="1.5" strokeLinecap="round"/>
        <line x1="14" y1="54" x2="50" y2="54" stroke="#cbd5e1" strokeWidth="1.5" strokeLinecap="round"/>
        <line x1="14" y1="64" x2="56" y2="64" stroke="#cbd5e1" strokeWidth="1.5" strokeLinecap="round"/>
        <text x="40" y="88" textAnchor="middle" fontSize="8" fill="#94a3b8" fontFamily="system-ui">draft</text>
      </svg>
    );
  },

  Render({ data }) {
    if (!data) return null;
    const body = (data.body || "").replace(/\\n/g, "\n");
    const paragraphs = body.split(/\n\n+/).filter(Boolean);
    return (
      <div style={{
        width: 1080, minHeight: 1350, background: "#ffffff",
        padding: "72px 80px", boxSizing: "border-box",
        fontFamily: "'Segoe UI', system-ui, sans-serif", color: "#1e293b",
      }}>
        {data.title && (
          <h1 style={{
            fontSize: 42, fontWeight: 700, lineHeight: 1.25, margin: "0 0 40px",
            color: "#0f172a", letterSpacing: "-0.02em",
          }}>{data.title}</h1>
        )}
        <div style={{ fontSize: 26, lineHeight: 1.65, color: "#334155" }}>
          {paragraphs.length > 0
            ? paragraphs.map((p, i) => (
                <p key={i} style={{ margin: "0 0 22px", whiteSpace: "pre-wrap" }}>{p}</p>
              ))
            : <p style={{ margin: 0, whiteSpace: "pre-wrap" }}>{body}</p>}
        </div>
      </div>
    );
  },
};

/* ========================================================================
   REGISTRY
   ======================================================================== */
const TEMPLATES = [
  blankCanvasTemplate,
  whiteboardTemplate,
  stackedTemplate,
  anatomyTemplate,
  vsTemplate,
  splitComparisonTemplate,
  comparisonRevealTemplate,
  tipCardsTemplate,
  processFlowTemplate,
  conceptGridTemplate,
  bigListTemplate,
  wideBannerTemplate,
  horizontalTimelineTemplate,
  squareSpotlightTemplate,
  cheatSheetTemplate,
  mythFactTemplate,
  statDashboardTemplate,
  storySlidesTemplate,
  scrollStepsTemplate,
  frameSequenceTemplate,
  flipQuadrantTemplate,
  scrollReelTemplate,
  typewriterListTemplate,
  spotlightCycleTemplate,
  counterSplashTemplate,
  layerStackRevealTemplate,
  quoteSpotlightTemplate,
  pdfDeckSlidesTemplate,
  dosDontsTemplate,
  iconMatrixTemplate,
  topRankingTemplate,
  problemSolutionTemplate,
];

function buildVisualSystemPrompt() {
  return `You are an expert information designer who turns topics into structured, dense, accurate infographic content.

You will receive a topic, niche, and target audience, plus a TEMPLATE SCHEMA describing the exact JSON shape required.

RULES — follow strictly:
- Output ONLY a valid JSON object that matches the schema. No preamble, no markdown fences, no explanation text.
- Fill EVERY field. Do not invent fields not in the schema.
- Be specific and authoritative. Use real names, real tools, real terminology where appropriate.
- Keep each text field as short as the schema implies. Infographics are space-constrained — short, punchy phrases win.
- Be opinionated and concrete. Avoid generic filler ("important considerations", "various aspects").
- ${TPL_TABLER_ICON_HINT}`;
}

function buildVisualUserPrompt({ template, topic, niche, audience, byline, layoutGuidance, recentVisualPosts, sourceContent }) {
  const lines = [];
  const hasDraft = !!(sourceContent && sourceContent.trim());
  const isBlank = template.id === "blank-canvas";

  if (hasDraft) {
    lines.push(`Improve and adapt the user's SOURCE DRAFT into the template structure below.`);
    lines.push(`Preserve facts, examples, numbered sections, and voice — do NOT invent unrelated claims.`);
    if (isBlank) {
      lines.push(`BLANK TEMPLATE: Polish the draft for maximum readability. Keep full depth — body may be 400–900 words.`);
    } else {
      lines.push(`FIT TO TEMPLATE: Extract and compress the source into the schema fields. Map list items to template sections.`);
    }
    lines.push(``);
  }

  lines.push(`TEMPLATE: ${template.name}`);
  lines.push(`TEMPLATE DESCRIPTION: ${template.description}`);
  lines.push(``);
  lines.push(`SCHEMA — produce a JSON object EXACTLY in this shape:`);
  lines.push(template.schema);
  lines.push(``);
  lines.push(`INPUTS:`);
  lines.push(`  topic: ${topic || (hasDraft ? "(derived from draft)" : "(unspecified)")}`);
  lines.push(`  niche / expertise area: ${niche || "(unspecified)"}`);
  lines.push(`  target audience: ${audience || "(unspecified)"}`);
  if (byline) lines.push(`  byline: use "${byline}" wherever a byline field appears`);
  if (layoutGuidance) lines.push(`  layout guidance: ${layoutGuidance}`);
  if (template.animated) {
    lines.push(`  animation: structure content so each major list item maps to one GIF frame; keep each frame self-contained and concise.`);
  }

  if (hasDraft) {
    lines.push(``);
    lines.push(`=== SOURCE DRAFT ===`);
    lines.push(sourceContent.trim());
    lines.push(`=== END SOURCE DRAFT ===`);
  }

  if (recentVisualPosts && recentVisualPosts.length) {
    lines.push(``);
    lines.push(`=== ANTI-REPEAT MEMORY ===`);
    lines.push(`Recent visual posts from this user. Do NOT repeat topics, structures, framings, or example items:`);
    recentVisualPosts.forEach((p, i) => {
      lines.push(`#${i+1} (${p.templateId}): ${p.topic} — ${(p.data && (p.data.title || p.data.eyebrow || "")).toString().slice(0, 80)}`);
    });
    lines.push(`=== END MEMORY ===`);
  }

  lines.push(``);
  lines.push(`Now produce the JSON for: ${topic}`);
  lines.push(`Output JSON only.`);
  return lines.join("\n");
}

Object.assign(window, {
  TEMPLATES,
  buildVisualSystemPrompt,
  buildVisualUserPrompt,
});
