"
+ for text, color in items
+ )
+
+ def _implication(text, color):
+ return (
+ f"
"
+ f"{text}
"
+ )
+
+ # ── Attention bullets ─────────────────────────────────────────────────────
+ face_txt = (
+ f"{vf['face_count']} face(s) — will trigger an orienting response and accelerate processing"
+ if vf["face_count"] > 0
+ else "No face — will not trigger the fastest biological attention mechanism available"
+ )
+ contrast_txt = (
+ f"Contrast {vf['contrast_score']:.0f}/100 — " +
+ ("will pass the visual salience gate in a busy feed" if vf["contrast_score"] >= 60
+ else "will not pass the visual salience test — disappears in a competitive feed")
+ )
+ obj_txt = (
+ f"{vf['object_count']} object(s) — " +
+ ("clean composition — focus will land on the primary subject" if vf["object_count"] <= 4
+ else "visual attention will fragment across elements — no single thing will dominate")
+ )
+ if attn > 60:
+ attn_impl = _implication("→ Will interrupt scrolling and trigger processing in cold audiences", a_color)
+ elif attn >= 30:
+ attn_impl = _implication("→ Will not reliably stop cold audiences — loses the first cognitive gate before the message is seen", a_color)
+ else:
+ attn_impl = _implication("→ Will be skipped at near-zero processing depth — the brain never engages with the content", a_color)
+
+ # ── Memory bullets ────────────────────────────────────────────────────────
+ text_pct = vf["text_density"] * 100
+ if text_pct < 5:
+ text_mem_txt = f"Text coverage {text_pct:.0f}% — no verbal anchor; the visual alone will not survive working memory"
+ elif text_pct <= 25:
+ text_mem_txt = f"Text coverage {text_pct:.0f}% — dual-coding range; both visual and verbal channels will encode simultaneously"
+ else:
+ text_mem_txt = f"Text coverage {text_pct:.0f}% — verbal overload; neither channel will encode cleanly, recall will suffer"
+
+ mem_obj_txt = (
+ f"{vf['object_count']} element(s) — " +
+ ("sparse enough to form a dominant memory trace" if vf["object_count"] <= 4
+ else "too many competing elements — nothing will be remembered as the primary object")
+ )
+ if mem > 70:
+ mem_impl = _implication("→ Will be recognised at point of purchase — brand memory survives the gap between exposure and decision", m_color)
+ elif mem >= 40:
+ mem_impl = _implication("→ Will not encode on a single exposure — requires 6–8 impressions to build reliable recall, increasing effective CPM", m_color)
+ else:
+ mem_impl = _implication("→ Will leave no trace — viewers will not recall the brand or message within minutes of scrolling past", m_color)
+
+ # ── Emotion bullets ───────────────────────────────────────────────────────
+ val_norm = (val + 1) / 2 * 100
+ face_emo = (
+ f"{vf['face_count']} face(s) — will drive affiliative warmth and accelerate positive affect"
+ if vf["face_count"] > 0
+ else "No face — will forfeit the strongest emotion driver available in still imagery"
+ )
+ palette_hex = vf["dominant_colors"][0] if vf["dominant_colors"] else "#888888"
+ r_hex = int(palette_hex[1:3], 16) if len(palette_hex) == 7 else 128
+ b_hex = int(palette_hex[5:7], 16) if len(palette_hex) == 7 else 128
+ v_hex = (int(palette_hex[1:3], 16) + int(palette_hex[3:5], 16) + b_hex) // 3
+ if r_hex > b_hex + 30:
+ pal_txt = f"Warm palette ({palette_hex}) — will activate approach affect and lift emotional valence"
+ elif b_hex > r_hex + 20:
+ pal_txt = f"Cool palette ({palette_hex}) — will signal credibility but will not drive arousal or urgency"
+ elif v_hex < 80:
+ pal_txt = f"Dark palette ({palette_hex}) — will project premium cues but will suppress warmth and affinity"
+ else:
+ pal_txt = f"Neutral palette ({palette_hex}) — will generate no emotional signal — a missed valence opportunity"
+
+ if val > 0.1:
+ val_impl = _implication("→ Will build positive brand associations with repeated exposure — emotion compounds into long-term affinity", v_color)
+ elif val > -0.1:
+ val_impl = _implication("→ Will generate no emotional memory — neutral affect means the brand will not benefit from the exposure beyond the impression", v_color)
+ else:
+ val_impl = _implication("→ Will silently erode brand equity — negative affect embeds subconsciously and accumulates across each impression served", v_color)
+
+ # ── Cognitive Load bullets ────────────────────────────────────────────────
+ vis_complexity = (
+ f"{vf['object_count']} visual elements — " +
+ ("within comfortable processing capacity — brain will not fragment focus" if vf["object_count"] <= 5
+ else "exceeds working memory capacity — brain will abandon full processing")
+ )
+ text_load = (
+ f"Text at {text_pct:.0f}% coverage — " +
+ ("low verbal demand — will not compete with visual processing" if text_pct <= 15
+ else "high verbal demand — audience will skim or skip rather than read")
+ )
+ if cl == "Low":
+ cl_impl = _implication("→ Will process in under 2 seconds — fits feed, display, and OOH without cognitive friction", cl_color)
+ elif cl == "Medium":
+ cl_impl = _implication("→ Will underperform in fast-scroll formats — requires dwell time the audience will not give", cl_color)
+ else:
+ cl_impl = _implication("→ Will saturate working memory before the message lands — high load kills attention, memory, and emotion simultaneously", cl_color)
+
+ # ── Build HTML ────────────────────────────────────────────────────────────
+ def _block(icon, label, score_val, score_lbl, color, bullets_html, impl_html, sig_color=None):
+ lbl_color = sig_color if sig_color else color
+ return (
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+
+def _quick_read(
+ cpci: float,
+ attn: int,
+ mem: int,
+ val: float,
+ cl: str,
+ vf: dict,
+ use_case: str,
+) -> None:
+ """
+ Renders the 🧠 What This Means (Quick Read) banner.
+ Three plain-English lines: performance prediction, core issue, immediate fix.
+ No technical language. Max one sentence each.
+ """
+
+ # ── Use-case context plain name ────────────────────────────────────────────
+ ctx = {
+ "FMCG Branding": "brand awareness campaigns",
+ "Performance Marketing": "paid social and search ads",
+ "Retail Media": "retail and in-store placements",
+ }.get(use_case, "this campaign type")
+
+ # ── Line 1 — Performance Prediction ───────────────────────────────────────
+ if cpci >= 70:
+ prediction = f"This creative will earn its media spend in {ctx} — the cognitive gates are clear."
+ elif cpci >= 55:
+ prediction = f"This creative will generate impressions in {ctx} but will not convert efficiently — one weak signal is costing reach."
+ elif cpci >= 40:
+ prediction = f"This creative will struggle in cold audiences for {ctx} — it may survive retargeting but will not scale profitably."
+ else:
+ prediction = f"This creative will underperform regardless of budget — the cognitive barriers are too significant to overcome with spend."
+
+ # ── Line 2 — Core Issue ───────────────────────────────────────────────────
+ attn_gap = max(0, 60 - attn)
+ mem_gap = max(0, 70 - mem)
+ val_gap = max(0, 0.1 - val) * 100
+ load_gap = 25 if cl == "High" else (10 if cl == "Medium" else 0)
+
+ worst = max(attn_gap, mem_gap, val_gap, load_gap)
+
+ if worst == 0:
+ issue = "All signals are above threshold — no single dimension is holding this back."
+ elif worst == load_gap and cl == "High":
+ issue = "Visual overload will cause viewers to disengage before the message registers — the brain cannot parse this quickly enough in a feed."
+ elif worst == attn_gap:
+ if vf.get("face_count", 0) == 0:
+ issue = "This creative will fail to trigger an orienting response — without a face or salient focal point, the brain will not interrupt scrolling to process it."
+ elif vf.get("contrast_score", 50) < 45:
+ issue = "This creative will disappear in a busy feed — insufficient contrast means it fails the first visual gate before content is even assessed."
+ else:
+ issue = "This creative will be processed at low depth — no dominant focal point means the brain distributes attention thinly and nothing is prioritised."
+ elif worst == mem_gap:
+ if vf.get("text_density", 0) > 0.25:
+ issue = "This creative will be remembered only if repeatedly exposed — text overload prevents dual-coding, so neither the visual nor verbal message encodes cleanly."
+ elif vf.get("text_density", 0) < 0.05:
+ issue = "This creative will be seen but not recalled — without a verbal anchor, the visual alone will not survive beyond a few seconds in working memory."
+ else:
+ issue = "This creative will not build brand recall efficiently — too many competing elements mean nothing dominates the memory trace."
+ elif worst == val_gap:
+ if vf.get("face_count", 0) == 0:
+ issue = "This creative will generate neutral-to-negative affect on each exposure — without a human face, the palette alone cannot drive positive brand association."
+ else:
+ issue = "This creative will subtly undermine brand affinity over time — the colour palette is triggering mild avoidance without the viewer being aware of it."
+ elif worst == load_gap:
+ issue = "This creative demands more cognitive effort than a scrolling audience will commit — the message will not land in fast-feed placements."
+ else:
+ issue = "Attention, memory, and emotion are pulling in different directions — the creative is sending mixed signals that dilute overall cognitive impact."
+
+ # ── Line 3 — Immediate Fix ────────────────────────────────────────────────
+ if vf.get("face_count", 0) == 0 and attn_gap > 15:
+ fix = "Introduce a human face as the dominant visual element — it is the single fastest mechanism for triggering an orienting response in feed."
+ elif vf.get("contrast_score", 50) < 45 and attn_gap > 10:
+ fix = "Increase foreground-to-background contrast significantly — the creative needs to pass the visual salience test before any other signal matters."
+ elif vf.get("object_count", 0) > 7 and (worst == load_gap or worst == mem_gap):
+ fix = "Eliminate all secondary visual elements and commit to a single hero — cognitive load is suppressing every other signal."
+ elif vf.get("text_density", 0) > 0.28:
+ fix = "Reduce copy to a single declarative line under six words — audiences process far less text than advertisers write."
+ elif vf.get("text_density", 0) < 0.04 and mem_gap > 15:
+ fix = "Add a 4–6 word brand or message line — the verbal channel is completely unused, which is costing you recall without any benefit."
+ elif val < -0.05 and vf.get("face_count", 0) == 0:
+ fix = "Add a person with a natural, warm expression — emotional valence cannot be fixed with colour alone at this deficit."
+ elif val < -0.05:
+ fix = "Replace the dominant cool or dark tones with warmer equivalents — the palette is the primary driver of the negative valence reading."
+ elif attn_gap > 10:
+ fix = "Scale up the primary subject and increase its contrast against the background — give the eye an unmissable landing point."
+ elif mem_gap > 15:
+ fix = "Reduce the composition to one dominant image and one short message — simplicity is the only reliable route to single-exposure recall."
+ else:
+ fix = "Test a version with a human face replacing the current hero element — it will lift both attention and emotional valence simultaneously."
+
+ # ── Render ─────────────────────────────────────────────────────────────────
+ pcolor = "#22C55E" if cpci >= 70 else ("#F59E0B" if cpci >= 40 else "#EF4444")
+
+ st.markdown(f"""
+
+
🧠 What This Means
+
+ 01
+ Performance
+ {prediction}
+
+
+ 02
+ Core Issue
+ {issue}
+
+
+ 03
+ Fix First
+ {fix}
+
+
""", unsafe_allow_html=True)
+
+
+# ── Why This Matters hero block ───────────────────────────────────────────────
+
+def _why_this_matters(
+ cpci: float,
+ attn: int,
+ mem: int,
+ val: float,
+ cl: str,
+ use_case: str,
+) -> None:
+ """
+ Full-width emotional impact statement placed directly after the CPCi number.
+ Goal: make the user FEEL the score, not just read it.
+ """
+
+ # ── Tier-adaptive copy ────────────────────────────────────────────────────
+ if cpci < 30:
+ accent = "#EF4444"
+ bg = "rgba(239,68,68,0.07)"
+ border_col = "#7F1D1D"
+ icon = "🚨"
+ bold_line = (
+ "This creative is likely wasting 50–70% of your media budget "
+ "due to critically low cognitive engagement."
+ )
+ support = (
+ f"At CPCi {cpci}, the brain won't reliably process this message. "
+ "Impressions served are impressions lost — "
+ "no targeting strategy can compensate for a creative the brain ignores."
+ )
+
+ elif cpci < 40:
+ accent = "#EF4444"
+ bg = "rgba(239,68,68,0.06)"
+ border_col = "#7F1D1D"
+ icon = "🚨"
+ bold_line = (
+ "This creative is likely wasting 40–55% of your media budget "
+ "due to low cognitive engagement."
+ )
+ support = (
+ f"At CPCi {cpci}, most impressions will not cognitively register. "
+ "The brand message won't be encoded — and won't be recalled at point of purchase."
+ )
+
+ elif cpci < 55:
+ accent = "#F59E0B"
+ bg = "rgba(245,158,11,0.06)"
+ border_col = "#78350F"
+ icon = "⚠️"
+ bold_line = "This creative is leaving 25–40% of its potential media efficiency on the table."
+ support = (
+ f"At CPCi {cpci}, you have signal — but the brain is only partially engaged. "
+ "You'll need higher frequency to achieve the same recall as a top-quartile creative "
+ "at half the impressions."
+ )
+
+ elif cpci < 70:
+ accent = "#F59E0B"
+ bg = "rgba(245,158,11,0.05)"
+ border_col = "#78350F"
+ icon = "⚠️"
+ bold_line = (
+ "This creative is performing adequately — but there is a gap before it earns "
+ "top-quartile media efficiency."
+ )
+ support = (
+ f"At CPCi {cpci}, you're in the average band. A single targeted fix — "
+ "attention, memory, or load — could close the gap and meaningfully reduce "
+ "your effective cost per recalled impression."
+ )
+
+ else:
+ accent = "#22C55E"
+ bg = "rgba(34,197,94,0.06)"
+ border_col = "#14532D"
+ icon = "✅"
+ bold_line = (
+ "This creative earns every impression — the brain is processing, "
+ "encoding, and responding at above-average efficiency."
+ )
+ support = (
+ f"At CPCi {cpci}, this is top-quartile cognitive performance. "
+ "Your media spend is working at maximum brain-level impact. "
+ "Scale with confidence."
+ )
+
+ # ── Media cost multiplier (uses mem, the function parameter) ─────────────
+ effective_memory = max(mem, 10)
+ multiplier = round(70 / effective_memory, 1)
+
+ if mem < 70:
+ mult_color = (
+ "#EF4444" if multiplier > 1.8 else
+ "#F59E0B" if multiplier >= 1.2 else
+ "#22C55E"
+ )
+ media_cost_html = (
+ f"
"
+ f"🔥 This creative will cost you {multiplier}× more media "
+ f"to achieve the same recall."
+ f"
"
+ )
+ waste_html = (
+ "
"
+ "Equivalent to wasting ~35–50% of your media budget on ineffective impressions."
+ "
",
+ unsafe_allow_html=True,
+ )
+
+ # ── Detail expander ───────────────────────────────────────────────────────
+ with st.expander("View media efficiency breakdown"):
+ rows_html = ""
+ for label, value, color in efficiency_bullets:
+ rows_html += (
+ f"
"
+ f"
{label}
"
+ f"
{value}
"
+ f"
"
+ )
+ st.markdown(
+ f"
"
+ f"{sub}
"
+ f"
"
+ f"Expected Media Efficiency Impact
"
+ f"{rows_html}",
+ unsafe_allow_html=True,
+ )
+
+ st.markdown("", unsafe_allow_html=True)
+
+
+# ── Creative Optimization Scenario ───────────────────────────────────────────
+
+def _compute_scenarios(
+ cpci: float, attn: int, mem: int, val: float, cl: str, vf: dict, use_case: str
+) -> list:
+ """
+ Compute up to 4 independent CPCi lift scenarios.
+ Each uses the real CPCi formula with the actual use-case weights.
+ Returns a list of dicts sorted by lift descending.
+ """
+ w = USE_CASES[use_case]["weights"]
+ val_norm = (val + 1) / 2 * 100 # [-1,+1] → [0,100]
+ load_pen = LOAD_PENALTY_MAP.get(cl, 0)
+
+ def _new_cpci(new_attn=attn, new_mem=mem, new_val_norm=val_norm, new_load_pen=load_pen):
+ raw = (w["attention"]*new_attn + w["memory"]*new_mem
+ + w["emotion"]*new_val_norm + new_load_pen)
+ return round(max(0.0, min(100.0, raw)), 1)
+
+ face_count = vf.get("face_count", 0)
+ contrast = vf.get("contrast_score", 50)
+ obj_count = vf.get("object_count", 4)
+ text_pct = vf.get("text_density", 0.1) * 100
+
+ scenarios = []
+
+ # ── Scenario A: attention improvement ────────────────────────────────────
+ attn_gap = max(0, 65 - attn)
+ if attn_gap >= 8:
+ lift_pts = min(attn_gap, 22)
+ new_a = min(attn + lift_pts, 100)
+ projected = _new_cpci(new_attn=new_a)
+ delta = round(projected - cpci, 1)
+ if delta >= 2:
+ if face_count == 0:
+ action = "Add a human face as the primary visual subject"
+ rationale = "Face presence triggers the brain's fastest attention mechanism — an orienting response in under 13ms."
+ elif contrast < 45:
+ action = "Increase contrast ratio to at least 4.5:1 against the background"
+ rationale = "Pre-attentive salience is driven by contrast. Below threshold, the creative disappears in a competitive feed."
+ else:
+ action = "Simplify to one dominant focal point — remove competing visual elements"
+ rationale = "Fragmented compositions split visual attention. A single dominant subject maximises stopping power."
+ scenarios.append({
+ "signal": "Attention",
+ "sig_color": "#3B82F6",
+ "action": action,
+ "rationale": rationale,
+ "method": "improving attention signal",
+ "from_signal": attn, "to_signal": new_a,
+ "from_cpci": cpci, "to_cpci": projected,
+ "lift": delta,
+ })
+
+ # ── Scenario B: memory improvement ────────────────────────────────────────
+ mem_gap = max(0, 70 - mem)
+ if mem_gap >= 8:
+ lift_pts = min(mem_gap, 22)
+ new_m = min(mem + lift_pts, 100)
+ projected = _new_cpci(new_mem=new_m)
+ delta = round(projected - cpci, 1)
+ if delta >= 2:
+ if text_pct < 5:
+ action = "Add a short brand tagline or product callout overlay"
+ rationale = "Dual-coding (visual + verbal) simultaneously encodes two memory traces — significantly lifting recall."
+ elif obj_count > 5:
+ action = "Reduce to one dominant product or hero image"
+ rationale = "Memory encodes the most salient object. Competing elements prevent a single strong trace from forming."
+ else:
+ action = "Strengthen logo placement and brand mnemonic visibility"
+ rationale = "Recognition at point of purchase requires a clear brand anchor encoded during ad exposure."
+ scenarios.append({
+ "signal": "Memory",
+ "sig_color": "#8B5CF6",
+ "action": action,
+ "rationale": rationale,
+ "method": "improving memory encoding",
+ "from_signal": mem, "to_signal": new_m,
+ "from_cpci": cpci, "to_cpci": projected,
+ "lift": delta,
+ })
+
+ # ── Scenario C: load reduction (High → Medium) ────────────────────────────
+ if cl == "High":
+ new_pen = LOAD_PENALTY_MAP.get("Medium", -5)
+ projected = _new_cpci(new_load_pen=new_pen)
+ delta = round(projected - cpci, 1)
+ if delta >= 2:
+ scenarios.append({
+ "signal": "Cognitive Load",
+ "sig_color": "#F59E0B",
+ "action": "Reduce on-screen text and visual elements by 40%",
+ "rationale": "High cognitive load causes viewers to abandon processing before the message registers — no targeting fix compensates for this.",
+ "method": "reducing cognitive load from High to Medium",
+ "from_signal": "High", "to_signal": "Medium",
+ "from_cpci": cpci, "to_cpci": projected,
+ "lift": delta,
+ })
+
+ # ── Scenario D: valence improvement ───────────────────────────────────────
+ val_gap = max(0.0, 0.15 - val)
+ if val_gap >= 0.12:
+ new_val = min(val + 0.28, 1.0)
+ new_vn = (new_val + 1) / 2 * 100
+ projected = _new_cpci(new_val_norm=new_vn)
+ delta = round(projected - cpci, 1)
+ if delta >= 2:
+ if face_count == 0:
+ action = "Introduce a human face with a positive expression"
+ rationale = "Facial expressions are the most direct driver of emotional valence — warmth and affinity are triggered involuntarily."
+ else:
+ action = "Shift colour palette toward warmer tones (amber, coral, warm white)"
+ rationale = "Cool and neutral palettes suppress approach affect. Warm palettes activate positive emotional responses without conscious effort."
+ scenarios.append({
+ "signal": "Emotion",
+ "sig_color": "#EC4899",
+ "action": action,
+ "rationale": rationale,
+ "method": "improving emotional valence",
+ "from_signal": f"{val:+.2f}", "to_signal": f"{new_val:+.2f}",
+ "from_cpci": cpci, "to_cpci": projected,
+ "lift": delta,
+ })
+
+ scenarios.sort(key=lambda s: s["lift"], reverse=True)
+ return scenarios
+
+
+def _optimization_scenario(
+ cpci: float, attn: int, mem: int, val: float, cl: str, vf: dict, use_case: str
+) -> None:
+ """
+ CMO-facing optimization scenario card.
+ Shows before/after CPCi, potential lift, and specific actionable fixes.
+ """
+ scenarios = _compute_scenarios(cpci, attn, mem, val, cl, vf, use_case)
+
+ if not scenarios:
+ # All signals already at or above target — no meaningful lift available
+ st.markdown(
+ "
"
+ "
🎯 Creative Optimization Scenario
"
+ "
"
+ "All cognitive signals are above threshold — no single optimization is likely to "
+ "produce meaningful additional lift. This creative is ready to scale.
"
+ "
",
+ unsafe_allow_html=True,
+ )
+ return
+
+ best = scenarios[0]
+ from_c = int(round(best["from_cpci"]))
+ to_c = int(round(best["to_cpci"]))
+ lift = best["lift"]
+ sig_color = best["sig_color"]
+
+ # Tier label helper
+ def _tier(c):
+ if c >= 70: return ("High Efficiency", "#22C55E")
+ if c >= 40: return ("Moderate Performance","#F59E0B")
+ return ("High Waste Risk", "#EF4444")
+
+ from_tier_label, from_tier_color = _tier(from_c)
+ to_tier_label, to_tier_color = _tier(to_c)
+ tier_change = from_tier_label != to_tier_label
+
+ # ── Before/after progress bar ─────────────────────────────────────────────
+ bar_from = f"{from_c}%"
+ bar_to = f"{to_c}%"
+
+ # Secondary scenario bullets
+ secondary_html = ""
+ for s in scenarios[1:3]: # show up to 2 more
+ secondary_html += (
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+ # ── Detail expander — rationale + secondary scenarios ─────────────────────
+ with st.expander("Why this works + additional opportunities"):
+ # Progress bar
+ st.markdown(
+ f"
"
+ f""
+ f""
+ f"
"
+ f"
"
+ f"Improving {best['method']} could increase CPCi from "
+ f"{from_c} → "
+ f"{to_c}.
"
+ f"
"
+ f"{best['rationale']}
"
+ + (
+ f"
"
+ f"Additional Opportunities
"
+ f"{secondary_html}"
+ if secondary_html else ""
+ ),
+ unsafe_allow_html=True,
+ )
+
+ st.markdown("", unsafe_allow_html=True)
+
+
+# ── Creative Brief ────────────────────────────────────────────────────────────
+
+def _creative_brief(
+ cpci: float,
+ attn: int,
+ mem: int,
+ val: float,
+ cl: str,
+ vf: dict,
+ use_case: str,
+) -> None:
+ """
+ Compact, executive-style brief. One panel. Every line is a decision.
+ Format mirrors a media/creative brief — diagnosis, issue, fix, use / don't use.
+ """
+ face = vf.get("face_count", 0) > 0
+ contrast = vf.get("contrast_score", 50)
+ objects = vf.get("object_count", 4)
+ text_pct = vf.get("text_density", 0.1) * 100
+ load_high = cl == "High"
+ load_low = cl == "Low"
+ attn_str = attn >= 65
+ attn_weak = attn < 45
+ mem_str = mem >= 65
+ mem_weak = mem < 45
+ val_pos = val > 0.10
+ val_neg = val < -0.05
+
+ cc = "#22C55E" if cpci >= 70 else ("#F59E0B" if cpci >= 40 else "#EF4444")
+
+ # ── Scale label ───────────────────────────────────────────────────────────
+ if cpci >= 70: scale_label = "Ready to scale"
+ elif cpci >= 55: scale_label = "Optimise before scaling"
+ elif cpci >= 40: scale_label = "Not ready for scale"
+ else: scale_label = "Do not deploy"
+
+ # ── Diagnosis (2 lines max) ───────────────────────────────────────────────
+ if cpci >= 70:
+ if attn_str and mem_str:
+ diag = [
+ "This creative clears every cognitive gate — attention, memory, and emotion are all above threshold.",
+ "It will work in feed environments against cold audiences and compound brand recall over time.",
+ ]
+ elif attn_str:
+ diag = [
+ "This creative has the attention signal to stop cold audiences, but memory encoding limits its long-term brand value.",
+ "It will drive clicks and initial engagement but will not build durable recall without frequency.",
+ ]
+ else:
+ diag = [
+ "This creative builds strong brand memory and emotional affinity, but its attention signal is below the cold-scroll threshold.",
+ "It will perform reliably with warm audiences and frequency-based brand campaigns.",
+ ]
+ elif cpci >= 55:
+ if attn_weak:
+ diag = [
+ "This creative will not reliably interrupt cold-audience feeds — it lacks the attention signal to win the first cognitive gate.",
+ "It can contribute to recall if repeatedly exposed to warm audiences, but acquisition spend is premature.",
+ ]
+ elif load_high:
+ diag = [
+ "This creative carries too much visual complexity for fast-scroll environments — the brain abandons processing before the message lands.",
+ "Stripping cognitive load is the single highest-leverage fix before any spend increase.",
+ ]
+ else:
+ diag = [
+ "This creative has real potential but one signal is suppressing the composite score.",
+ "A targeted fix — not a rebuild — is likely all that stands between this and scale-readiness.",
+ ]
+ elif cpci >= 40:
+ if not face and attn_weak:
+ diag = [
+ "This creative will not perform in feed environments — it lacks an attention trigger and fails to produce an orienting response.",
+ "It may work in retargeting environments where familiarity reduces the processing effort required.",
+ ]
+ elif load_high:
+ diag = [
+ "This creative asks too much of a scrolling audience — visual overload causes disengagement before the message registers.",
+ "No amount of targeting precision will compensate for cognitive friction at this level.",
+ ]
+ elif val_neg:
+ diag = [
+ "This creative generates mildly negative affect on each impression — a hidden brand tax at scale.",
+ "The emotional tone must be corrected before deployment; otherwise, spend compounds the damage.",
+ ]
+ else:
+ diag = [
+ "This creative has insufficient cognitive signal strength to justify cold-audience spend.",
+ "Narrow the audience to warm segments while the creative is improved.",
+ ]
+ else:
+ diag = [
+ "This creative will not perform regardless of budget, targeting, or placement.",
+ "The cognitive barriers are fundamental — a partial fix will not be sufficient.",
+ ]
+
+ # ── Primary issue ─────────────────────────────────────────────────────────
+ attn_gap = max(0, 60 - attn)
+ mem_gap = max(0, 70 - mem)
+ val_gap = max(0, 0.1 - val) * 100
+ load_gap = 25 if cl == "High" else (10 if cl == "Medium" else 0)
+ worst = max(attn_gap, mem_gap, val_gap, load_gap)
+
+ if worst == 0:
+ primary_issue = "No dominant weakness — all signals are above threshold."
+ elif worst == load_gap and load_high:
+ primary_issue = "Visual overload — too many elements competing for attention simultaneously."
+ elif worst == attn_gap and not face:
+ primary_issue = "No dominant focal point — nothing triggers an orienting response."
+ elif worst == attn_gap and contrast < 45:
+ primary_issue = "Insufficient contrast — creative is invisible in a competitive feed."
+ elif worst == attn_gap:
+ primary_issue = "Diffuse composition — attention fragments with no single dominant subject."
+ elif worst == mem_gap and text_pct > 25:
+ primary_issue = "Text overload — verbal channel is saturated, blocking dual-coding."
+ elif worst == mem_gap and text_pct < 5:
+ primary_issue = "No verbal anchor — visual alone will not survive working memory."
+ elif worst == mem_gap:
+ primary_issue = "Too many competing elements — no single memory trace will dominate."
+ elif worst == val_gap and not face:
+ primary_issue = "No human presence — palette alone cannot generate positive affect."
+ else:
+ primary_issue = "Negative emotional signal — colour palette is triggering subconscious avoidance."
+
+ # ── Fix ───────────────────────────────────────────────────────────────────
+ if not face and attn_gap > 15:
+ fix = "Introduce a face or strong visual anchor — it is the highest-leverage single change available."
+ elif contrast < 45 and attn_gap > 10:
+ fix = "Increase foreground contrast significantly — pass the visual salience threshold first."
+ elif objects > 7 and (worst == load_gap or worst == mem_gap):
+ fix = "Commit to one hero element — remove everything that is not the primary message."
+ elif text_pct > 28:
+ fix = "Cut copy to a single line under six words — audiences read far less than advertisers write."
+ elif text_pct < 4 and mem_gap > 15:
+ fix = "Add a 4–6 word brand line — the verbal channel is unused at zero cost to attention."
+ elif val_neg and not face:
+ fix = "Add a person with a warm, natural expression — valence cannot be fixed with colour at this deficit."
+ elif val_neg:
+ fix = "Replace dominant cool or dark tones with warmer equivalents — the palette is the valence driver."
+ elif attn_gap > 10:
+ fix = "Scale up the primary subject and push contrast — give the eye an unmissable landing point."
+ else:
+ fix = "Simplify to one dominant image and one short message — single-exposure recall demands it."
+
+ # ── Recommended use ───────────────────────────────────────────────────────
+ recommended, avoid = [], []
+
+ if cpci >= 70:
+ recommended = ["Cold acquisition", "Full-funnel spend", "High-reach brand campaigns"]
+ avoid = []
+ elif cpci >= 55:
+ if attn_weak:
+ recommended = ["Warm retargeting", "Lookalike audiences (L1–L3)", "Frequency-capped brand campaigns"]
+ avoid = ["Cold acquisition", "Prospecting campaigns"]
+ else:
+ recommended = ["Warm retargeting", "Mid-funnel conversion", "Frequency builds"]
+ avoid = ["Top-of-funnel cold spend at scale"]
+ elif cpci >= 40:
+ recommended = ["Retargeting (strict frequency cap: 2–3x)", "High-intent warm audiences only"]
+ avoid = ["Cold acquisition", "Reach campaigns", "OOH or display"]
+ else:
+ recommended = []
+ avoid = ["Any paid deployment", "Cold audiences", "Retargeting", "Brand campaigns"]
+
+ # ── Build HTML ────────────────────────────────────────────────────────────
+ def arrow_list(items, color):
+ return "".join(
+ f"
"
+ f"→"
+ f"{item}"
+ f"
"
+ for item in items
+ )
+
+ diag_html = "".join(
+ f"
{d}
"
+ for d in diag
+ )
+
+ rec_html = arrow_list(recommended, "#22C55E") if recommended else (
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+
+# ── Media Implications ────────────────────────────────────────────────────────
+
+def _media_implications(
+ cpci: float,
+ attn: int,
+ mem: int,
+ val: float,
+ cl: str,
+ vf: dict,
+ use_case: str,
+) -> None:
+ """
+ Translates cognitive signals into placement-level media planning decisions:
+ - Where this creative will work
+ - Where it will fail
+ - Recommended media strategy
+ """
+
+ face = vf.get("face_count", 0) > 0
+ contrast = vf.get("contrast_score", 50)
+ objects = vf.get("object_count", 4)
+ text_pct = vf.get("text_density", 0.1) * 100
+ load_high = cl == "High"
+ load_low = cl == "Low"
+ attn_str = attn >= 65
+ attn_weak = attn < 45
+ mem_str = mem >= 65
+ mem_weak = mem < 45
+ val_pos = val > 0.10
+ val_neg = val < -0.05
+
+ # ── Placement definitions ──────────────────────────────────────────────────
+ ALL_PLACEMENTS = {
+ "Social Feed": {"requires": "attn_str or contrast >= 60", "format": "fast-scroll"},
+ "YouTube Pre-roll": {"requires": "attn_str and val_pos", "format": "interruptive"},
+ "Display / Programmatic": {"requires": "not load_high", "format": "passive"},
+ "Retail Media": {"requires": "mem_str and not load_high", "format": "purchase-adjacent"},
+ "OOH / DOOH": {"requires": "load_low and not load_high and contrast >= 60", "format": "sub-2s"},
+ "Retargeting": {"requires": "not attn_str", "format": "warm-audience"},
+ "CTV / Connected TV": {"requires": "val_pos and mem_str", "format": "lean-back"},
+ }
+
+ # Evaluate each placement
+ works, fails = [], []
+
+ # Social Feed
+ if attn_str or contrast >= 60:
+ works.append(("Social Feed", "Attention signal is strong enough to interrupt the scroll"))
+ else:
+ fails.append(("Social Feed", "Will not survive the scroll — attention signal too weak to compete in a competitive feed"))
+
+ # YouTube Pre-roll
+ if attn_str and val_pos:
+ works.append(("YouTube Pre-roll", "Strong attention + positive valence — will hold viewers through the skip window"))
+ elif attn_str and not val_pos:
+ fails.append(("YouTube Pre-roll", "Will get noticed but emotional tone will not build the affinity needed to drive post-view action"))
+ else:
+ fails.append(("YouTube Pre-roll", "Insufficient attention to survive the skip — viewers will disengage before the message lands"))
+
+ # Display / Programmatic
+ if not load_high:
+ works.append(("Display / Programmatic", "Low cognitive load means the message processes passively — effective for frequency builds"))
+ else:
+ fails.append(("Display / Programmatic", "High cognitive load in a passive format — the viewer will not invest the effort required to decode this"))
+
+ # Retail Media
+ if mem_str and not load_high:
+ works.append(("Retail Media", "Strong memory encoding at point of purchase proximity — will drive recognition when intent is highest"))
+ else:
+ fails.append(("Retail Media", "Weak memory signal means the brand will not be recognised at the shelf or checkout — the moment the placement is designed for"))
+
+ # OOH / DOOH
+ if load_low and contrast >= 60:
+ works.append(("OOH / DOOH", "Low load + high contrast — will process within the 1.5s average dwell time for outdoor formats"))
+ else:
+ fails.append(("OOH / DOOH", "Will not process in under 2 seconds — too complex or too low-contrast for outdoor dwell times"))
+
+ # CTV
+ if val_pos and mem_str:
+ works.append(("CTV / Connected TV", "Positive valence + strong memory encoding — lean-back audiences will absorb and retain the brand message"))
+ else:
+ fails.append(("CTV / Connected TV", "Neutral or negative valence in a lean-back format — the emotional opportunity of CTV will be wasted"))
+
+ # ── Strategy ───────────────────────────────────────────────────────────────
+ if cpci >= 70:
+ if attn_str and mem_str:
+ strategy = "top_funnel"
+ strat_label = "Full-funnel — prioritise reach"
+ strat_detail = (
+ "Deploy at scale across cold audiences. This creative clears the cognitive bar "
+ "for both acquisition and recall — a rare combination. Allocate the majority of "
+ "budget to prospecting. Retargeting will compound the memory already built."
+ )
+ elif attn_str:
+ strategy = "top_funnel"
+ strat_label = "Top-of-funnel acquisition"
+ strat_detail = (
+ "Strong in cold audiences — deploy for reach and awareness. "
+ "Pair with a simpler retargeting creative to close the memory gap, "
+ "or add a short text overlay to anchor the brand name."
+ )
+ else:
+ strategy = "frequency"
+ strat_label = "Frequency play — build recall"
+ strat_detail = (
+ "CPCi is strong but memory needs frequency to compound. Cap at 4–5 "
+ "impressions per user per week. Recall will build reliably — avoid "
+ "over-exposing or the returns diminish."
+ )
+ elif cpci >= 55:
+ strategy = "warm_audience"
+ strat_label = "Warm audiences — not cold"
+ strat_detail = (
+ "This creative is not ready for cold-audience acquisition — the cognitive "
+ "signal is too mixed to convert efficiently at scale. Restrict spend to "
+ "retargeting and lookalike audiences where intent is pre-established. "
+ "Fix the weakest signal before opening to prospecting."
+ )
+ elif cpci >= 40:
+ strategy = "retargeting"
+ strat_label = "Retargeting only — strict frequency cap"
+ strat_detail = (
+ "Limit to retargeting with a hard frequency cap of 2–3 exposures. "
+ "The cognitive signal is insufficient for cold acquisition — spend here "
+ "will generate impressions but not conversions. Treat as a stopgap "
+ "while the creative is rebuilt."
+ )
+ else:
+ strategy = "hold"
+ strat_label = "Hold spend — rebuild before deployment"
+ strat_detail = (
+ "Do not deploy at any scale. Budget spent on this creative will generate "
+ "impressions at near-zero cognitive impact — or worse, accumulate negative "
+ "brand associations. The creative requires a rebuild before media spend is justified."
+ )
+
+ strat_colors = {
+ "top_funnel": "#22C55E",
+ "full_funnel": "#22C55E",
+ "frequency": "#3B82F6",
+ "warm_audience": "#F59E0B",
+ "retargeting": "#F59E0B",
+ "hold": "#EF4444",
+ }
+ sc = strat_colors.get(strategy, "#CBD5E1")
+
+ # ── Render ─────────────────────────────────────────────────────────────────
+ def _place_row(icon, name, reason, good: bool):
+ dot = "#22C55E" if good else "#EF4444"
+ label = "Works" if good else "Fails"
+ return (
+ f"
"
+ f""
+ f"
"
+ f"
"
+ f"{name}"
+ f"{label}"
+ f"
"
+ f"
{reason}
"
+ f"
"
+ f"
"
+ )
+
+ works_html = "".join(_place_row("✓", n, r, True) for n, r in works)
+ fails_html = "".join(_place_row("✗", n, r, False) for n, r in fails)
+
+ st.markdown(
+ "
"
+ "Upload any JPG, PNG, or MP4 — results in under 30 seconds.
"
+ "
",
+ unsafe_allow_html=True,
+ )
+ cta_back, cta_exit = st.columns([1, 2])
+ with cta_back:
+ if st.button("← Back to analysis", use_container_width=True):
+ st.session_state["demo_step"] = 2
+ st.rerun()
+ with cta_exit:
+ if st.button(
+ "Upload my own creative →",
+ type="primary",
+ use_container_width=True,
+ ):
+ # Can't set "demo_mode" directly here — it's bound to a widget key.
+ # Signal the header guard (runs before the toggle) to clear it on next rerun.
+ st.session_state["_exit_demo"] = True
+ st.rerun()
+
+
+def _render_trust_indicators(cpci: float, attn: int, mem: int, conf_level: str, conf_color: str) -> None:
+ """
+ Three trust signals rendered below every result:
+ 1. How this works (collapsed expander)
+ 2. Limitations (1-line inline note)
+ 3. Confidence indicator (dot + label)
+ """
+ st.markdown(
+ "",
+ unsafe_allow_html=True,
+ )
+
+ left_col, right_col = st.columns([3, 1], gap="large")
+
+ # ── Left: How this works (collapsed) + Limitations ────────────────────────
+ with left_col:
+ with st.expander("ℹ️ How this works", expanded=False):
+ st.markdown(
+ "
"
+
+ "What CPCi measures "
+ "CPCi (Cost Per Cognitive Impression) is a composite score produced by the "
+ "Cognitive Signal Engine™ — an original multi-layer analytical system that models how "
+ "the human brain processes advertising creative. It draws on principles from "
+ "neuroscience, behavioural science, and advertising theory to produce three weighted "
+ "signals: Attention, "
+ "Memory encoding, and "
+ "Emotional valence.
"
+
+ "Signal sources "
+ "Each signal is derived from measurable visual properties — face presence, "
+ "contrast ratios, object density, text load, colour palette, and spatial "
+ "composition — across independent analytical layers. The system does not rely "
+ "on a single AI model. Theoretical grounding comes from Sweller's Cognitive "
+ "Load Theory, Paivio's Dual-Coding Theory, and empirical attention research.
"
+
+ "Use case weighting "
+ "The formula shifts weights based on your campaign objective — "
+ "Performance Marketing prioritises attention, Brand campaigns prioritise "
+ "memory encoding. This changes the final CPCi score without changing "
+ "the underlying signal readings.
"
+
+ "Origin "
+ "Cognitive Signal Engine™ is a proprietary framework developed by "
+ "Anil Pandit, "
+ "integrating neuroscience, behavioral science, and advertising theory "
+ "into a decision system for creative effectiveness. "
+ "It was built independently using computer vision, signal processing, "
+ "and cognitive science theory. TRIBE v2 was the originating research context — "
+ "the Cognitive Signal Engine is the productised analytical layer built on top of it."
+
+ "
"
+ "⚠ Limitations "
+ "This is a predictive cognitive model based on visual signals, not real user "
+ "behaviour. Scores are directional — use them to prioritise testing, not to "
+ "replace it."
+ "
"
+ "Methodology note"
+ " — This system is inspired by advances like Meta's TRIBE v2, but extends "
+ "them into a practical decision framework for advertising using cognitive signal modeling."
+ "
",
+ unsafe_allow_html=True,
+ )
+
+ # ── Right: Confidence indicator ───────────────────────────────────────────
+ with right_col:
+ conf_bg = {"High": "#22C55E18", "Medium": "#F59E0B18", "Low": "#EF444418"}.get(conf_level, "#1F2937")
+ conf_desc = {
+ "High": "Both attention and memory signals are strong. Score is reliable.",
+ "Medium": "Signals are mixed. Score is directional — validate with a test.",
+ "Low": "Signals diverge significantly. Treat this score as indicative only.",
+ }.get(conf_level, "")
+ st.markdown(
+ f"
"
+ f"
"
+ f"Model Confidence
"
+ f"
"
+ f"●"
+ f"{conf_level}"
+ f"
"
+ f"
{conf_desc}
"
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+
+def _render_cta_block(r: dict, use_case: str) -> None:
+ """Bottom-of-page CTA — makes the app feel like a product, not a demo."""
+ st.markdown(
+ "
"
+ "
"
+ "Cognitive Signal Engine™
"
+ "
"
+ "Ready to test your creatives before media spend?
"
+ "
"
+ "Stop guessing. Know which creatives earn attention, build memory, "
+ "and drive response — before you commit budget.
",
+ unsafe_allow_html=True,
+ )
+
+ detail_left, detail_right = st.columns([1, 1], gap="large")
+
+ with detail_left:
+ _section_card(
+ icon = "📈",
+ title = "Strategic Implication",
+ accent= "#3B82F6",
+ body = narr.get("strategic_implication", ""),
+ pointers=[
+ ("Use case", use_case, "#3B82F6"),
+ ("CPCi", f"{cpci}/100", cc),
+ ("Attention", f"{attn}/100", a_color),
+ ("Memory", f"{mem}/100", m_color),
+ ],
+ )
+
+ with detail_right:
+ _render_recommendations(
+ body = narr.get("recommendations", ""),
+ pointers = [
+ ("Priority fix",
+ "Add face" if vf["face_count"] == 0
+ else "Boost contrast" if vf["contrast_score"] < 60
+ else "Reduce objects" if vf["object_count"] > 6
+ else "Add tagline" if vf["text_density"] < 0.05
+ else "Trim copy",
+ "#F59E0B"),
+ ("Attention gap", f"{max(0, 60-attn)} pts", a_color),
+ ("Memory gap", f"{max(0, 70-mem)} pts", m_color),
+ ("Load", cl, cl_color),
+ ],
+ )
+
+ # Cognitive Diagnosis — full width, most technical, last
+ _cognitive_diagnosis(
+ attn=attn, mem=mem, val=val, cl=cl, cl_score=cl_score,
+ vf=vf,
+ a_color=a_color, a_label=a_label,
+ m_color=m_color, m_label=m_label,
+ v_color=v_color, v_label=v_label,
+ cl_color=cl_color,
+ )
+
+ # Trust indicators — how this works, limitations, confidence
+ st.markdown("", unsafe_allow_html=True)
+ _render_trust_indicators(cpci, attn, mem, conf_level, conf_color)
+
+ # Export report bar
+ st.markdown("", unsafe_allow_html=True)
+ _render_export_bar(r, use_case)
+
+ # ── CTA block ────────────────────────────────────────────────────────────
+ _render_cta_block(r, use_case)
+
+
+# ── Multi-creative comparison ─────────────────────────────────────────────────
+
+import base64 as _b64
+
+def _client_insight(narr: dict, cpci: float, attn: int, mem: int, val: float, cl: str, use_case: str) -> str:
+ """
+ One plain-English sentence a client can instantly understand.
+ Pulled from narrative if available, otherwise generated from signals.
+ No jargon. No scores. Just what it means for the campaign.
+ """
+ # Try narrative first
+ si = narr.get("strategic_implication", "")
+ if si and len(si) > 20:
+ # Return first sentence only
+ return si.split(".")[0].strip() + "."
+
+ # Fallback: signal-driven
+ if cpci >= 70:
+ if attn >= 65:
+ return "This creative will stop the scroll and encode the brand — both are rare in the same creative, and this is ready to scale."
+ return "This creative will drive cognitive engagement efficiently — all signals are above the threshold required for reliable performance."
+ elif cpci >= 55:
+ if attn < 45:
+ return "This creative will build recall among viewers who see it, but will not reliably stop cold audiences — acquisition efficiency is at risk."
+ if mem < 50:
+ return "This creative will attract attention but will not be remembered after a single exposure — reach without recall is wasted media."
+ return "This creative is one signal fix away from scale — the foundation is strong but one dimension is suppressing the composite score."
+ elif cpci >= 40:
+ if cl == "High":
+ return "This creative will lose viewers before the message lands — visual overload is causing the brain to abandon processing before engagement occurs."
+ return "This creative will not convert efficiently at scale — the cognitive signal profile is too weak to justify full budget deployment."
+ else:
+ return "This creative will not perform regardless of budget or targeting — the cognitive barriers require a rebuild, not a boost in spend."
+
+
+def _client_recommendation(narr: dict, cpci: float, attn: int, mem: int, val: float, cl: str, vf: dict) -> str:
+ """One actionable sentence a client can take to their creative team."""
+ rec = narr.get("recommendations", "")
+ if rec and len(rec) > 20:
+ return rec.split(".")[0].strip() + "."
+
+ # Fallback
+ if vf.get("face_count", 0) == 0 and attn < 50:
+ return "Add a human face as the primary visual — it is the fastest single change to trigger an orienting response and lift emotional valence."
+ if cl == "High":
+ return "Remove at least half the visual elements before scaling — working memory saturation is actively blocking every other signal."
+ if mem < 45:
+ return "Rebuild around one dominant image and one short line of copy — single-exposure recall requires radical simplicity."
+ if val < -0.05:
+ return "Replace the dominant cool tones with warmer equivalents — the palette is generating subconscious avoidance that will compound across impressions."
+ if attn < 45:
+ return "Increase the primary subject's size and contrast — the creative needs to clear the visual salience threshold before targeting or spend can help."
+ return "Test a version with a human face as the hero — it is the highest-probability change for lifting attention, emotion, and recall simultaneously."
+
+
+def _show_results_client(r: dict, use_case: str) -> None:
+ """Client Mode — single creative. Clean, no technical metrics."""
+ s = r["signals"]
+ narr = r.get("narrative", {})
+ cpci = r["cpci"]
+ attn = s["attention_score"]
+ mem = s["memory_score"]
+ val = s["emotional_valence"]
+ cl = s["cognitive_load"]
+ vf = r["visual_features"]
+
+ cc = "#22C55E" if cpci >= 70 else ("#F59E0B" if cpci >= 40 else "#EF4444")
+ verdict = _final_verdict_text(cpci, attn, mem, val, cl, use_case)
+ insight = _client_insight(narr, cpci, attn, mem, val, cl, use_case)
+ rec = _client_recommendation(narr, cpci, attn, mem, val, cl, vf)
+
+ # Confidence (same logic as expert mode)
+ if attn > 50 and mem > 50:
+ conf_level, conf_color = "High", "#22C55E"
+ elif attn < 30 and mem < 30:
+ conf_level, conf_color = "Low", "#EF4444"
+ elif abs(attn - mem) > 35 or (attn < 30 or mem < 30):
+ conf_level, conf_color = "Low", "#EF4444"
+ else:
+ conf_level, conf_color = "Medium", "#F59E0B"
+
+ # Label
+ if cpci >= 70: perf_label = "Strong Performer"
+ elif cpci >= 40: perf_label = "Needs Optimisation"
+ else: perf_label = "Not Ready to Scale"
+
+ # ── Creative image hero ───────────────────────────────────────────────────
+ _render_creative_hero(r.get("file_path", ""), r.get("name", ""), is_video=r.get("visual_features", {}).get("is_video", False))
+
+ left, right = st.columns([2, 3], gap="large")
+
+ with left:
+ st.markdown(
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+
+_IMG_EXTS = {"jpg", "jpeg", "png", "gif", "webp"}
+_VIDEO_EXTS_B64 = {"mp4", "mov", "avi", "webm", "m4v", "mkv"}
+
+def _img_b64(file_path: str) -> str:
+ """Return a base64 data URI for an image file (for inline HTML display).
+ For video files, uses the saved thumbnail (generated during analysis).
+ Returns '' if unavailable so callers show a 'No preview' fallback.
+ """
+ if not file_path:
+ return ""
+ try:
+ ext = file_path.rsplit(".", 1)[-1].lower()
+ # For video files, use the thumbnail PNG saved alongside it
+ if ext in _VIDEO_EXTS_B64:
+ thumb = file_path + "_thumb.png"
+ if os.path.exists(thumb):
+ with open(thumb, "rb") as f:
+ data = _b64.b64encode(f.read()).decode()
+ return f"data:image/png;base64,{data}"
+ return "" # no thumbnail → show "No preview" placeholder
+ # Image files — only encode known image types to avoid binary garbage
+ if ext not in _IMG_EXTS:
+ return ""
+ with open(file_path, "rb") as f:
+ data = _b64.b64encode(f.read()).decode()
+ mime = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png",
+ "gif": "gif", "webp": "webp"}.get(ext, "jpeg")
+ return f"data:image/{mime};base64,{data}"
+ except Exception:
+ return ""
+
+
+_HERO_VIDEO_EXTS = {".mp4", ".mov", ".avi", ".webm", ".m4v"}
+
+def _render_creative_hero(file_path: str, name: str = "", is_video: bool = False) -> None:
+ """Render the uploaded creative prominently above analysis output."""
+ label_html = (
+ f"
"
+ f"{'🎬 Analyzed Video Creative' if is_video else 'Analyzed Creative'}
"
+ )
+ name_html = (
+ f"
{name}
"
+ if name else ""
+ )
+
+ # Check by extension if not explicitly flagged
+ if not is_video:
+ is_video = os.path.splitext(file_path)[1].lower() in _HERO_VIDEO_EXTS
+
+ if is_video and os.path.exists(file_path):
+ st.markdown(
+ f"
{label_html}
",
+ unsafe_allow_html=True,
+ )
+ # Centre the video player with constrained width
+ _vid_l, _vid_c, _vid_r = st.columns([1, 4, 1])
+ with _vid_c:
+ with open(file_path, "rb") as vf:
+ st.video(vf.read())
+ if name:
+ st.caption(name)
+ return
+
+ img_src = _img_b64(file_path)
+ if not img_src:
+ return
+ st.markdown(
+ f"
"
+ f"{label_html}"
+ f"
"
+ f""
+ f"{name_html}"
+ f"
"
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+
+def _why_wins(winner: dict, runner_up: dict, use_case: str) -> str:
+ """
+ Return a tight 2–3 sentence decisive summary of why the winner beats the runner-up.
+ No hedging. No 'it seems'. Just facts + numbers.
+ """
+ ws = winner["signals"]; rs = runner_up["signals"]
+ wvf = winner["visual_features"]; rvf = runner_up["visual_features"]
+ w_cpci = winner["cpci"]; r_cpci = runner_up["cpci"]
+ gap = round(w_cpci - r_cpci, 1)
+
+ parts = []
+
+ # --- CPCi gap opener
+ if gap >= 20:
+ parts.append(
+ f"{winner['name']} dominates by {gap} CPCi points — "
+ f"a margin that translates directly to higher conversion probability in market."
+ )
+ elif gap >= 10:
+ parts.append(
+ f"{winner['name']} outperforms by {gap} CPCi points — "
+ f"a clear, meaningful edge across the cognitive signal stack."
+ )
+ else:
+ parts.append(
+ f"{winner['name']} edges ahead by {gap} CPCi points — "
+ f"a narrow but consistent advantage across multiple signals."
+ )
+
+ # --- Strongest signal advantage
+ attn_gap = ws["attention_score"] - rs["attention_score"]
+ mem_gap = ws["memory_score"] - rs["memory_score"]
+ val_gap = round(ws["emotional_valence"] - rs["emotional_valence"], 2)
+ load_adv = {"Low": 0, "Medium": 1, "High": 2}
+ load_gap = load_adv.get(rs["cognitive_load"], 1) - load_adv.get(ws["cognitive_load"], 1)
+
+ advantages = sorted(
+ [("attn", attn_gap), ("mem", mem_gap), ("val", val_gap * 40), ("load", load_gap * 15)],
+ key=lambda x: x[1], reverse=True,
+ )
+ top_sig, top_gap = advantages[0]
+
+ if top_sig == "attn" and attn_gap > 3:
+ reason = (
+ f"The primary edge is attention (+{attn_gap} pts) — "
+ )
+ if wvf.get("face_count", 0) > rvf.get("face_count", 0):
+ reason += "the winner's human face triggers involuntary fixation before the viewer consciously decides to look."
+ elif wvf.get("contrast_score", 50) > rvf.get("contrast_score", 50) + 8:
+ reason += "higher contrast pops the winner out of feed clutter where the loser blends in."
+ else:
+ reason += "the winner's cleaner composition gives the eye a clear landing point — the loser fragments attention across too many elements."
+ elif top_sig == "mem" and mem_gap > 3:
+ reason = (
+ f"The decisive factor is memory encoding (+{mem_gap} pts) — "
+ )
+ if wvf.get("object_count", 5) < rvf.get("object_count", 5):
+ reason += f"the winner's simpler layout ({wvf.get('object_count',0)} objects vs {rvf.get('object_count',0)}) encodes cleanly into long-term memory; the loser's clutter gets discarded."
+ elif 0.05 <= wvf.get("text_density", 0) <= 0.25 and rvf.get("text_density", 0) > 0.25:
+ reason += "optimal text density gives the brain a verbal anchor; the loser overloads the verbal channel and nothing sticks."
+ else:
+ reason += "a simpler visual hierarchy lets the brain commit the message — the loser asks viewers to do too much cognitive work."
+ elif top_sig == "load" and load_gap > 0:
+ reason = (
+ f"Lower cognitive load is the key differentiator — "
+ f"the winner processes in milliseconds ({ws['cognitive_load']}), "
+ f"the loser demands effort ({rs['cognitive_load']}) that scrolling audiences won't give."
+ )
+ elif top_sig == "val" and val_gap > 0.05:
+ reason = (
+ f"Emotional valence (+{val_gap:+.2f}) tips the balance — "
+ )
+ if wvf.get("face_count", 0) > rvf.get("face_count", 0):
+ reason += "the winner's human face generates affiliative warmth the loser's visuals simply cannot replicate."
+ else:
+ reason += "the winner's color palette encodes positive affect subconsciously; the loser's palette creates mild aversion most viewers never consciously notice."
+ else:
+ reason = (
+ "The winner holds a consistent but slim edge across all signals — "
+ "no single metric dominates, but small advantages in contrast, simplicity, "
+ "and color warmth compound into a measurable CPCi lead."
+ )
+
+ parts.append(reason)
+
+ # --- Use-case verdict
+ is_perf = use_case == "Performance Marketing"
+ is_brand = use_case == "FMCG Branding"
+ if is_perf:
+ parts.append(
+ f"For Performance Marketing, attention is the first conversion gate — "
+ f"{winner['name']} clears it; {runner_up['name']} risks the scroll-past before a click can happen."
+ )
+ elif is_brand:
+ parts.append(
+ f"For Brand Building, memory encoding determines whether this spend compounds over time — "
+ f"{winner['name']} encodes; {runner_up['name']} fades."
+ )
+ else:
+ parts.append(
+ f"Scale {winner['name']} first. Test {runner_up['name']} only after fixing its weakest signal."
+ )
+
+ return " ".join(parts)
+
+
+def show_comparison(sorted_results: list, use_case: str = "Performance Marketing", client_mode: bool = False) -> None:
+ """
+ A/B testing decision screen:
+ 1. Side-by-side creative cards — winner glows green
+ 2. "Why this wins" decisive summary
+ 3. Signal bar breakdown
+ 4. Per-creative score explanations + download
+ """
+ if client_mode:
+ _show_comparison_client(sorted_results, use_case)
+ return
+ winner = sorted_results[0]
+ n = len(sorted_results)
+
+ # ── Header ────────────────────────────────────────────────────────────────
+ st.markdown(
+ "
Cognitive Signal Engine™
"
+ "
Creative Intelligence Analyzer
"
+ f"
Comparing {n} Creatives — {use_case}
",
+ unsafe_allow_html=True,
+ )
+
+ # ── Creative cards ────────────────────────────────────────────────────────
+ cols = st.columns(n, gap="small")
+ for i, (col, r) in enumerate(zip(cols, sorted_results)):
+ s = r["signals"]
+ cpci = r["cpci"]
+ is_win = (i == 0)
+ cc = "#22C55E" if cpci >= 70 else ("#F59E0B" if cpci >= 40 else "#EF4444")
+ card_cls = "ab-card-winner" if is_win else "ab-card"
+
+ # Rank label / winner badge
+ rank_html = (
+ "
🏆 Winner
"
+ if is_win else
+ f"
#{i+1}
"
+ )
+
+ # Signal colors
+ a_c = "#22C55E" if s["attention_score"] >= 65 else ("#F59E0B" if s["attention_score"] >= 35 else "#EF4444")
+ m_c = "#22C55E" if s["memory_score"] >= 65 else ("#F59E0B" if s["memory_score"] >= 40 else "#EF4444")
+ v = s["emotional_valence"]
+ v_c = "#22C55E" if v > 0.1 else ("#F59E0B" if v > -0.1 else "#EF4444")
+ cl = s["cognitive_load"]
+ l_c = "#22C55E" if cl == "Low" else ("#F59E0B" if cl == "Medium" else "#EF4444")
+
+ # Verdict
+ vdict_color = cc
+ vdict_txt = _final_verdict_text(cpci, s["attention_score"], s["memory_score"], v, cl, use_case)
+
+ # Image thumbnail
+ img_src = _img_b64(r.get("file_path", ""))
+ img_html = (
+ f""
+ if img_src else
+ f"
No preview
"
+ )
+
+ col.markdown(
+ f"
"
+ f"{rank_html}"
+ f"{img_html}"
+ f"
{short_name(r['name'], 22)}
"
+ f"
{cpci}
"
+ f"
CPCi Score / 100
"
+ f"
"
+ f"
Attention
"
+ f"
{s['attention_score']}
"
+ f"
Memory
"
+ f"
{s['memory_score']}
"
+ f"
Valence
"
+ f"
{v:+.2f}
"
+ f"
Load
"
+ f"
{cl}
"
+ f"
"
+ f"
"
+ f"{vdict_txt}
"
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+ # ── CPCi score bars ────────────────────────────────────────────────────────
+ max_cpci = sorted_results[0]["cpci"] if sorted_results[0]["cpci"] > 0 else 1
+ bars_html = ""
+ for r in sorted_results:
+ pct = int(r["cpci"] / max_cpci * 100)
+ bc = "#22C55E" if r["cpci"] >= 70 else ("#F59E0B" if r["cpci"] >= 40 else "#EF4444")
+ nm = short_name(r["name"], 18)
+ bars_html += (
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+
+def _final_verdict_text(cpci, attn, mem, val, cl, use_case) -> str:
+ """Return the verdict string only (used by both show_results and comparison cards)."""
+ attn_weak = attn < 45; mem_weak = mem < 50
+ load_high = cl == "High"; val_neg = val < -0.05
+ attn_str = attn >= 65; mem_str = mem >= 65
+ is_perf = use_case == "Performance Marketing"
+ is_brand = use_case == "FMCG Branding"
+ if cpci >= 70:
+ if attn_str and mem_str: return "Ready to scale — strong on every signal."
+ if attn_str and mem_weak: return "Strong for attention, weak for recall — add retargeting."
+ if mem_str and attn_weak: return "Powerful recall, weak hook — fix opening frame."
+ if load_high: return "High scores, high clutter — simplify before scaling."
+ return "Ready to scale — above threshold on all signals."
+ elif cpci >= 55:
+ if attn_weak and not mem_weak: return "Strong recall, weak acquisition — not cold-audience ready."
+ if mem_weak and not attn_weak: return "Stops the scroll but won't be remembered."
+ if load_high: return "High potential — strip cognitive load first."
+ if val_neg and is_brand: return "Brand risk — emotional tone must improve before spend."
+ return "High potential — optimize attention before scaling."
+ elif cpci >= 40:
+ if attn_weak and mem_weak: return "NOT ready for scale."
+ if load_high and attn_weak: return "Too cluttered to convert — rebuild."
+ if val_neg: return "Negative signal — will hurt brand at scale."
+ if is_perf and mem_weak: return "Will not convert — memory encoding too weak."
+ return "Borderline — fix one signal before scaling."
+ else:
+ if val_neg and load_high: return "Do not run — will damage brand perception."
+ if attn < 25: return "NOT ready for scale — will be ignored."
+ if mem < 30: return "Forgettable at any spend level — rebuild."
+ return "NOT ready for scale."
+
+
+MEDALS = ["🥇", "🥈", "🥉", "④", "⑤"]
+
+def show_comparison_table(results: list) -> None:
+ """
+ Section 1: Comparison table — one row per creative, all key metrics side by side.
+ Sorted in upload order (not ranked — ranking is Section 2).
+ """
+ st.markdown("", unsafe_allow_html=True)
+ st.markdown("
", unsafe_allow_html=True)
+
+ max_cpci = sorted_results[0]["cpci"] if sorted_results else 100
+
+ for i, r in enumerate(sorted_results):
+ medal = MEDALS[i] if i < len(MEDALS) else f"#{i+1}"
+ card_class = "rank-card rank-winner" if i == 0 else ("rank-card rank-second" if i == 1 else "rank-card")
+ cc = cpci_color(r["cpci"])
+ s = r["signals"]
+
+ st.markdown(f"""
+
+
+
+ {medal}
+ {r['name']}
+ {"Winner" if i == 0 else ""}
+
+ This creative wins because it delivers the highest cognitive impact (CPCi) —
+ meaning it is most likely to convert attention into memory and action.
+
+
""", unsafe_allow_html=True)
+
+ # Per-competitor breakdown
+ for loser in losers:
+ ls = loser["signals"]
+ lvf = loser["visual_features"]
+ l_attn = ls["attention_score"]
+ l_mem = ls["memory_score"]
+ l_val = ls["emotional_valence"]
+ l_cl = ls["cognitive_load"]
+ l_cpci = loser["cpci"]
+ cpci_gap = round(w_cpci - l_cpci, 1)
+
+ # Build advantage list
+ advantages = []
+ weaknesses = []
+
+ if w_attn > l_attn + 5:
+ diff = w_attn - l_attn
+ advantages.append(
+ f"+{diff} pts Attention ({w_attn} vs {l_attn}) — "
+ + ("Winner has faces creating involuntary fixation. " if wvf["face_count"] > lvf["face_count"] else "")
+ + (f"Winner contrast {wvf['contrast_score']:.0f} vs {lvf['contrast_score']:.0f} — "
+ f"higher contrast stops the scroll. " if wvf["contrast_score"] > lvf["contrast_score"] + 10 else "")
+ + (f"Loser has {lvf['object_count']} objects vs {wvf['object_count']} — clutter fragments attention." if lvf["object_count"] > wvf["object_count"] + 2 else "")
+ )
+ elif l_attn > w_attn + 5:
+ weaknesses.append(f"Loser has +{l_attn - w_attn} pts higher attention ({l_attn} vs {w_attn}) but weaker on other metrics.")
+
+ if w_mem > l_mem + 5:
+ diff = w_mem - l_mem
+ advantages.append(
+ f"+{diff} pts Memory Encoding ({w_mem} vs {l_mem}) — "
+ + (f"Winner has {wvf['object_count']} objects vs {lvf['object_count']} — simpler composition encodes more cleanly. " if wvf["object_count"] < lvf["object_count"] else "")
+ + ("Winner text density is in optimal 5–25% range. " if 0.05 <= wvf["text_density"] <= 0.25 and not (0.05 <= lvf["text_density"] <= 0.25) else "")
+ + (f"Loser text density {lvf['text_density']*100:.0f}% overloads the verbal channel." if lvf["text_density"] > 0.25 else "")
+ )
+
+ if w_val > l_val + 0.1:
+ diff = round(w_val - l_val, 2)
+ advantages.append(
+ f"+{diff} Emotional Valence ({w_val:+.2f} vs {l_val:+.2f}) — "
+ + ("Winner has warmer color palette driving positive affect. " if w_val > 0 else "")
+ + ("Loser colors are dark/desaturated, encoding mild aversion. " if l_val < -0.05 else "")
+ + (f"Winner has {wvf['face_count']} face(s) adding affiliative warmth. " if wvf["face_count"] > lvf["face_count"] else "")
+ )
+
+ if load_rank[w_cl] < load_rank[l_cl]:
+ advantages.append(
+ f"Lower Cognitive Load ({w_cl} vs {l_cl}) — "
+ f"winner processes faster in scroll environments. "
+ f"Loser has {lvf['object_count']} objects + {lvf['text_density']*100:.0f}% text "
+ f"saturating working memory capacity."
+ )
+
+ # If winner leads on everything, add a clean sweep note
+ if not advantages:
+ advantages.append(
+ f"Narrow but consistent margin — winner outperforms by {cpci_gap} CPCi points "
+ f"across a combination of small improvements in contrast, simplicity, and color warmth."
+ )
+
+ adv_html = "".join(f"
{a}
" for a in advantages)
+ weak_html = (
+ " Note: " + " ".join(weaknesses) + ""
+ if weaknesses else ""
+ )
+
+ st.markdown(f"""
+
"
+ "This system is built on the Cognitive Signal Engine — "
+ "a proprietary framework that models how advertising stimuli are processed "
+ "across four dimensions:"
+ "
"
+
+ "
"
+
+ "
"
+ "01"
+ "
Attention"
+ " · Does it get noticed?
"
+ "
"
+
+ "
"
+ "02"
+ "
Memory"
+ " · Is it encoded?
"
+ "
"
+
+ "
"
+ "03"
+ "
Emotion"
+ " · Does it create affinity?
"
+ "
"
+
+ "
"
+ "04"
+ "
Cognitive Load"
+ " · Is it easy to process?
"
+ "
"
+
+ "
"
+
+ "
"
+ "These signals are combined into "
+ "CPCi — a unified measure of "
+ "cognitive effectiveness before media spend."
+ "
"
+ "A single, pre-spend measure of how effectively an ad creative engages "
+ "the human brain — across attention, memory, emotion, and processing ease."
+ "
"
+ "CTR, CVR, ROAS — measured after exposure. "
+ "They tell you what happened. They do not tell you why.
"
+ "By the time you have the data, budget has already been spent on creative "
+ "that failed at the first cognitive gate."
+ "
"
+ "
"
+
+ "
"
+ "
"
+ "CPCi
"
+ "
"
+ "Measured before exposure — at the visual signal layer. "
+ "Models what happens in the first 1–3 seconds: does the brain notice, "
+ "encode, and respond positively?
"
+ "Evaluate creative effectiveness before media spend. "
+ "Kill weak creatives before they consume budget."
+ "
"
+ "All three signals (attention, memory, emotion) clear their individual "
+ "thresholds. Cognitive Load Theory suggests low-load creative at these "
+ "levels is processed fluently — the necessary precondition for action."
+ "
"
+ "
"
+
+ "
"
+ "
40–69
"
+ "
"
+ "Optimise first
"
+ "
"
+ "Mixed signal profile — at least one dimension is below the encoding "
+ "floor. Paivio's Dual-Coding research indicates partial encoding leads "
+ "to poor recall and weak brand linkage over time."
+ "
"
+ "
"
+
+ "
"
+ "
<40
"
+ "
"
+ "Do not scale
"
+ "
"
+ "Insufficient cognitive engagement. Consistent with attention research "
+ "showing creatives below contrast and salience minimums are processed "
+ "at near-zero depth — spend is wasted before the message lands."
+ "
"
+ "
"
+
+ "
"
+ "
"
+
+ "
",
+ unsafe_allow_html=True,
+ )
+
+ st.markdown("", unsafe_allow_html=True)
+
+ # ── What is CPCi? ─────────────────────────────────────────────────────────
+ with st.expander("💡 What is CPCi? — Click to learn how scoring works", expanded=False):
+ col_a, col_b, col_c, col_d = st.columns(4)
+ col_a.markdown("""
+
+
What is CPCi?
+
+ CPCi is produced by the Cognitive Signal Engine —
+ a system that models how advertising creative is processed by the human brain,
+ before any click or conversion occurs.
+ Warm colours and human faces drive positive valence.
+
+
""", unsafe_allow_html=True)
+ st.markdown("""
+
+ How weights work:
+ CPCi formula is adjusted per use case — brand campaign prioritises memory,
+ performance campaign prioritises attention. See the 🧠 Science tab for the full formula.
+
""", unsafe_allow_html=True)
+
+ # ── Resolve mode flags from header toggles ───────────────────────────────
+ client_mode = not st.session_state.get("expert_mode", False)
+ demo_mode = st.session_state.get("demo_mode", False)
+
+ st.markdown("", unsafe_allow_html=True)
+
+ # ── Use-case selector ─────────────────────────────────────────────────────
+ uc_col, info_col = st.columns([1, 2])
+ with uc_col:
+ selected_uc = st.selectbox(
+ "🎯 Select Use Case",
+ options=list(USE_CASES.keys()),
+ index=1,
+ help="Changes CPCi weights to match your campaign objective",
+ )
+ uc_cfg = USE_CASES[selected_uc]
+ uc_weights = uc_cfg["weights"]
+ with info_col:
+ w = uc_weights
+ penalty_note = " · Load Penalty Active" if uc_cfg["load_penalty"] else ""
+ st.markdown(
+ f"
",
+ unsafe_allow_html=True,
+ )
+
+ col_l, col_r = st.columns([3, 2])
+
+ with col_l:
+ st.markdown(_block("The Model", "#40c4ff",
+ _ptr("#40c4ff", "Full name:", "Tri-modal Brain Imaging Encoding model v2") +
+ _ptr("#40c4ff", "Origin:", "Meta FAIR (Fundamental AI Research), 2025") +
+ _ptr("#40c4ff", "Authors:", "d'Ascoli, Rapin, Benchetrit, King et al.") +
+ _ptr("#40c4ff", "Paper:", "\"A foundation model of vision, audition, and language for in-silico neuroscience\"") +
+ _ptr("#40c4ff", "Type:", "Tri-modal transformer encoder — processes video + audio + text simultaneously")
+ ), unsafe_allow_html=True)
+
+ st.markdown(_block("What It Does", "#00e676",
+ _ptr("#00e676", "Core task:", "Predict human brain activity (fMRI) from any audio, visual, or language stimulus") +
+ _ptr("#00e676", "Input modalities:", "Video (V-JEPA embeddings) · Audio (W2V-BERT embeddings) · Text (LLaMA embeddings)") +
+ _ptr("#00e676", "Output:", "BOLD signal across 20,484 cortical + 8,802 subcortical brain locations") +
+ _ptr("#00e676", "Key innovation:", "One model — all stimuli types, all brain regions, zero-shot generalisation to new subjects")
+ ), unsafe_allow_html=True)
+
+ with col_r:
+ st.markdown(_block("Training Scale", "#ffb300",
+ _ptr("#ffb300", "Subjects:", "720 healthy volunteers") +
+ _ptr("#ffb300", "Sessions:", "5,094 scan sessions") +
+ _ptr("#ffb300", "fMRI hours:", "1,117 hours of brain recordings") +
+ _ptr("#ffb300", "Video:", "121 hours of stimulus video") +
+ _ptr("#ffb300", "Audio:", "142 hours of stimulus audio") +
+ _ptr("#ffb300", "Text sentences:", "71,000 transcribed sentences") +
+ _ptr("#ffb300", "Training GPU:", "128 × V100 GPUs for feature extraction")
+ ), unsafe_allow_html=True)
+
+ st.markdown(
+ "
"
+ "\"By aligning the representations of AI systems to those of the human brain, "
+ "we demonstrate that a single architecture can integrate a vast range "
+ "of fMRI responses… moving from the fragmented mapping of isolated cognitive tasks "
+ "toward a unified, predictive foundation model.\""
+ "
"
+ "Brain activation prediction model trained on 720 subjects and 1,117 hours of fMRI scans. "
+ "Given any visual, audio, or language stimulus, TRIBE v2 predicts which brain regions fire "
+ "— and at what intensity."
+ "
"
+ "What it tells us: "
+ "Which features of a creative cause the human brain to activate — "
+ "contrast, faces, text, emotional tone — and by how much."
+ "
"
+ "CPCi takes TRIBE v2's brain-region activation patterns and converts them into four "
+ "measurable signals — each one a direct media performance lever."
+ "
"
+ # Signal pills with contribution arrows
+ "
"
+
+ # Attention
+ "
"
+ "
"
+ "Attention
"
+ "
"
+ "Visual cortex activation → stopping power in feed
"
+ "
"
+
+ # Memory
+ "
"
+ "
"
+ "Memory
"
+ "
"
+ "Language area activity → brand recall after exposure
"
+ "
"
+
+ # Emotion
+ "
"
+ "
"
+ "Emotion
"
+ "
"
+ "Limbic/subcortical response → valence and purchase intent
"
+ "TRIBE v2 predicts responses across all of these regions simultaneously. "
+ "The cognitive signal thresholds in this tool are calibrated against TRIBE's "
+ "encoding score patterns — the same patterns that showed V1–V7 responding "
+ "to contrast and edges, the FaceBody area responding to faces, and language areas "
+ "competing with visual processing for working memory resources."
+ "
"
+ "TRIBE v2 (d'Ascoli et al., Meta FAIR 2025) is a multimodal brain encoding model trained on "
+ "720 subjects · 1,117h fMRI · 121h video stimuli. "
+ "It maps AI representations onto cortical brain activity, achieving rank #1 at Algonauts 2025 "
+ "(263 competing teams) with a mean encoding score of 0.2146.
"
+ "The TRIBE v2 brain data established which visual features activate which brain regions — "
+ "and at what magnitudes. This is the neuroscience foundation for the signal thresholds, "
+ "clutter penalties, face boosts, and text density curves in CPCi."
+ "
"
+ "
"
+ + _ptr("#3B82F6", "V1–V7 visual cortex", "validates contrast as the primary pre-attentive driver")
+ + _ptr("#3B82F6", "FaceBody area (lateral cortex)", "fires in <13ms — justifies face attention boost of +22 pts")
+ + _ptr("#3B82F6", "Language areas (left hemisphere)", "competes with visual memory — basis for text density sweet spot")
+ + _ptr("#3B82F6", "Limbic / subcortical", "colour and face affect — basis for emotional valence scoring")
+ + "
"
+ "
"
+ "Published: d'Ascoli S, Rapin J, Benchetrit Y, King J-R et al. · Meta FAIR 2025 · "
+ "Paper · "
+ "Weights (HuggingFace) · "
+ "License: CC BY-NC 4.0"
+ "
"
+ "Note on methodology: "
+ "TRIBE v2 provides the neuroscience foundation — it tells us which brain regions respond to "
+ "contrast, faces, and text, and at what relative magnitudes. "
+ "Anil Pandit's CPCi translates those brain-level insights into a fast, deterministic scoring system "
+ "using OpenCV + OCR features — no GPU required, no fMRI needed. "
+ "The signal thresholds and formula constants were calibrated against TRIBE v2's published encoding "
+ "scores and brain parcel activations, then validated against marketing effectiveness benchmarks. "
+ "CPCi does not run TRIBE v2 inference directly — it uses TRIBE v2's brain data as its calibration reference."
+ "
",
+ unsafe_allow_html=True,
+ )
+
+
+# ══════════════════════════════════════════════════════════════════════════════
+# TAB 2 — Glossary
+# ══════════════════════════════════════════════════════════════════════════════
+
+GLOSSARY = {
+ "Neuroscience & TRIBE": [
+ ("TRIBE v2",
+ "Tri-modal Brain Imaging Encoding model v2. A foundation model from Meta FAIR that predicts fMRI brain activity in response to video, audio, and text. Used as the neuroscience basis for CPCi signal calibration.",
+ "#40c4ff",
+ ["Meta FAIR 2025", "720 subjects", "1,117h fMRI", "#1 Algonauts 2025"]),
+
+ ("fMRI",
+ "Functional Magnetic Resonance Imaging. Measures changes in blood oxygenation across the brain. When a brain region is more active, it consumes more oxygen — fMRI detects this as a BOLD signal change.",
+ "#40c4ff",
+ ["Blood oxygen measurement", "~2s temporal resolution", "Whole-brain coverage"]),
+
+ ("BOLD Signal",
+ "Blood-Oxygen-Level-Dependent signal. The raw measurement from fMRI. Higher BOLD = higher neuronal activity. TRIBE v2 predicts BOLD across 20,484 cortical vertices and 8,802 subcortical voxels.",
+ "#40c4ff",
+ ["Neuronal activity proxy", "TRIBE predicts this", "20,484 cortical points"]),
+
+ ("Encoding Model",
+ "A model that predicts brain activity from sensory inputs. TRIBE v2 is an encoding model — it takes video/audio/text and outputs the expected BOLD signal. Encoding score = Pearson correlation between predicted and actual brain response.",
+ "#40c4ff",
+ ["Input → brain output", "Pearson correlation score", "Used in Algonauts"]),
+
+ ("Encoding Score",
+ "The accuracy metric for brain encoding models. Measured as Pearson correlation (r) between the model's predicted BOLD signal and the participant's actual fMRI signal. TRIBE v2 achieved rank #1 with mean score 0.2146 ± 0.0312.",
+ "#40c4ff",
+ ["Pearson correlation", "TRIBE: 0.2146 mean", "Higher = better prediction"]),
+
+ ("Cortical Vertices / Parcellation",
+ "The brain's surface is divided into 20,484 measurement points (vertices). The HCP atlas groups these into 360 functional parcels (brain regions) including visual cortex V1–V7, face-body areas, language regions, and subcortical zones.",
+ "#40c4ff",
+ ["360 HCP parcels", "V1–V7 visual regions", "FaceBody, Language areas"]),
+
+ ("Foundation Model",
+ "A large pre-trained AI model that can be applied to many downstream tasks without retraining from scratch. TRIBE v2 is a foundation model for neuroscience — one model predicts brain responses across all stimulus types and experimental conditions.",
+ "#40c4ff",
+ ["Pre-trained at scale", "Zero-shot generalisation", "One model, many tasks"]),
+
+ ("ICA (Independent Component Analysis)",
+ "A statistical technique to separate a multi-dimensional signal into independent components. Used in the TRIBE v2 paper to reveal that the model learned neuroscientifically meaningful brain patterns without being explicitly trained to do so.",
+ "#40c4ff",
+ ["Unsupervised decomposition", "Reveals latent structure", "Validates TRIBE learning"]),
+ ],
+
+ "Cognitive Science": [
+ ("Attention Score",
+ "A 0–100 measure of a creative's ability to capture and hold visual attention. Driven by visual contrast (50% weight), face presence (up to +36 pts), and clutter penalty (based on object count). Calibrated against V1–V7 visual cortex encoding.",
+ "#00e676",
+ ["> 60 = High Capture", "30–60 = Moderate", "< 30 = Scroll-Past Risk"]),
+
+ ("Memory Score",
+ "A 0–100 measure of how strongly a creative will be encoded into long-term memory. Based on composition simplicity (fewer objects = better) and dual-coding balance (text density 5–25% optimal). Maps to hippocampal encoding strength.",
+ "#00e676",
+ ["> 70 = Strong Signal", "40–70 = Moderate", "< 40 = Low Retention"]),
+
+ ("Emotional Valence",
+ "The positive/negative emotional charge a creative produces in the viewer. Measured −1.0 (strongly aversive) to +1.0 (strongly rewarding). Driven by face presence, dominant colour HSV properties, and composition warmth.",
+ "#00e676",
+ ["+1.0 = Rewarding", "0.0 = Neutral", "−1.0 = Aversive"]),
+
+ ("Cognitive Load",
+ "The total mental effort required to process a creative. Calculated as a composite of visual complexity (object count) and verbal load (text density). Low = effortless processing. High = working memory overload → brand message not retained.",
+ "#00e676",
+ ["Low = < 35 composite", "Medium = 35–60", "High = > 60 · −12 CPCi"]),
+
+ ("Pre-attentive Processing",
+ "Brain processing that happens automatically before conscious attention, typically within 200ms. Features processed pre-attentively: luminance contrast, colour, motion, face presence, orientation. Ads that pass this filter don't 'ask' for attention.",
+ "#00e676",
+ ["< 200ms response", "Before conscious choice", "Contrast + face = strongest"]),
+
+ ("Dual-Coding Theory",
+ "Allan Paivio's (1971) theory that the brain stores verbal information (text) and visual information (images) in separate memory systems. Content encoded in both systems is 2× more memorable. Source of the 5–25% text density guideline.",
+ "#00e676",
+ ["Paivio 1971", "Two memory channels", "5–25% text = sweet spot"]),
+
+ ("Cognitive Load Theory",
+ "John Sweller's (1988) theory that working memory has a fixed capacity. Extraneous load (visual clutter, excess copy) consumes that capacity, leaving less room for germane load (brand message encoding). Basis for the object-count thresholds.",
+ "#00e676",
+ ["Sweller 1988", "7 ± 2 items limit", "Clutter = capacity waste"]),
+
+ ("Russell Circumplex",
+ "James Russell's (1980) two-dimensional model of affect: Valence (pleasant/unpleasant) × Arousal (activated/deactivated). Emotional valence in this tool maps to the horizontal valence axis. Used to explain why colour and faces shift emotional response.",
+ "#00e676",
+ ["Russell 1980", "Valence + Arousal axes", "Predicts purchase intent"]),
+
+ ("Miller's Law",
+ "George Miller's (1956) finding that short-term memory holds 7 ± 2 chunks simultaneously. In ad creative, each distinct visual object is a 'chunk'. Beyond 7 objects, viewers sample randomly — the brand message may not be what gets sampled.",
+ "#00e676",
+ ["Miller 1956", "7 ± 2 chunks", "Object threshold basis"]),
+ ],
+
+ "Visual Detection": [
+ ("Object Detection (Contour Analysis)",
+ "TRIBE pipeline step that counts distinct visual objects using Canny edge detection, dilation, and contour analysis. Threshold: a contour must cover ≥ 0.1% of total image area to count. Outputs object_count.",
+ "#ffb300",
+ ["OpenCV Canny", "0.1% area threshold", "Proxy for visual clutter"]),
+
+ ("Face Detection (Haar Cascade)",
+ "OpenCV's Haar Cascade classifier detects frontal human faces. Settings: scaleFactor=1.1, minNeighbors=5, minSize=30×30px. Runs in ~50–200ms. Face presence is the single highest-return attention variable in ad creative.",
+ "#ffb300",
+ ["OpenCV Haar", "50–200ms", "+22 pts attention per face"]),
+
+ ("Text Density (OCR)",
+ "Tesseract OCR in PSM 11 sparse-text mode detects all text blocks regardless of layout. Text density = sum of text bounding box areas ÷ total image area. Confidence threshold ≥ 40% to filter noise.",
+ "#ffb300",
+ ["Tesseract PSM 11", "Confidence ≥ 40%", "0.0–1.0 ratio"]),
+
+ ("Contrast Score",
+ "Computed as the standard deviation of grayscale pixel values, normalised to 0–100 (max std = 127.5 for an 8-bit image). Low std = flat/washed out. High std = strong light-dark variation = pre-attentive pop-out.",
+ "#ffb300",
+ ["Std dev / 127.5 × 100", "0 = flat", "100 = max contrast"]),
+
+ ("Dominant Colours (K-means)",
+ "K-means clustering (k=3) on a 100×100 downscaled image identifies the 3 most dominant colours by pixel count. Sorted by frequency. Converted to RGB hex strings. Used for valence calculation via HSV colour psychology.",
+ "#ffb300",
+ ["K-means k=3", "100×100 downsample", "Sorted by frequency"]),
+
+ ("HSV Colour Space",
+ "Hue-Saturation-Value colour representation. Used for emotion classification instead of RGB because HSV separates colour identity (hue) from brightness (value) more naturally. Warm hues (0–60°) = positive valence. Dark (V < 0.20) = negative.",
+ "#ffb300",
+ ["Hue 0–360°", "Saturation 0–1", "Value 0–1 (brightness)"]),
+ ],
+
+ "Media & Advertising": [
+ ("CPCi (Creative Performance Composite Index)",
+ "A 0–100 weighted score combining Attention, Memory, and Emotional Valence, calibrated to the specific cognitive demands of a given use case. Higher CPCi = stronger cognitive impact = better predicted media efficiency.",
+ "#ff80ab",
+ ["0–100 scale", "Use-case weighted", "Predicts media ROI"]),
+
+ ("Thumb-Stop Rate",
+ "The percentage of users who stop scrolling when they encounter an ad. Directly correlated with Attention Score. Low attention ads produce low thumb-stop rates, which the platform algorithm interprets as low quality, increasing effective CPM.",
+ "#ff80ab",
+ ["Attention proxy", "Algorithm quality signal", "Determines CPM cost"]),
+
+ ("Brand Salience",
+ "How quickly and easily a brand comes to mind in buying situations. Built through consistent memory encoding over multiple exposures. High Memory Score creatives build salience faster and with fewer exposures required.",
+ "#ff80ab",
+ ["Byron Sharp concept", "Built via Memory Score", "Drives shelf recognition"]),
+
+ ("Effective CPM",
+ "The true cost per 1,000 impressions accounting for placement quality. Low-attention creatives inflate effective CPM because the platform algorithm bids them into lower-quality inventory with lower engagement.",
+ "#ff80ab",
+ ["True cost metric", "Rises with low attention", "CPCi predicts direction"]),
+
+ ("Brand Lift",
+ "An increase in brand awareness, preference, or purchase intent measured by surveys after campaign exposure. CPCi scores above 70 historically correlate with 3–5 percentage point brand lift per campaign.",
+ "#ff80ab",
+ ["Survey-measured", "CPCi > 70 = 3–5pt lift", "Gold standard KPI"]),
+
+ ("Frequency Capping",
+ "Setting a maximum number of times a user sees the same ad. High Memory Score creatives need lower frequency (3–5 impressions) to achieve the same recall as low-memory creatives (6–9 impressions needed). Impacts media budget efficiency.",
+ "#ff80ab",
+ ["3–5 for high memory", "6–9 for low memory", "Controls wear-out"]),
+
+ ("Cognitive Load Penalty",
+ "A CPCi deduction applied in Retail Media contexts only: −0 for Low load, −5 for Medium, −12 for High. Applied because retail environments already impose high cognitive load on shoppers — an ad that adds to it will not convert.",
+ "#ff80ab",
+ ["Retail Media only", "−0 / −5 / −12", "Reflects real-world clutter"]),
+
+ ("Quality Score",
+ "The ad quality metric used by platforms (Google, Meta) that determines ad auction ranking and CPM. Creatives with high engagement (driven by attention and positive emotion) receive better Quality Scores, lowering cost per click.",
+ "#ff80ab",
+ ["Platform auction metric", "Attention → higher score", "Lowers CPC directly"]),
+ ],
+}
+
+
+def show_glossary_tab() -> None:
+ st.markdown(SCI_CSS, unsafe_allow_html=True)
+
+ st.markdown(
+ "
"
+ "📖 Glossary of Terms
"
+ "
"
+ "Every technical term used in the reports, scores, and methodology — explained for marketers.
",
+ unsafe_allow_html=True,
+ )
+
+ for category, terms in GLOSSARY.items():
+ with st.expander(f"{'🔬' if 'Neuro' in category else '🧩' if 'Cognitive' in category else '👁' if 'Visual' in category else '📢'} {category} — {len(terms)} terms", expanded=False):
+ cols = st.columns(2)
+ for idx, (term, definition, color, tags) in enumerate(terms):
+ tag_html = " ".join(f"{t}" for t in tags)
+ cols[idx % 2].markdown(
+ f"