PageSourceSearch

https://postiq.netlify.app/js/features/content-pillars.js

js postiq.netlify.app collected 2026-10-03 11:17:37 UTC 72,320 bytes, 741 lines download raw bytes

1'use strict';
2
3// ── CONTENT PILLARS v2 ────────────────────────────────────
4// ═══════════════════════════════════════════════════════════════
5// STRATEGY SNACK MACHINE × POSTIQ INTEGRATION
6// Drop this entire block into app.js, replacing the existing
7// ContentPillars IIFE. Then update the cpGateNew handler below.
8//
9// CHANGES TO EXISTING HTML (app.html):
10//   1. Replace cpGateNew card inner HTML (see GATE CARD HTML section)
11//   2. Add SSM form HTML (see SSM FORM HTML section)
12//   Both are drop-in replacements — no structural changes needed.
13// ═══════════════════════════════════════════════════════════════
14
15// ── SSM GATE CARD HTML (replace cpGateNew button contents) ──────
16// Paste this as the innerHTML of the existing #cpGateNew button:
17/*
18<span class="cp-gate-icon">⚡</span>
19<span style="display:inline-flex;align-items:center;gap:4px;font-size:10px;font-family:'DM Mono',monospace;font-weight:600;letter-spacing:.06em;text-transform:uppercase;background:var(--brand-dim);border:1px solid var(--brand-glow);color:var(--brand);padding:2px 8px;border-radius:4px;margin-bottom:8px;">Pillar Plan Builder</span>
20<span class="cp-gate-title">Generate my content strategy</span>
21<span class="cp-gate-desc">Answer 6 quick questions. Get 5 content pillars, 25 post ideas, hooks, CTAs, recurring series ideas, and one post you can publish today.</span>
22<span class="cp-gate-cta">Build my plan →</span>
23*/
24
25// ── SSM FORM HTML (insert inside #cpStageJourney, replace all inner content) ──
26/*
27Replace everything inside <div class="cp-stage" id="cpStageJourney"> with:
28
29<div style="max-width:660px;">
30  <!-- Loading screen (hidden until generate()) -->
31  <div id="ssmLoading" style="display:none;text-align:center;padding:40px 20px;">
32    <div style="font-family:'DM Mono',monospace;font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--accent);margin-bottom:12px;" id="ssmLoadStep">Reading your inputs...</div>
33    <div style="border:1px solid var(--border2);height:14px;max-width:280px;margin:0 auto 16px;overflow:hidden;border-radius:2px;"><div style="height:100%;background:var(--brand);width:0%;transition:width .1s linear;" id="ssmLoadBar"></div></div>
34    <div style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:20px;color:var(--ink);letter-spacing:-.02em;">Building your pillar plan<span id="ssmDots">...</span></div>
35  </div>
36
37  <!-- Form (shown by default) -->
38  <div id="ssmForm">
39    <div style="margin-bottom:20px;">
40      <div style="font-family:'DM Mono',monospace;font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--brand);margin-bottom:8px;">Pillar Plan Builder</div>
41      <div style="font-family:'Bricolage Grotesque',sans-serif;font-weight:700;font-size:18px;color:var(--ink);margin-bottom:4px;">Answer a few questions. Get a pillar plan.</div>
42      <div style="font-size:13px;color:var(--muted);line-height:1.6;">6 quick fields. Deterministic output — built from your inputs.</div>
43    </div>
44
45    <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
46      <div class="field">
47        <label class="label">Brand / Creator Name</label>
48        <input type="text" class="input" id="ssm-brand" placeholder="e.g. Maria Chen, TechWave" />
49      </div>
50      <div class="field">
51        <label class="label">Industry Category</label>
52        <select class="input" id="ssm-industry">
53          <option value="">Select industry...</option>
54          <option value="fitness">Fitness & Wellness</option>
55          <option value="food">Food & Beverage</option>
56          <option value="saas">SaaS / Tech</option>
57          <option value="realestate">Real Estate</option>
58          <option value="creative">Creative Services</option>
59          <option value="ecommerce">E-Commerce / Product Brand</option>
60          <option value="coaching">Coaching & Consulting</option>
61          <option value="finance">Finance & Money</option>
62          <option value="education">Education & Courses</option>
63          <option value="nonprofit">Nonprofit / Mission-Driven</option>
64          <option value="entertainment">Entertainment / Media</option>
65          <option value="localbiz">Local Business</option>
66        </select>
67      </div>
68      <div class="field">
69        <label class="label">Target Audience</label>
70        <input type="text" class="input" id="ssm-audience" placeholder="e.g. early-stage founders, busy moms" />
71      </div>
72      <div class="field">
73        <label class="label">Product / Service / Offer</label>
74        <input type="text" class="input" id="ssm-offer" placeholder="e.g. 1:1 coaching, SaaS tool, meal kits" />
75      </div>
76      <div class="field">
77        <label class="label">Brand Tone</label>
78        <select class="input" id="ssm-tone">
79          <option value="">Select tone...</option>
80          <option value="bold">Bold & Direct</option>
81          <option value="playful">Playful & Fun</option>
82          <option value="expert">Expert & Authoritative</option>
83          <option value="warm">Warm & Relatable</option>
84          <option value="edgy">Edgy & Provocative</option>
85          <option value="professional">Professional & Polished</option>
86        </select>
87      </div>
88      <div class="field">
89        <label class="label">Main Goal</label>
90        <select class="input" id="ssm-goal">
91          <option value="">Select goal...</option>
92          <option value="grow">Grow Audience</option>
93          <option value="sales">Drive Sales</option>
94          <option value="trust">Build Trust</option>
95          <option value="educate">Educate</option>
96          <option value="hired">Get Hired</option>
97          <option value="launch">Launch Product</option>
98          <option value="event">Promote Event</option>
99        </select>
100      </div>
101    </div>
102
103    <div class="field" style="margin-top:4px;grid-column:1/-1;">
104      <label class="label">Anything else? (optional)</label>
105      <textarea class="input" id="ssm-notes" placeholder="Upcoming launches, topics to avoid, specific angles..." style="min-height:60px;"></textarea>
106    </div>
107
108    <div style="display:flex;gap:8px;margin-top:16px;align-items:center;">
109      <button class="btn ghost" id="ssmBackBtn" type="button">← Back</button>
110      <button class="btn primary" id="ssmGenerateBtn" type="button">⚡ Generate pillar plan →</button>
111    </div>
112  </div>
113</div>
114*/
115
116// ═══════════════════════════════════════════════════════════════
117// JAVASCRIPT — replaces window.ContentPillars IIFE in app.js
118// ═══════════════════════════════════════════════════════════════
119
120window.ContentPillars = (() => {
121  const CP_KEY    = 'postiq_pillars_v3';
122  const USAGE_KEY = 'postiq_pillars_usage_v1';
123  const CP_TEMPLATE_TAG = 'postiq-content-pillars';
124  const TONES     = ['Practical', 'Story', 'Contrarian', 'Question'];
125  const COLORS    = ['#3a3fff','#0fa672','#f59e0b','#ff4f6a','#7c3aed','#9298b0'];
126
127  const cpState = {
128    identity: '',
129    pillars: [],
130    _ssmInputs: null,
131  };
132
133  const cpQs    = id => document.getElementById(id);
134  const cpUid   = () => Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 7);
135  const cpEsc   = s  => String(s || '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
136  const pillarColor = i => COLORS[i % COLORS.length];
137
138  function cpGetUsage() { try { return JSON.parse(localStorage.getItem(USAGE_KEY) || '{}'); } catch { return {}; } }
139  function cpBumpUsage(pid) { const u = cpGetUsage(); u[pid] = (u[pid] || 0) + 1; try { localStorage.setItem(USAGE_KEY, JSON.stringify(u)); } catch {} }
140  function cpTotalUsage() { return Object.values(cpGetUsage()).reduce((a, b) => a + Number(b || 0), 0); }
141  function cpUsageFor(pid) { return cpGetUsage()[pid] || 0; }
142
143  function cpGeneratedSeriesNames() {
144    try { return new Set(Object.values(SERIES || {}).flat().map(s => String(s?.name || '').trim()).filter(Boolean)); }
145    catch { return new Set(); }
146  }
147
148  function cpIsGeneratedTemplate(tpl) {
149    const tags = Array.isArray(tpl?.tags) ? tpl.tags.map(x => String(x || '').trim().toLowerCase()) : [];
150    if (tags.includes(CP_TEMPLATE_TAG) || tags.includes('pillar-plan')) return true;
151    const title = String(tpl?.title || '').trim();
152    return tags.includes('series') && cpGeneratedSeriesNames().has(title);
153  }
154
155  function cpClearGeneratedTemplates() {
156    if (typeof state === 'undefined' || !Array.isArray(state.templates)) return 0;
157    const before = state.templates.length;
158    state.templates = state.templates.filter(t => !cpIsGeneratedTemplate(t));
159    const removed = before - state.templates.length;
160    if (removed > 0) {
161      if (typeof persistTemplates === 'function') persistTemplates();
162      if (typeof renderTemplates === 'function') renderTemplates();
163    }
164    return removed;
165  }
166
167  function cpNormalizePillar(p, i = 0) {
168    return {
169      id:      String(p?.id      || cpUid()),
170      name:    String(p?.name    || `Pillar ${i + 1}`),
171      promise: String(p?.promise || p?.desc || ''),
172      layer:   ['awareness','credibility','action'].includes(p?.layer) ? p.layer : '',
173      seeds:   Array.isArray(p?.seeds) && p.seeds.length ? p.seeds.map(s => String(s || '')) : (Array.isArray(p?.ideas) ? p.ideas.map(s => String(s || '')) : ['']),
174      tones:   p?.tones && typeof p.tones === 'object' ? p.tones : {},
175      hook:    String(p?.hook || ''),
176      cta:     String(p?.cta  || ''),
177    };
178  }
179
180  function cpPersist() {
181    try { localStorage.setItem(CP_KEY, JSON.stringify({ identity: cpState.identity, pillars: cpState.pillars })); } catch {}
182    cpRenderCompact();
183  }
184
185  function cpLoad() {
186    try {
187      const d = JSON.parse(localStorage.getItem(CP_KEY) || 'null');
188      if (d?.pillars?.length) {
189        cpState.identity = d.identity || '';
190        cpState.pillars  = d.pillars.map(cpNormalizePillar);
191        return true;
192      }
193    } catch {}
194    return false;
195  }
196
197  function cpShowStage(id) {
198    document.querySelectorAll('.cp-stage').forEach(el => el.classList.remove('active'));
199    const el = cpQs(id);
200    if (el) el.classList.add('active');
201  }
202
203  // ── SSM ENGINE ─────────────────────────────────────────────────
204
205  // Pillar library (subset — grow/sales for most verticals)
206  // Full library inlined from SSM index.html
207  const LIBRARY = {
208    fitness_grow: [
209      { name:'Real Progress, No Filter', desc:'Show the actual journey — sweat, setbacks, and small wins.', ideas:['Week 3 and I almost quit — here\'s what kept me going','My workout looked nothing like the plan today. Here\'s why that\'s fine','The fitness advice that actually worked for me (vs what I tried first)','What nobody shows you about getting in shape after 30','Progress pic I almost didn\'t post — but here we are'], hook:'The fitness account you actually need isn\'t showing you the full picture.', cta:'Follow if you\'re tired of the highlight reel. Real progress lives here.' },
210      { name:'Workout How-Tos', desc:'Teach your audience a specific exercise, modification, or technique.', ideas:['How to do a proper Romanian deadlift if you\'ve never tried one','3 beginner-friendly swaps for exercises that hurt your knees','The form mistake everyone makes on bench press (and how to fix it)','5-minute morning mobility routine you can do in bed','How to build a full-body workout with just a resistance band'], hook:'You\'re working out consistently and still not seeing results. Here\'s why.', cta:'Save this one. Come back to it when you need a form check.' },
211      { name:'Myth Busting', desc:'Fitness is full of bad advice that won\'t die. Be the person who calls it out.', ideas:['No, you don\'t need to do cardio every day to lose fat','The \'no pain no gain\' mindset is actually keeping you injured','Spot reduction isn\'t real — here\'s what actually works','You don\'t need a gym membership to get strong','Why eating less is sometimes making your weight loss harder'], hook:'I\'ve been in fitness for years and the amount of bad advice out there is actually alarming.', cta:'Drop your biggest fitness myth in the comments. Let\'s debunk it.' },
212      { name:'Community & Motivation', desc:'Build belonging. People stay loyal to communities, not just content creators.', ideas:['If you worked out at all this week, this post is for you','Who else started their fitness journey after a doctor\'s visit?','The type of gym-goer I love to see (and it\'s not who you think)','Tag someone who needs to hear this today','What getting fit actually gave me that had nothing to do with my body'], hook:'Fitness changed my life. But not the way I expected.', cta:'Tell me where you\'re at in your journey. No judgment here.' },
213      { name:'Lifestyle & Nutrition', desc:'Show that fitness is a full-system game — sleep, food, stress, recovery all matter.', ideas:['What I eat in a day as someone who actually lifts','The sleep habit that improved my training more than any supplement','3 high-protein meals I make when I have no time','Why I stopped counting calories and what I do instead','Recovery isn\'t lazy. Here\'s how I structure my rest days'], hook:'Your workouts aren\'t the problem. Your recovery is.', cta:'Save this. Your future self will thank you.' },
214    ],
215    fitness_sales: [
216      { name:'Client Transformations', desc:'Document real outcomes from real clients — not just bodies, but confidence and consistency.', ideas:['Client went from skipping workouts to showing up 5x/week — here\'s what changed','Before & after: 90 days with a plan that actually fit her schedule','He told me he\'d "tried everything." 8 weeks later, this happened.','What my client learned in month one that she wishes she knew sooner','Real talk from a client: what coaching actually feels like from the inside'], hook:'I don\'t sell shortcuts. I sell systems that stick — and here\'s proof.', cta:'DM me READY to see if my coaching is the right fit for you.' },
217      { name:'What You Get', desc:'Break down your offer in concrete, specific terms.', ideas:['Here\'s exactly what happens in week 1 of working with me','What\'s included in my program (and why I built it that way)','The difference between my coaching and a generic app','How I build your program around your actual schedule, not an ideal one','FAQ: Is your program right for me? Honest answer.'], hook:'Most fitness programs fail because they weren\'t built for your life. Mine is.', cta:'See the full breakdown at the link in bio. Spots are limited.' },
218      { name:'Common Objections Answered', desc:'Meet your audience where their doubts live.', ideas:['"I don\'t have time to work out." Let me reframe that.','"I\'ve tried programs before and quit." Here\'s why this is different.','"I can just use YouTube." You can. Here\'s what you\'ll miss.','"I can\'t afford a coach right now." Let\'s talk about what\'s actually expensive.','"I\'m not in good enough shape to start." That\'s exactly why you should.'], hook:'You\'ve been telling yourself the same story for months. Let\'s challenge it.', cta:'Your questions are valid. Let\'s talk. DM me anytime.' },
219      { name:'Social Proof', desc:'Let your clients do the selling.', ideas:['What she told me at the end of our 12 weeks together','Client DM that made my morning — sharing with permission','From "I hate working out" to 4x a week. Her words, not mine.','Results that surprised even me (and I\'ve been coaching for years)','The review that reminded me why I do this'], hook:'I could tell you my program works. Or I could let my clients.', cta:'Ready to write your own story? DM me to get started.' },
220      { name:'Your Story & Method', desc:'Share why you do this and how your method is different.', ideas:['Why I became a fitness coach (it wasn\'t for the obvious reason)','The method I use that most trainers skip entirely','What I got wrong about fitness before I figured this out','My philosophy: I don\'t chase aesthetics, I chase ability','The moment I knew I had to start coaching other people'], hook:'There are thousands of fitness coaches. Here\'s why my clients choose me.', cta:'Follow for a different kind of fitness perspective. No fluff.' },
221    ],
222    saas_grow: [
223      { name:'Build In Public', desc:'Document what you\'re building, deciding, and learning.', ideas:['We shipped a feature we almost cut. Here\'s what convinced us.','The metric we watch instead of MRR (and why)','3 things our users told us that changed our roadmap','Why we chose to build in [tech stack] and what we\'d do differently','What our churn data taught us that our NPS score never could'], hook:'Most SaaS companies share the wins. We\'re going to share everything.', cta:'Follow if you want to see what building a software company actually looks like.' },
224      { name:'Hot Takes & Industry POV', desc:'Have real opinions about the tools, trends, and practices in your space.', ideas:['Unpopular opinion: your tech stack matters less than your distribution strategy','The SaaS metric everyone obsesses over that actually means nothing','Why most B2B software has a UX problem and nobody\'s talking about it','The "growth hack" that\'s actually just good product thinking','AI isn\'t replacing SaaS — but it\'s changing what SaaS has to be'], hook:'The SaaS advice that keeps getting recycled is keeping founders stuck.', cta:'Follow for takes on software, growth, and building products that last.' },
225      { name:'Founder & Team Stories', desc:'Put humans behind the logo.', ideas:['Why we turned down a partnership that looked perfect on paper','The hire that changed everything about how we build','A decision I made in year one that still haunts me (and what I fixed)','Our remote team has never met in person. Here\'s how we make it work.','The week we thought about shutting down — and what we learned'], hook:'Behind every SaaS is a team making hard calls with incomplete information.', cta:'Follow along as we build this thing in public — wins, mistakes, and all.' },
226      { name:'Use Case Education', desc:'Show your product solving real problems in specific industries.', ideas:['How a solo foun
226der uses [product] to replace 3 different tools','[Industry] team saved 6 hours/week with this workflow in [product]','The exact setup a [role] uses in [product] to manage their pipeline','3 ways [product] handles [problem] that [competitor] can\'t','A day in the life of a power user — every tool, every trigger'], hook:'You\'ve heard about [product]. Here\'s what it actually looks like in the wild.', cta:'Try it free. Link in bio. No credit card required.' },
227      { name:'Market Commentary', desc:'React to industry news with genuine analysis.', ideas:['What [major company] shutting down [feature] means for everyone in [space]','This funding round tells us a lot about where the market is going','The trend I\'m watching in [category] that nobody\'s written about yet','Why the consolidation happening in [space] is actually good for smaller players','My take on the "AI-first" narrative and what it gets wrong'], hook:'The [industry] news cycle moves fast. Here\'s the take you actually need.', cta:'Follow for weekly signal vs noise analysis on [space].' },
228    ],
229    saas_sales: [
230      { name:'Problem-First Content', desc:'Name the exact friction your product removes.', ideas:['If your team is still doing [task] manually, here\'s what that\'s costing you','The reason [workflow] breaks down at scale (and what companies do about it)','Signs y
230our current [tool category] is holding your team back','How much time does [problem] actually cost per week? (We did the math)','What happens when [process] doesn\'t have a system behind it'], hook:'The [problem] you\'ve been working around has a fix. You\'re just not using it.', cta:'See how [offer] handles this in 2 minutes — demo link in bio.' },
231      { name:'Feature Storytelling', desc:'Tell the story of the problem features solve.', ideas:['We built [feature] because a customer showed us something that broke our heart','How [feature] works and why we built it this way (not the obvious way)','The edge case that led to [feature] — and why it matters for your team','[Feature] in 60 seconds: here\'s what changes for your workflow','Before [feature] vs after [feature] — a real user workflow comparison'], hook:'Software features don\'t matter. The problems they solve do. Here\'s ours.', cta:'Activate [feature] in your account today.' },
232      { name:'ROI & Business Case', desc:'Help your buyers justify the purchase internally.', ideas:['How to calculate what [problem] is actually costing your company','The ROI calculation our customers use to justify [product] internally','What "saving time" actually means when 4 people use [product] every day','Our customers report [outcome] in the first 30 days — here\'s how','The business case for [product]: a template you can steal'], hook:'Your boss needs a number, not a feature list. Here\'s the number.', cta:'Need a custom ROI analysis? DM us your team size.' },
233      { name:'Customer Stories', desc:'Your customers are your best salespeople.', ideas:['[Customer type] reduced [metric] by [X]% using this workflow in [product]','From spreadsheet chaos to [outcome]: a [company type] story','How a [team size] team uses [product] to do what used to take twice as many people','The customer who said they\'d "never switch tools again" — and what changed their mind','Interview: how [customer type] went from [before] to [after] in 60 days'], hook:'Don\'t take our word for it. Here\'s what [industry] teams say after 90 days.', cta:'Book a 15-min call. We\'ll show you how this works for your team.' },
234      { name:'Comparison & Positioning', desc:'Help buyers understand why you\'re the right choice.', ideas:['[Product] vs [competitor]: what\'s actually different (honest breakdown)','Why [product] isn\'t for everyone — and who it\'s built for','The workflow [competitor] can\'t do that [product] was designed for','What to look for when evaluating [tool category] (regardless of who you choose)','The questions to ask any [tool category] vendor before you sign'], hook:'Every [tool category] looks the same on a comparison page. Here\'s what to actually look for.', cta:'See a side-by-side comparison. Link in bio.' },
235    ],
236    coaching_grow: [
237      { name:'Thought Leadership', desc:'Share your genuine perspective on the problems your clients face.', ideas:['The advice every [client type] keeps getting that\'s actually holding them back','What [goal] actually requires — and why nobody wants to hear it','The coaching industry problem I\'m not willing to ignore anymore','Why [popular approach] works for some people and fails for most','My most controversial belief about [your niche] — and why I stand by it'], hook:'Most coaches in [space] are teaching the same thing. Here\'s what they\'re missing.', cta:'Follow if you\'re tired of advice that sounds good but doesn\'t land.' },
238      { name:'Client Story Spotlights', desc:'Tell transformation stories with context, emotion, and specifics.', ideas:['She came to me believing [limiting belief]. Here\'s what shifted.','The client who had tried everything before working with me — and what finally worked','What 6 months of coaching looks like from the inside','A client win that had nothing to do with [obvious metric]','The moment a client said something that completely changed how I coach'], hook:'Transformation doesn\'t look the way you think it does. Let me show you.', cta:'Follow for real stories about what growth actually looks like.' },
239      { name:'Your Framework & Method', desc:'Give your audience a taste of how you think and how you work.', ideas:['The 3-part framework I use with every new client','How I diagnose what\'s really blocking [audience type] in the first session','The question I ask that unlocks everything else','My signature process, explained — step by step','Why most [audience type] are solving the wrong problem'], hook:'Most people in [niche] focus on the symptom. I work on the root.', cta:'Follow to learn the framework behind real results.' },
240      { name:'Behind The Coaching Business', desc:'Show what it\'s like to run a coaching practice.', ideas:['What a full coaching week actually looks like for me','How I structure my offers (and why I changed them twice)','The boundary I set that changed my business','What I charge and why (transparency post)','The tools I use to run my entire coaching practice'], hook:'Running a coaching business is nothing like what the gurus show you.', cta:'Follow for an honest look at what building a coaching practice actually takes.' },
241      { name:'Mindset & Growth Content', desc:'Speak to the internal game — beliefs, fears, and patterns.', ideas:['The belief that\'s costing [audience type] the most right now','Why [audience] keep starting over (and how to stop)','The fear I see in almost every new client — and how we move through it','What "not ready" really means (and what to do about it)','The mindset shift that changes everything for [audience type]'], hook:'Your [goal] 
241isn\'t a strategy problem. It\'s a belief problem.', cta:'Follow if you\'re ready to work on the thing that\'s actually in the way.' },
242    ],
243    creative_grow: [
244      { name:'Process & Craft', desc:'Show your work in progress — not just the polished final product.', ideas:['Before and after: how this project evolved from brief to final delivery','The sketch that became the brand identity for [client type]','How I approach color when I have complete creative freedom','Why this logo went through 6 rounds — and what each version taught me','The creative decision I almost didn\'t make (and why I\'m glad I did)'], hook:'The final version never looks like the first one. Here\'s the full story.', cta:'Follow for a behind-the-scenes look at how creative work actually gets made.' },
245      { name:'Portfolio Storytelling', desc:'Tell the story behind the work — the brief, the challenge, the decision-making.', ideas:['The brief: [client] needed [goal]. Here\'s what we built and why.','This project had one constraint that made it better','The client who trusted us completely — and what that unlocked','A project I\'m proud of that almost nobody saw','What I would do differently on this project if I had another shot'], hook:'Great creative work doesn\'t happen in a vacuum. Here\'s the context behind this one.', cta:'See the full project breakdown at [link]. Follow for more.' },
246      { name:'Industry Takes', desc:'Have opinions about design, photography, video, or your craft.', ideas:['The design trend I\'m already tired of seeing everywhere','What makes [craft] actually hard — not just technically, but creatively','The client feedback that secretly made the project better','Why [popular style] is overused and what I\'d recommend instead','What AI is doing to [creative field] — my honest take'], hook:'Most designers/photographers/editors won\'t say this publicly. I will.', cta:'Follow for takes on [craft] that go beyond tutorials and aesthetics.' },
247      { name:'Creative Tips & Education', desc:'Teach your audience something real about your craft.', ideas:['The typography rule I apply to every project without fail','3 things I check before I call a design/photo/video done','How I approach creative briefs when clients don\'t know what they want','The free tool that changed how I present work to clients','What I wish I knew in year one of being a [role]'], hook:'I\'ve been doing [craft] for [X] years. Here\'s the shortcut I wish I had.', cta:'Save this. The tutorial version is coming next week.' },
248      { name:'Client Collaboration', desc:'Show what it\'s like to work with you.', ideas:['What my client onboarding actually looks like — step by step','The questions I ask before I touch any creative work','How I handle client feedback that misses the mark — honestly','What "unlimited revisions" really means and why I don\'t offer it','Why I show 1-2 concepts instead of 5 — and how clients feel about it afterwards'], hook:'Working with a creative shouldn\'t feel like a mystery. Here\'s how I make it easy.', cta:'Curious about working together? DM me. Let\'s see if it\'s a fit.' },
249    ],
250    ecommerce_grow: [
251      { name:'Product in the Wild', desc:'Show your product living in the world — styled, used, loved.', ideas:['How our customers are styling [product] this season','UGC of the week: [product] in a home we never expected','The way [product] actually gets used vs how we originally imagined','Our team\'s favorite way to use [product] (3 very different answers)','[Product] in a day: morning, afternoon, and night'], hook:'The best thing about [product] is how many different ways people use it.', cta:'Tag us in your [product] photos. We share every one.' },
252      { name:'Behind The Brand', desc:'Show your sourcing, your values, your process.', ideas:['Where [product] is made — and why we chose that factory/supplier/maker','The material we spent 6 months finding before we felt good about launching','Meet the person who [role] at [brand]','Why we made [brand decision] that other brands don\'t','The product we almost launched — and why we pulled it'], hook:'You can buy [product category] anywhere. Here\'s why people buy ours.', cta:'Follow [brand] to go behind the curtain on what we build and why.' },
253      { name:'Education & Use Cases', desc:'Teach your audience how to get more value out of what you sell.', ideas:['The [product] mistake most people make in the first week','How to get the most out of [product] — tips from our most loyal customers','[Product] 101: everything to know before your first order','The use case for [product] you probably haven\'t tried yet','How to pair [product] with [complementary thing] for [better outcome]'], hook:'Most people only use [product] one way. Here are four more.', cta:'Save this for when your order arrives.' },
254      { name:'Trend & Culture', desc:'Connect your product to what\'s culturally relevant right now.', ideas:['[Product] and the [trend] moment: why this is the year to care about [value]','How [cultural shift] is changing how people think about [product category]','The aesthetic moment [product] was made for — and why it\'s having a moment','What [pop culture thing] gets right about [product category]','Why [season/moment/year] is the perfect time for [product]'], hook:'[Product category] is having a moment. Here\'s where [brand] fits in.', cta:'Follow [brand] — we stay ahead of what\'s coming next.' },
255      { name:'Community & UGC', desc:'Turn your customers into content.', ideas:['Customer photo of the month — congrats to [handle]','The review that stopped us in our tracks — sharing it here','How our community is using [product] in ways we didn\'t expect','The [product] photo that outperformed every piece of branded content we\'ve made','Real customers, real homes, real [product]: this week\'s roundup'], hook:'Our best marketing doesn\'t come from us. It comes from people like you.', cta:'Order [product], take a photo, tag us. We\'ll feature you here.' },
256    ],
257    finance_grow: [
258      { name:'Personal Finance Education', desc:'Break down money concepts that feel intimidating into content that actually helps.', ideas:['What a Roth IRA actually is — explained like you\'re 25 and finally ready to care','The difference between good debt and bad debt (with real examples)','Why your budget keeps failing — and the simpler version that works','What [financial concept] means and why it matters for [audience]','The money move I wish I\'d made in my 20s (not clickbait)'], hook:'The financial system wasn\'t built to be understood. Let me fix that.', cta:'Follow for money content that makes you feel smarter, not worse.' },
259      { name:'Market & News Commentary', desc:'React to economic news and market events with genuine analysis.', ideas:['What today\'s [economic news] actually means for your accounts/savings/portfolio','The Fed rate decision explained in 3 sentences that actually make sense','Why I\'m not panicking about [market event] — here\'s how I\'m thinking about it','The economic news nobody\'s talking about — and why it matters','What this recession signal does and doesn\'t mean for your money'], hook:'Financial news is designed to be alarming. Let\'s look at it clearly instead.', cta:'Follow for weekly market context — no fear, no hype.' },
260      { name:'Budgeting & Spending Systems', desc:'Give your audience practical tools for managing money day-to-day.', ideas:['The budgeting method I\'ve seen work for people who hate budgets','How I allocate my income — the percentages and why','The subscription audit that saves people $100+ a month (do this today)','Why I stopped using a spending category called "miscellaneous"','How to pay yourself first when money is already tight'], hook:'Budgeting advice is everywhere. Here\'s what actually works in the real world.', cta:'Save this and actually do it this weekend. Your future self will be glad.' },
261      { name:'Building Wealth Basics', desc:'Demystify investing, saving, and building long-term wealth.', ideas:['What to do with $1,000 if you have no idea where to start','Index funds explained without the jargon — why I recommend them first','The wealth gap between people who invest and people who don\'t (the math is wild)','How compound interest works — visualized simply','3 investing mistakes I see over and over with first-timers'], hook:'Wealth building isn\'t complicated. It\'s just not taught anywhere.', cta:'Follow for the financial education system that failed you.' },
262      { name:'Financial Mindset', desc:'Address the emotional and psychological side of money.', ideas:['The money story from childhood that\'s affecting your finances right now','Why high earners still feel broke — the spending psychology behind it','The guilt I see around money that\'s keeping people from building wealth','What "good with money" actually means — it\'s not what you think','Financial anxiety is real. Here\'s how I talk to clients about it.'], hook:'Most money problems aren\'t math problems. They\'re psychology problems.', cta:'Follow if you want to fix your relationship with money — not just your budget.' },
263    ],
264    localbiz_grow: [
265      { name:'Local Expertise & Context', desc:'Be the most useful local expert in your space.', ideas:['The [neighborhood] spots we recommend to our own customers','What [local event/season] means for [your business category]','Why [city] customers are different from national trends — and what we do about it','The [local thing] that inspired something we do at [business]','Our take on what\'s happening in [local area] right now'], hook:'Nobody knows [city] like we do. Here\'s what we\'re seeing.', cta:'Follow [business] — we\'re as local as it gets.' },
266      { name:'Behind The Business', desc:'Show your team, your space, your process.', ideas:['Meet [team member] — what they do here and why they love it','A day in [business] before we open the doors','How we prep for [busy season] — the real version','What it actually took to open [business] in [city]','The decision we made about [business practice] that surprised people'], hook:'You\'ve probably walked past [business]. Here\'s who we actually are.', cta:'Come visit us at [address]. We\'re here [hours].' },
267      { name:'Customer Love', desc:'Celebrate your regulars, your reviews, and the community.', ideas:['Customer of the month: [name] has been coming since [year]','The review that made our whole team stop what they were doing','A thank-you post to our neighborhood for [milestone]','What a local regular means to a small business — honestly','The community event we hosted and what it meant to us'], hook:'Small businesses run on regulars. Here\'s a thank-you to ours.', cta:'Leave us a review if we\'ve earned it. It matters more than you know.' },
268      { name:'Educational & Service Content', desc:'Teach your audience something relevant to your category.', ideas:['What to look for when choosing a [business category] in [city]','The question you should always ask before hiring a [service type]','How to tell quality [product/service] from the generic version','Why [common misconception about your category] isn\'t quite right','What we\'ve learned after [X] years serving [city]'], hook:'We\'ve been in [business category] long enough to know what actually matters.', cta:'Questions about [service/product]? DM us or stop in anytime.' },
269      { name:'Events & Promotions', desc:'Drive foot traffic and community engagement.', ideas:['[Event] at [business] on [date] — here\'s what to expect','We\'re doing [special offer] this week only — here\'s the deal','Celebrating [local event] with [something special] at [business]','Why we [host/participate in] [local thing] every year','[Limited time thing] is back — and it won\'t last long'], hook:'Something\'s happening at [business] and you don\'t want to miss it.', cta:'See you at [location] on [date]. Bring a friend.' },
270    ],
271  };
272
273  // Inline the full fallback + series + hooks from SSM
274  const SERIES = {
275    grow:  [ { name:'Weekly Insight Drop', desc:'Every week, one specific insight from your world. Keep it concrete, keep it useful.' }, { name:'Myth vs. Reality', desc:'Monthly myth-busting post targeted at your audience\'s most common misconceptions.' }, { name:'Community Spotlight', desc:'Regular feature on a real person from your community — customer, follower, or collaborator.' } ],
276    sales: [ { name:'Client Story of the Month', desc:'Feature one real client outcome every month. The before, the work, the result.' }, { name:'Objection of the Week', desc:'Pick one real objection you hear and address it with care and specificity.' }, { name:'Value Stack Breakdown', desc:'Monthly breakdown of everything inside your offer. Reinforce perceived value without discounting.' } ],
277    trust: [ { name:'Learning in Public', desc:'Weekly update on something you\'re actively figuring out. Show the messy middle.' }, { name:'Process Deep Dive', desc:'Every two weeks, document the exact process behind something you do.' }, { name:'Transparency Report', desc:'Monthly real numbers post — what happened, what worked, what didn\'t.' } ],
278    educate: [ { name:'101 → 301 Series', desc:'Build a curriculum from beginner to advanced on your core topic.' }, { name:'Myth of the Month', desc:'Monthly explainer that kills one widely-held misconception in your space.' }, { name:'Tool Teardown', desc:'Biweekly breakdown of a relevant tool — what it does, who it\'s for, honest verdict.' } ],
279    hired: [ { name:'Work in Progress', desc:'Document a real project across multiple posts. Show your thinking, not just the output.' }, { name:'Skills Unlocked', desc:'Weekly post on one specific skill — what it is, how you apply it, proof it works.' }, { name:'Industry Read of the Week', desc:'Share and react to one article or trend each week.' } ],
280    launch: [ { name:'Build Log', desc:'Weekly documentation of building your product.' }, { name:'Founding Story Arc', desc:'Multi-part narrative from idea to launch.' }, { name:'Beta Diaries', desc:'Share early user stories as they come in.' } ],
281    event: [ { name:'Road to the Event', desc:'A short countdown series that reveals why the event matters.' }, { name:'Speaker / Feature Spotlight', desc:'Highlight one person, product, or reason to attend at a time.' }, { name:'Last Call Logistics', desc:'A practical series answering time, place, cost, schedule, and what-to-bring questions.' } ],
282  };
283
284  const HOOKS = {
285    bold: ['Stop doing this in {industry}. It\'s costing you.','The {industry} advice everyone repeats that is actually wrong.','Nobody in {industry} wants to say this out loud.','Here is the take that will get me unfollowed:','If you are doing {offer}, you need to hear this first.'],
286    playful: ['Okay but why does nobody talk about this in {industry}??','This is unhinged but hear me out...','POV: you finally figured out {industry}','Friendly reminder that this exists and it is completely free.','Hot take incoming and I am not sorry at all.'],
287    expert: ['After years working in {industry}, here is what I know with confidence:','The research on this is clearer than most people realize.','A framework I have used across many {audience}: it works.','Here is what actually happens in {industry} — not what the courses say.','If you only take one thing from this:'],
288    warm: ['I have been wanting to share this for a while now.','Can we be honest for a second about {industry}?','Something I wish someone had told me earlier:','This community keeps teaching me things. Here is what I mean.','You are not behind. You are just human. Let me explain.'],
289    edgy: ['Everyone is wrong about this in {industry}. Here is proof.','I am about to upset some people and that is fine.','This is what {industry} does not want {audience} to know.','Hard truth: the popular {industry} advice is keeping {audience} stuck.','Controversial? Maybe. But someone had to say it.'],
290    professional: ['Sharing a framework that consistently drives results for {audience}:','Based on our experience across {industry}, here is what we have found:','A strategic insight for {audience} worth bookmarking:','The data does not lie: here is what matters in {industry}.','For {audience} making decisions in {industry}: this is worth your time.'],
291  };
292
293  const QUICK_WINS = {
294    fitness_grow:'Post a real photo from your last workout — no filter, no setup. Caption it with one honest sentence about where you\'re at in your fitness journey. Authenticity outperforms aesthetic every time.',
295    fitness_sales:'Share a 3-sentence client story: the problem they came to you with, what changed, and what they s
295aid to you after. End with "DM me READY if this sounds familiar."',
296    saas_grow:'Share one thing you learned from your last user interview or support ticket that changed how you think about your product. Be specific. Founders and operators love content that shows real product thinking.',
297    saas_sales:'Write a 3-line business case for your product: the problem, what it costs companies to ignore it, and what your product does about it. No features — just outcome language.',
298    coaching_grow:'Give away one piece of genuinely useful content from inside your coaching — a framework, a question you ask clients, a mindset shift. If it\'s useful enough to put in a program, it\'s useful enough to post.',
299    coaching_sales:'Describe your ideal client in 4 sentences: where they are, what they\'ve already tried, what\'s actually blocking them, and where they want to be. The right person will read it and DM you.',
300    creative_grow:'Show a before/after of a project you finished recently — the rough first version and the final delivery. Add 2 sentences about the key creative decision that changed everything.',
301    creative_sales:'Post a project spotlight: the brief you received, the challenge you faced, and the outcome you delivered. Use the client\'s industry so the right buyer sees themselves in the story.',
302    ecommerce_grow:'Post a real UGC photo from a customer — with permission — and in the caption, share what they said about the product. No polish. Real people, real products, real words.',
303    finance_grow:'Break down one financial concept that most of your audience is confused about — in plain language, no jargon. The simpler and more useful, the better the save rate.',
304    localbiz_grow:'Post a "day before we open" photo or video — your team getting ready, the space being prepped. Add one sentence about what you love about your neighborhood. Local and human always wins.',
305  };
306
307  // Trust layer mapping — maps SSM pillar order to PostIQ trust layers
308  const LAYER_MAP = ['awareness', 'credibility', 'awareness', 'action', 'credibility'];
309
310  function getFallback(industry, goal, inp) {
311    const byGoal = {
312      grow: [
313        { name:'Behind The Scenes', desc:`Pull the curtain back on how ${inp.brand} works.`, ideas:[`A day running ${inp.brand}`,`The decision behind ${inp.offer} that surprised us`,`What ${inp.brand} looks like before it's polished`,`The team/person behind ${inp.brand}`,`Why we do things differently in ${inp.industry}`], hook:`Most ${inp.industry} brands only show you the highlight reel.`, cta:`Follow ${inp.brand} for the real version.` },
314        { name:'Education & Value', desc:`Teach ${inp.audience} something genuinely useful every week.`, ideas:[`The ${inp.industry} mistake ${inp.audience} keep making`,`How to get more out of ${inp.offer}`,`What most people misunderstand about ${inp.industry}`,`3 things ${inp.audience} should know before ${inp.offer}`,`The question I get asked most — answered properly`], hook:`${inp.audience} deserve better information about ${inp.industry}.`, cta:`Follow for real, useful content — no fluff.` },
315        { name:'Hot Takes & POV', desc:`Say something real. Generic accounts are invisible.`, ideas:[`The ${inp.industry} advice I'm tired of seeing`,`Unpopular opinion about ${inp.offer}`,`What everyone gets wrong about ${inp.industry}`,`The trend in ${inp.industry} I think is overhyped`,`My honest take on where ${inp.industry} is headed`], hook:`Most ${inp.industry} content sounds the same. Here's a different take.`, cta:`Follow if you want ${inp.industry} content with an actual perspective.` },
316        { name:'Community & Stories', desc:`Build belonging. Feature your audience, celebrate wins.`, ideas:[`A win from someone in our community worth celebrating`,`The DM that made our week`,`Who our ${inp.audience} actually are — real stories`,`What ${inp.audience} have in common (it's not what you think)`,`Shoutout to every ${inp.audience} doing the work right now`], hook:`${inp.brand} isn't just content. It's a community.`, cta:`Tell us your story in the comments. We want to hear it.` },
317        { name:'Progress & Milestones', desc:`Document your journey — the wins, the setbacks, the lessons.`, ideas:[`What ${inp.brand} looked like 1 year ago vs today`,`A mistake we made and what it taught us`,`The milestone we just hit — and what it took`,`What we're working on right now (and why it's hard)`,`The decision that changed everything for ${inp.brand}`], hook:`The best brands show you where they started and how far they've come.`, cta:`Follow ${inp.brand} to see where we go next.` },
318      ],
319      sales: [
320        { name:'Problem Clarity', desc:`Name the exact pain ${inp.offer} solves.`, ideas:[`The real reason ${inp.audience} struggle with ${inp.industry}`,`What happens when ${inp.audience} keep doing ${inp.industry} without a system`,`Signs ${inp.audience} are ready for ${inp.offer}`,`The cost of not solving this problem`,`What ${inp.audience} tell us they tried before ${inp.offer}`], hook:`If you're ${inp.audience} dealing with [problem], this is for you.`, cta:`DM us or visit the link in bio to learn more.` },
321        { name:'Offer Breakdown', desc:`Explain ${inp.offer} with specificity.`, ideas:[`What's inside ${inp.offer} — the full breakdown`,`Who ${inp.offer}
321 is built for (and who it's not)`,`How ${inp.offer} works from start to finish`,`The difference between ${inp.offer} and everything else in ${inp.industry}`,`FAQ about ${inp.offer} — the real questions answered`], hook:`Here's exactly what you get with ${inp.offer} — no surprises.`, cta:`Full details at the link in bio. Questions? DM us.` },
322        { name:'Social Proof', desc:`Let results, reviews, and real customers do the selling.`, ideas:[`A customer result we're proud of — with the full story`,`What ${inp.audience} say after 30 days with ${inp.offer}`,`The review that surprised even us`,`Before & after: how ${inp.offer} changed things for a real customer`,`X customers in — here's what we're hearing`], hook:`Don't take our word for it. Here's what ${inp.audience} say.`, cta:`Ready to add your name to this list? Link in bio.` },
323        { name:'Objection Handling', desc:`Meet your audience where their doubts live.`, ideas:[`"Is ${inp.offer} right for me?" — honest answer`,`The hesitation we hear most often (and why it makes sense)`,`What makes ${inp.offer} worth it, even if you've tried other things`,`For ${inp.audience} who aren't sure they're ready`,`Why now is actually a better time than waiting`], hook:`The reason you haven't bought yet is probably one of these things.`, cta:`Still have questions? DM us. We'll be straight with you.` },
324        { name:'Your Story & Credibility', desc:`Show who's behind ${inp.brand} and why they're qualified.`, ideas:[`Why we built ${inp.offer}`,`What qualifies us to help ${inp.audience} with ${inp.industry}`,`The mistake that led to building ${inp.brand}`,`What we've learned after serving ${inp.audience}`,`Our values — and how they show up in ${inp.offer}`], hook:`There are options in ${inp.industry}. Here's why ${inp.audience} choose us.`, cta:`Follow ${inp.brand} — and reach out when you're ready.` },
325      ],
326    };
327    return byGoal[goal] || byGoal.grow;
328  }
329
330  function fillHook(hook, inp) {
331    return hook.replace(/{industry}/g, inp.industry).replace(/{audience}/g, inp.audience).replace(/{offer}/g, inp.offer).replace(/{brand}/g, inp.brand);
332  }
333
334  function getQuickWin(industry, goal, inp) {
335    const key = `${industry}_${goal}`;
336    if (QUICK_WINS[key]) return QUICK_WINS[key];
337    const fallbacks = {
338      grow: `Post one honest, specific insight from your work in ${inp.industry} this week. Not a tip — a real observation. Something ${inp.audience} rarely hear from anyone in your space.`,
339      sales: `Write three sentences about ${inp.offer}: the problem it solves, who it's for, and one specific outcome a real customer got. Then add your contact or link.`,
340      trust: `Share something you got wrong recently in ${inp.industry} and specifically what you changed because of it. Honest, specific, and humble content is the fastest trust-builder there is.`,
341      educate: `Teach one small, specific, genuinely useful thing to ${inp.audience} about ${inp.industry}. Make it complete — not a teaser.`,
342      hired: `Post a breakdown of a recent project: the brief, your process, and the outcome. Show your actual thinking, not just the final product.`,
343      launch: `Share the "why" behind ${inp.offer} in 3-5 sentences. No features, no specs. Just the problem it solves, who it's for, and why you cared enough to build it.`,
344      event: `Post the clearest possible invite for ${inp.offer}: who should come, what they will get, when it happens, and why it is worth showing up.`,
345    };
346    return fallbacks[goal] || fallbacks.grow;
347  }
348
349  function ssmGetPillars(industry, goal) {
350    return LIBRARY[`${industry}_${goal}`] || null;
351  }
352
353  function ssmBuildStrategy(inp) {
354    let rawPillars = ssmGetPillars(inp.industry, inp.goal);
355    if (!rawPillars) rawPillars = getFallback(inp.industry, inp.goal, inp);
356
357    const tonePool = HOOKS[inp.tone] || HOOKS.bold;
358    const series   = SERIES[inp.goal] || SERIES.grow;
359    const quickWin = getQuickWin(inp.industry, inp.goal, inp);
360
361    // Convert SSM pillars → PostIQ pillar format with trust layer tags
362    const pillars = rawPillars.map((p, i) => ({
363      id:      cpUid(),
364      name:    p.name,
365      promise: p.desc,
366      layer:   LAYER_MAP[i] || 'awareness',
367      seeds:   (p.ideas || []).slice(0, 5).map(s => String(s || '')),
368      tones:   {},
369      hook:    fillHook(tonePool[i] || tonePool[0], inp),
370      cta:     p.cta || '',
371    }));
372
373    if (inp.notes && pillars.length) {
374      pillars[0].seeds = [
375        `Context to work in: ${inp.notes}`,
376        ...pillars[0].seeds
377      ].slice(0, 6);
378    }
379
380    return { pillars, series, quickWin, inp };
381  }
382
383  // ── SSM FORM LOGIC ─────────────────────────────────────────────
384
385  function ssmInit() {
386    const backBtn     = cpQs('ssmBackBtn');
387    const generateBtn = cpQs('ssmGenerateBtn');
388    if (!backBtn || !generateBtn) return;
389
390    backBtn.onclick     = () => cpShowStage('cpStageGate');
391    generateBtn.onclick = ssmRunGenerate;
392  }
393
394  function ssmRunGenerate() {
395    const generationStartTime = Date.now();
396    window.__postiqSsmGenerationStart = generationStartTime;
397    const brand    = (cpQs('ssm-brand')?.value    || '').trim() || 'Your Brand';
398    const industry = cpQs('ssm-industry')?.value  || '';
399    const audience = (cpQs('ssm-audience')?.value || '').trim() || 'your audience';
400    const offer    = (cpQs('ssm-offer')?.value    || '').trim() || 'your offer';
401    const tone     = cpQs('ssm-tone')?.value      || 'bold';
402    const goal     = cpQs('ssm-goal')?.value      || 'grow';
403    const notes    = (cpQs('ssm-notes')?.value    || '').trim();
404
405    if (!industry) { if (typeof showToast === 'function') showToast('Select an industry first', 'error'); return; }
406    if (!goal)     { if (typeof showToast === 'function') showToast('Select a goal first', 'error'); return; }
407
408    const inp = { brand, industry, audience, offer, tone, goal, notes }
409    cpState._ssmInputs = inp;
410    safeTrack(() =>
410 GA4_Pillars.ssmQuestionnaireComplete({ industry, tone, goal }));
411    safeTrack(() => GA4_Pillars.ssmGenerationStarted());
412
413    // Show loading screen
414    const form    = cpQs('ssmForm');
415    const loading = cpQs('ssmLoading');
416    if (form)    form.style.display    = 'none';
417    if (loading) loading.style.display = 'block';
418
419    const steps  = ['Detecting your industry...','Matching goal + industry combo...','Pulling pillar templates...','Generating hooks for your tone...','Finalizing your pillar plan...'];
420    const bar    = cpQs('ssmLoadBar');
421    const label  = cpQs('ssmLoadStep');
422    let i = 0;
423    const iv = setInterval(() => {
424      if (bar)   bar.style.width    = ((i + 1) * 20) + '%';
425      if (label) label.textContent  = steps[i] || 'Almost ready...';
426      i++;
427      if (i >= 5) {
428        clearInterval(iv);
429        setTimeout(() => ssmFinish(inp), 200);
430      }
431    }, 320);
432  }
433
434  function ssmFinish(inp) {
435    const result = ssmBuildStrategy(inp);
436
437    // Set identity from inputs
438    cpState.identity = `${inp.brand} — ${inp.audience} — ${inp.offer}${inp.notes ? ` — Context: ${inp.notes}` : ''}`;
439    cpState.pillars  = result.pillars.map(cpNormalizePillar);
440    safeTrack(() => GA4_Pillars.ssmGenerationComplete(cpState.pillars.length));
441    safeTrack(() => GA4_System.performanceMetric('ssm_generation', Date.now() - (window.__postiqSsmGenerationStart || Date.now())));
442    cpState._ssmInputs = inp;
443
444    cpPersist();
445
446    // Move to builder
447    cpShowStage('cpStageBuilder');
448
449    // Populate identity field
450    const bi = cpQs('cpBuilderIdentity');
451    if (bi) bi.value = cpState.identity;
452
453    // Render pillars in builder
454    cpRenderPillars();
455
456    // Keep the quick-win starter inside the pillar plan. Do not auto-insert into Compose.
457    cpState._latestGeneratedStarter = result.quickWin || '';
458
459    // Replace any old pillar-generated series templates so reset/new generation stays clean.
460    cpClearGeneratedTemplates();
461
462    // Save recurring series as PostIQ templates
463    ssmSaveSeriesAsTemplates(result.series, inp);
464
465    if (typeof showToast === 'function') {
466      showToast('Pillar plan generated — review your starters below', 'success');
467    }
468
469    // Restore form for next time
470    const form    = cpQs('ssmForm');
471    const loading = cpQs('ssmLoading');
472    const bar     = cpQs('ssmLoadBar');
473    if (form)    form.style.display    = 'block';
474    if (loading) loading.style.display = 'none';
475    if (bar)     bar.style.width       = '0%';
476  }
477
478  function ssmSendQuickWin(text) {
479    const editor = document.getElementById('composerEditor');
480    if (!editor) return;
481    const existing = editor.innerText.trim();
482    editor.innerText = existing ? `${existing}\n\n${text}` : text;
483    editor.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
484  }
485
486  function ssmSaveSeriesAsTemplates(series, inp) {
487    if (!Array.isArray(series) || !series.length) return;
488    const now = new Date().toISOString();
489    const newTpls = series.map(s => ({
490      id:        cpUid(),
491      title:     s.name,
492      type:      'Hooks',
493      platform:  'Universal',
494      tags:      [CP_TEMPLATE_TAG, 'pillar-plan', 'series', inp.industry, inp.goal, inp.notes ? 'custom-context' : ''].filter(Boolean),
495      body:      inp.notes ? `${s.desc}\n\nContext to consider: ${inp.notes}` : s.desc,
496      createdAt: now,
497      updatedAt: now,
498    }));
499    if (typeof state !== 'undefined' && Array.isArray(state.templates)) {
500      state.templates = [...newTpls, ...state.templates];
501      if (typeof persistTemplates === 'function') persistTemplates();
502      if (typeof renderTemplates   === 'function') renderTemplates();
503    }
504  }
505
506  // ── BUILDER RENDER ─────────────────────────────────────────────
507
508  function cpRenderPillars() {
509    const list = cpQs('cpPillarsList');
510    if (!list) return;
511    cpState.pillars = cpState.pillars.map(cpNormalizePillar);
512    list.innerHTML = '';
513    cpState.pillars.forEach((pillar, pi) => {
514      const card  = document.createElement('div');
515      card.className  = 'cp-pillar-card';
516      card.dataset.pid = pillar.id;
517      const usage = cpUsageFor(pillar.id);
518      card.innerHTML = `<div class="cp-pillar-head"><div class="cp-pillar-tab" style="background:${pillarColor(pi)};"></div><div class="cp-pillar-head-inner"><div class="cp-pillar-inputs"><input class="cp-pillar-name" data-field="name" value="${cpEsc(pillar.name)}" placeholder="Pillar name" /><input class="cp-pillar-promise" data-field="promise" value="${cpEsc(pillar.promise)}" placeholder="The recurring promise this pillar makes…" /></div></div><div class="cp-pillar-head-right"><select class="cp-layer-select" data-field="layer"><option value="" ${pillar.layer ? '' : 'selected'}>
518Tag layer…</option><option value="awareness" ${pillar.layer==='awareness'?'selected':''}>👁 Awareness</option><option value="credibility" ${pillar.layer==='credibility'?'selected':''}>🎓 Credibility</option><option value="action" ${pillar.layer==='action'?'selected':''}>🛒 Action</option></select><span class="cp-usage-badge ${usage > 0 ? 'used' : ''}">${usage > 0 ? `${usage} starter${usage > 1 ? 's' : ''}` : 'unused'}</span></div></div>` +
519        (pillar.hook ? `<div style="font-size:11px;font-family:'DM Mono',monospace;color:var(--brand);background:var(--brand-dim);border-top:1px solid var(--brand-glow);padding:8px 13px;line-height:1.45;">Hook: "${cpEsc(pillar.hook)}"</div>` : '') +
520        `<div class="cp-seeds" data-seeds-for="${pillar.id}">${pillar.seeds.map((seed, si) => cpSeedRowHtml(pillar, si, seed)).join('')}</div><div class="cp-pillar-footer"><button class="btn sm ghost" data-action="add-seed" type="button" style="font-size:11px;height:26px;padding:0 10px;">+ Add seed idea</button><button class="btn sm ghost" data-action="remove-pillar" type="button" style="font-size:11px;height:26px;padding:0 8px;color:var(--subtle);">Remove pillar</button></div>`;
521
522      card.querySelectorAll('[data-field="name"],[data-field="promise"]').forEach(inp => {
523        inp.addEventListener('input', () =>
523 { pillar[inp.dataset.field] = inp.value; cpPersist(); cpUpdateHealth(); });
524      });
525      card.querySelector('[data-field="layer"]').addEventListener('change', e => { pillar.layer = e.target.value; cpPersist(); cpUpdateLayerCheck(); });
526      card.querySelector('[data-action="add-seed"]').addEventListener('click', () => { pillar.seeds.push(''); cpRenderPillars(); cpPersist(); });
527      card.querySelector('[data-action="remove-pillar"]').addEventListener('click', () => {
528        if (!confirm('Remove this pillar?')) return;
529        cpState.pillars = cpState.pillars.filter(p => p.id !== pillar.id);
530        cpRenderPillars(); cpPersist(); cpUpdateHealth();
531        if (typeof showToast === 'function') showToast('Pillar removed');
532      });
533      cpBindSeeds(card, pillar);
534      list.appendChild(card);
535    });
536    cpUpdateHealth();
537    cpUpdateLayerCheck();
538  }
539
540  function cpSeedRowHtml(pillar, si, seed) {
541    const tone = (pillar.tones && pillar.tones[si]) || 'Practical';
542    return `<div class="cp-seed-row" data-si="${si}"><div class="cp-seed-idx">${si + 1}</div><div class="cp-seed-body"><input class="cp-seed-input" value="${cpEsc(seed)}" placeholder="A specific, real idea you could write about…" /><select class="cp-tone-select" data-tone="${si}">${TONES.map(t => `<option value="${t}" ${t === tone ? 'selected' : ''}>${t}</option>`).join('')}</select></div><div class="cp-seed-actions"><button class="cp-seed-btn go" data-action="start" type="button">Start</button><button class="cp-seed-btn del" data-action="del" type="button" title="Remove">×</button></div></div>`;
543  }
544
545  function cpBindSeeds(card, pillar) {
546    const wrap = card.querySelector('[data-seeds-for]');
547    wrap.querySelectorAll('.cp-seed-input').forEach((inp, si) => {
548      inp.addEventListener('input', () => { pillar.seeds[si] = inp.value; cpPersist(); cpUpdateHealth(); });
549    });
550    wrap.querySelectorAll('[data-tone]').forEach(sel => {
551      sel.addEventListener('change', e => { if (!pillar.tones) pillar.tones = {}; pillar.tones[+e.target.dataset.tone] = e.target.value; cpPersist(); });
552    });
553    wrap.querySelectorAll('[data-action="start"]').forEach((btn, si) => {
554      btn.addEventListener('click', () => {
555        const seed = (pillar.seeds[si] || '').trim();
556        if (!seed) { if (typeof showToast === 'function') showToast('Add a seed idea first', 'error'); return; }
557        const tone = (pillar.tones && pillar.tones[si]) || 'Practical';
558        safeTrack(() => GA4_Pillars.seedIdeaStarted());
559        safeTrack(() => GA4_Composer.composerOpened('pillar_seed'));
560        cpSendToComposer(cpBuildStarter(pillar, seed, tone));
561        cpBumpUsage(pillar.id);
562        cpRenderPillars();
563        if (typeof showToast === 'function') showToast('Starter added to Compose', 'success');
564      });
565    });
566    wrap.querySelectorAll('[data-action="del"]').forEach((btn, si) => {
567      btn.addEventListener('click', () => {
568        if (pillar.seeds.length <= 1) pillar.seeds = [''];
569        else pillar.seeds.splice(si, 1);
570        cpRenderPillars(); cpPersist();
571      });
572    });
573  }
574
575  function cpBuildStarter(pillar, seed, tone) {
576    const identity  = (cpState.identity || '').trim();
577    const voiceLine = identity ? `Voice: ${identity}\n\n` : '';
578    const openers   = { Practical:'Here\'s the clearest way I can explain this:', Story:'Here\'s a moment that changed how I think about this:', Contrarian:'Unpopular take:', Question:'Quick question for you:' };
579    const hookLine  = pillar.hook ? `\nHook: "${pillar.hook}"\n` : '';
580    const ctaLine   = pillar.cta  ? `\nCTA: ${pillar.cta}\n`    : '';
581    return `${voiceLine}Pillar: ${pillar.name} — ${pillar.promise}\nTopic: ${seed}${hookLine}${ctaLine}\nPost starter: ${openers[tone] || openers.Practical}`;
582  }
583
584  function cpSendToComposer(text) {
585    const editor = document.getElementById('composerEditor');
586    if (editor) {
587      const existing = editor.innerText.trim();
588      editor.innerText = existing ? `${existing}\n\n${text}` : text;
589      editor.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
590      updateComposerClearButtonVisibility();
591      if (typeof window.activateView === 'function') window.activateView('composerView');
592      editor.focus();
593      return true;
594    }
595    return false;
596  }
597
598  function cpUpdateHealth() {
599    const total = cpState.pillars.length;
600    const seeds = cpState.pillars.reduce((a, p) => a + p.seeds.filter(s => String(s || '').trim()).length, 0);
601    const used  = cpTotalUsage();
602    const hP = cpQs('cpHealthPillars'), hS = cpQs('cpHealthSeeds'), hU = cpQs('cpHealthUsage');
603    if (hP) hP.textContent = total;
604    if (hS) hS.textContent = seeds;
605    if (hU) hU.textContent = used;
606    const score = Math.min(100, (Math.min(total, 5) / 5) * 40 + (Math.min(seeds, 15) / 15) * 40 + (Math.min(used, 5) / 5) * 20);
607    const bar = cpQs('cpHealthBar');
608    if (bar) { bar.style.width = `${score}%`; bar.className = `cp-health-fill${score >= 70 ? ' good' : score >= 40 ? ' warn' : ''}`; }
609    const msgs = [[90,'Pillar system firing on all cylinders.'],[70,'Strong foundation. Keep creating starters.'],[50,'Looking solid. Start from a few seeds.'],[20,'Good start — add more seed ideas.'],[0,'Add your pillars to start.']];
610    const msg = msgs.find(m => score >= m[0]);
611    const el = cpQs('cpHealthMsg'); if (el) el.textContent = (msg || msgs[msgs.length - 1])[1];
612  }
613
614  function cpUpdateLayerCheck() {
615    const has = l => cpState.pillars.some(p => p.layer === l);
616    const fmt = l => has(l) ? '<span style="color:var(--green);font-weight:700;">✓</span>' : '<span style="color:var(--border2);">—</span>';
617    const a = cpQs('cpLayerA'), c = cpQs('cpLayerC'), x = cpQs('cpLayerX');
618    if (a) a.innerHTML = fmt('awareness');
619    if (c) c.innerHTML = fmt('credibility');
620    if (x) x.innerHTML = fmt('action');
621  }
622
623  function cpRenderCompact() {
624    const wrap = document.getElementById('composerPillarsCompact');
625    if (!wrap) return;
626    const pillars = cpState.pillars.slice(0, 4);
627    if (!pillars.length) {
628      wrap.innerHTML = "<div style=\"font-size:12px;color:var(--subtle);padding:8px 0;font-family:'DM Mono',monospace;\">Build your pillars in the Ideas tab to see quick starters here.</div>";
629      return;
630    }
631    wrap.innerHTML = '';
632    const usage = cpGetUsage();
633    pillars.forEach(pillar => {
634      const card  = document.createElement('div');
635      card.className = 'cp-compact-card';
636      const seed  = pillar.seeds.find(s => String(s || '').trim()) || '';
637      const count = usage[pillar.id] || 0;
638      const unusedBadge  = count === 0 ? `<span style="font-size:10px;font-family:'DM Mono',monospace;color:var(--subtle);border:1px solid var(--border);padding:1px 6px;border-radius:999px;margin-left:6px;">unused</span>` : '';
639      const draftedLabel = count > 0 ? `${count} starter${count > 1 ? 's' : ''} created` : 'Not used yet';
640      const seedHtml = seed ? `<div class="cp-compact-seed">${cpEsc(seed)}</div><div class="cp-compact-row"><span style="font-size:10px;font-family:'DM Mono',monospace;color:var(--subtle);">${draftedLabel}</span><button class="btn sm primary" type="button" style="height:24px;font-size:11px;padding:0 8px;" data-start="${cpEsc(pillar.id)}">Start</button></div>` : '';
641      card.innerHTML = `<div class="cp-compact-name">${cpEsc(pillar.name)}${unusedBadge}</div>${seedHtml}`;
642      const btn = card.querySelector('[data-start]');
643      if (btn) btn.addEventListener('click', e => {
644        e.stopPropagation();
645        const p = cpState.pillars.find(item => item.id === pillar.id);
646        if (p) { cpSendToComposer(cpBuildStarter(p, seed, 'Practical')); cpBumpUsage(p.id); cpRenderCompact(); if (typeof showToast === 'function') showToast('Starter added to Compose', 'success'); }
647      });
648      wrap.appendChild(card);
649    });
650  }
651
652  function init() {
653    const hasData = cpLoad();
654
655    // Gate: SSM path
656    const gN = cpQs('cpGateNew');
657    if (gN) gN.addEventListener('click', () => { safeTrack(() => GA4_Pillars.ssmQuestionnaireStarted()); cpShowStage('cpStageJourney'); ssmInit(); });
658
659    // Gate: manual path
660    const gE = cpQs('cpGateExperienced');
661    if (gE) gE.addEventListener('click', () => {
662      if (!cpState.pillars.length) {
663        // Seed default pillars
664        cpState.pillars = ['Behind The Scenes','Education & Value','Hot Takes & POV','Community & Stories','Your Offer & Story'].map((name, i) => ({
665          id: cpUid(), name, promise: 'Define the recurring promise this pillar makes to your audience.', layer: LAYER_MAP[i] || 'awareness', seeds: [''], tones: {}, hook: '', cta: '',
666        }));
667      }
668      cpShowStage('cpStageBuilder');
669      const bi = cpQs('cpBuilderIdentity'); if (bi) bi.value = cpState.identity || '';
670      cpRenderPillars(); cpPersist();
671    });
672
673    // Builder UI
674    const bi = cpQs('cpBuilderIdentity');
675    if (bi) bi.addEventListener('input', e => { cpState.identity = e.target.value; cpPersist(); });
676
677    const ap = cpQs('cpAddPillarBtn');
678    if (ap) ap.addEventListener('click', () => {
679      const newPillar = { id: cpUid(), name: 'New Pillar', promise: 'The recurring promise this pillar makes…', layer: '', seeds: [''], tones: {}, hook: '', cta: '' };
680      cpState.pillars.push(newPillar);
681      safeTrack(() => GA4_Pillars.pillarCreated({ icon: 'none', seeds: newPillar.seeds.length, trustLayer: false }));
682      cpRenderPillars(); cpPersist(); if (typeof showToast === 'function') showToast('Pillar added');
683    });
684
685    const rb = cpQs('cpRestartBtn');
686    if (rb) rb.addEventListener('click', () => {
687      if (!confirm('Start over? This clears your pillars, starter counts, and pillar-generated series templates.')) return;
688      const removedTemplates = cpClearGeneratedTemplates();
689      cpState.pillars = [];
690      cpState.identity = '';
691      cpState._ssmInputs = null;
692      cpState._latestGeneratedStarter = '';
693      try { localStorage.removeItem(CP_KEY); localStorage.removeItem(USAGE_KEY); } catch {}
694      const bi = cpQs('cpBuilderIdentity'); if (bi) bi.value = '';
695      ['ssm-brand','ssm-industry','ssm-audience','ssm-offer','ssm-tone','ssm-goal','ssm-notes'].forEach(id => { const el = cpQs(id); if (el) el.value = ''; });
696      cpRenderPillars();
697      cpShowStage('cpStageGate');
698      cpRenderCompact();
699      if (typeof showToast === 'function') showToast(`Content pillars reset${removedTemplates ? ` — removed ${removedTemplates} generated template${removedTemplates > 1 ? 's' : ''}` : ''}.`, 'success');
700    });
701
702    const eb = cpQs('cpExportBtn');
703    if (eb) eb.addEventListener('click', () => {
704      const lines = ['# My Content Pillars\n', `Voice: ${cpState.identity || '(not set)'}\n`];
705      cpState.pillars.forEach(p => {
706        lines.push(`\n## ${p.name}`, `Promise: ${p.promise}`, `Layer: ${p.layer || 'untagged'}`, p.hook ? `Hook: "${p.hook}"` : '', p.cta ? `CTA: ${p.cta}` : '');
707        p.seeds.filter(s => String(s || '').trim()).forEach((s, i) => lines.push(`${i + 1}. ${s}`));
708      });
709      const a = document.createElement('a');
710      a.href     = URL.createObjectURL(new Blob([lines.filter(Boolean).join('\n')], { type: 'text/plain' }));
711      a.download = 'content-pillars.txt';
712      a.click();
713      URL.revokeObjectURL(a.href);
714      if (typeof showToast === 'function') showToast('Exported', 'success');
715    });
716
717    if (hasData) {
718      cpShowStage('cpStageBuilder');
719      const bi2 = cpQs('cpBuilderIdentity'); if (bi2) bi2.value = cpState.identity || '';
720      cpRenderPillars();
721    }
722    cpRenderCompact();
723  }
724
725  return {
726    init,
727    renderCompact: cpRenderCompact,
728    renderDraftCompact: cpRenderCompact,
729    getData: () => ({ identity: cpState.identity, pillars: cpState.pillars }),
730    insertStarter: (pillar, seed, dateLabel) => {
731      const normalized = cpNormalizePillar(pillar || { name: 'Pillar', promise: 'Post starter' });
732      const topic      = String(seed || '').trim();
733      if (!topic) return false;
734      const starter = `${dateLabel ? `Date: ${dateLabel}\n` : ''}${cpBuildStarter(normalized, topic, 'Practical')}`;
735      const wrote = cpSendToComposer(starter);
736      if (wrote && normalized.id) cpBumpUsage(normalized.id);
737      cpRenderCompact();
738      return wrote;
739    },
740  };
741})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.