// ─── FIELD RESULTS / CASE STUDIES ─────────────────────────────────────────────
// Showcases real deployments, testimonials, and press coverage of the WRAP®
// Non-Lethal Response™ System. Mirrors the UseCasesPage architecture: a data
// array (CASE_STUDIES) + list page + detail page, hash-routed via navigate().
//
// CONTENT RULES (do not remove — they encode brand/legal guardrails):
//  • Press items LINK OUT to the original publication. We do not republish or
//    iframe third-party article bodies (IP + CSP).
//  • Any statistic that originates from a publication is attributed to that
//    publication in its own card (e.g. "Circuit Magazine reports…"). Do NOT
//    restate third-party or investor-sourced numbers in WRAP's own voice on
//    this page. New WRAP-voice stats must be IR/legal-approved first.
//  • Entries marked `placeholder: true` are scaffolding — replace with real,
//    approved content and delete the flag. Nothing here should be invented fact.

const { useState: useCSState, useEffect: useCSEffect } = React;

// ─── DATA ─────────────────────────────────────────────────────────────────────
// type: 'press' | 'video' | 'testimonial'
const CASE_STUDIES = [
    // ── Real, seeded item ───────────────────────────────────────────────────────
    {
        id: 'circuit-the-liability-gap',
        type: 'thought-leadership',
        pinned: 1,
        kicker: 'Thought Leadership',
        source: 'Circuit Magazine',
        date: 'May 2026',
        title: 'The Liability Gap',
        summary:
            'A litigation attorney and use-of-force expert examines why federal courts have narrowed when conducted-energy devices can lawfully be used — and where the BolaWrap® 150 fits as a non-pain-compliance restraint option for law enforcement and security agencies.',
        // Stat is presented ONLY as the publication's reporting, never as WRAP's claim.
        attributedStat: {
            value: '92%',
            claim: 'field success rate reported',
            credit: 'As reported by Circuit Magazine, citing Wrap Technologies',
        },
        author: 'Kevin R. Madison, JD, CPP, PCI',
        authorImage: 'assets/press/kevin-madison.jpg',
        href: 'https://www.circuit-magazine.com/p/the-liability-gap',
        image: 'assets/press/circuit-liability-gap.jpg',
        placeholder: false,
    },

    // ── Field report: real deployment, agency-approved for publication ───────────
    // Mounds View PD approved WRAP highlighting this as a customer success story.
    // Dignity guardrail: name the agency + tell the de-escalation story; describe
    // the subject by situation, not as a catalogued individual (no standalone
    // age/gender data points). Set `published: false` to hide this card without
    // removing it (e.g. if the agency withdraws consent).
    {
        id: 'mounds-view-field-report',
        type: 'field-report',
        kicker: 'Hero Story',
        source: 'Mounds View Police Department',
        date: 'July 2026',
        title: 'A Safe Stop Without Pain or Escalation',
        summary:
            'Responding to a theft call, officers faced an intoxicated subject walking away and ignoring repeated commands. A single low BolaWrap® 150 deployment stopped the subject immediately — no strikes, no pain compliance. The person was safely taken into custody with handcuffs alone, and no further force was needed.',
        // Detail-page fields
        agencyDetail: {
            agency: 'Mounds View Police Department',
            location: 'Minnesota',
            natureOfCall: 'Theft',
            outcome: 'Subject safely restrained; handcuffs only; no further force required',
            reportedBy: 'Submitted by the agency via WRAP® Field Use Report',
        },
        href: null,
        image: 'assets/press/mounds-view-field-use.jpg',
        published: true,
        placeholder: false,
    },

    // ── Field report: Millbury PD (MA) — two incidents, agency-recognized ────────
    {
        id: 'millbury-field-report',
        type: 'field-report',
        kicker: 'Hero Story',
        source: 'Millbury Police Department',
        date: '2023',
        title: 'Two De-escalations, Two Peaceful Endings',
        summary:
            'Officers Mike O\u2019Leary and Timothy Dean each turned to the BolaWrap® 150 when verbal commands failed — resolving two potentially life-threatening calls without further force. Both subjects were safely restrained and transported for care.',
        agencyDetail: {
            agency: 'Millbury Police Department',
            location: 'Massachusetts',
            natureOfCall: 'Welfare / crisis response (two incidents)',
            outcome: 'Both subjects safely restrained and transported to hospital; no further force required',
            reportedBy: 'Recognized by Chief Brian Lewos via the WRAP® Deployment Recognition Program',
        },
        incidents: [
            'Feb 24, 2023 — Officer O\u2019Leary tracked a subject who had fled into the woods after a self-harm report. When verbal commands were ignored, a BolaWrap® 150 deployment wrapped the subject\u2019s legs and she was immediately placed in handcuffs, then transported safely to the hospital.',
            'Mar 7, 2023 — Officer Dean responded to a subject in crisis making threats. After repeated verbal commands were ignored, Officer Dean deployed the BolaWrap® 150; it had its desired effect and officers ended the encounter peacefully. The subject was transported safely to the hospital.',
        ],
        href: null,
        image: 'assets/press/millbury-field-report.jpg',
        published: true,
        placeholder: false,
    },

    // ── Field report: Quincy PD (MA) — EDP with knife ───────────────────────────
    {
        id: 'quincy-field-report',
        type: 'field-report',
        kicker: 'Hero Story',
        source: 'Quincy Police Department',
        date: '2023',
        title: 'De-escalating a High-Risk Crisis Call',
        summary:
            'Responding to an emotionally disturbed person armed with a knife and advancing on officers, Quincy PD deployed the BolaWrap® 150 to the subject\u2019s lower legs — restraining and distracting him so the situation could be resolved quickly, with a safe outcome for everyone involved.',
        agencyDetail: {
            agency: 'Quincy Police Department',
            location: 'Massachusetts',
            natureOfCall: 'Emotionally disturbed person, armed',
            outcome: 'Subject safely restrained; situation de-escalated without escalation of force',
            reportedBy: 'Officer Ronald McKim recognized by Captain Daniel Guarante via the WRAP® Deployment Recognition Program',
        },
        href: null,
        image: 'assets/press/quincy-field-report.jpg',
        published: true,
        placeholder: false,
    },

    // ── Field report: Socorro PD (TX) — two incidents ───────────────────────────
    {
        id: 'socorro-field-report',
        type: 'field-report',
        kicker: 'Hero Story',
        source: 'Socorro Police Department',
        date: '2023',
        title: 'Restraint Without Escalation, Twice Over',
        summary:
            'In two separate crisis calls, Socorro officers used the BolaWrap® 150 to safely restrain subjects — including one experiencing a mental-health crisis with a history of self-harm — allowing officers to prevent harm and connect each person with the help they needed.',
        agencyDetail: {
            agency: 'Socorro Police Department',
            location: 'Texas',
            natureOfCall: 'Mental-health crisis; armed disturbance (two incidents)',
            outcome: 'Both subjects safely restrained; mental-health specialists engaged; no serious injuries',
            reportedBy: 'Officers Dante Hinojos and Esteban Sifuentes recognized via the WRAP® Deployment Recognition Program',
        },
        incidents: [
            'Sept 11, 2023 — Officer Hinojos responded to a subject in a mental-health crisis with a history of self-harm. After de-escalation attempts, a BolaWrap® 150 deployment immobilized the subject; the officer secured the subject\u2019s arm to prevent self-harm and placed them in handcuffs, ending the incident safely.',
            'Oct 5, 2023 — Officer Sifuentes responded to an armed disturbance. When commands were ignored and the situation escalated, a BolaWrap® 150 deployment restrained the subject; officers then safely subdued them, and a mental-health specialist provided evaluation.',
        ],
        href: null,
        image: 'assets/press/socorro-field-report.jpg',
        published: true,
        placeholder: false,
    },

    // ── Testimonials ─────────────────────────────────────────────────────────────
    {
        id: 'lagrange-testimonial',
        type: 'testimonial',
        kicker: 'Testimonial',
        source: 'LaGrange Police Department',
        date: 'Georgia',
        title: 'Respectful, Humane Outcomes',
        summary:
            'LaGrange PD (Georgia) on building de-escalation into everyday policing — and recognizing officers who resolve encounters without resorting to force.',
        quote:
            'We try to provide our officers with as many tools and resources as possible so that all encounters with citizens — even those where someone may need to be taken into custody — are done in a respectful and humane way.',
        attribution: 'LaGrange Police Department, Georgia',
        href: null,
        image: 'assets/press/lagrange-testimonial.jpg',
        published: true,
        placeholder: false,
    },
    {
        id: 'socorro-testimonial',
        type: 'testimonial',
        kicker: 'Testimonial',
        source: 'Socorro Police Department',
        date: 'Texas',
        title: 'A Safer, More Effective Tool',
        summary:
            'Socorro PD (Texas) on how the BolaWrap® 150 has reduced serious injuries during use-of-force situations in their department.',
        quote:
            'It has been a great incentive... a safer and more effective tool in policing. Administration has been pleased with its effectiveness in the reduction of serious injuries during a use of force.',
        attribution: 'A. Castaneda, Socorro Police Department, Texas',
        href: null,
        image: 'assets/press/socorro-testimonial.jpg',
        published: true,
        placeholder: false,
    },
    {
        id: 'calstateparks-testimonial',
        type: 'testimonial',
        kicker: 'Testimonial',
        source: 'California State Parks Police',
        date: 'California',
        title: 'Acknowledging Discretion and Tactics',
        summary:
            'California State Parks Police on recognizing officers who use discretion and sound tactics to resolve problems safely.',
        quote:
            'A good method to acknowledge police officers using discretion and tactics to address problems.',
        attribution: 'California State Parks Police Department',
        href: null,
        image: 'assets/press/calstateparks-testimonial.jpg',
        published: true,
        placeholder: false,
    },
    {
        id: 'lexington-testimonial',
        type: 'testimonial',
        kicker: 'Testimonial',
        source: 'Lexington County Sheriff\u2019s Office',
        date: 'South Carolina',
        title: 'Training on the Why and When',
        summary:
            'Lexington County Sheriff\u2019s Office (South Carolina) on shifting training toward the judgment of when and why to deploy — not just how.',
        quote:
            'I am going to really focus on our new training philosophy — the why and when instead of so much on the how — and use this program as an incentive.',
        attribution: 'Lexington County Sheriff\u2019s Office, South Carolina',
        href: null,
        image: 'assets/press/lexington-testimonial.jpg',
        published: true,
        placeholder: false,
    },

    // ── Video: confirmed real bodycam deployment ────────────────────────────────
    {
        id: 'bodycam-indecent-exposure',
        type: 'video',
        pinned: 3,
        kicker: 'Bodycam Deployment',
        source: 'WRAP® Bodycam Footage',
        date: '',
        title: 'BolaWrap® 150 Bodycam Explainer',
        // Dignity guardrail: describe the de-escalation and safe outcome, not the
        // underlying offense. Kept factual and non-sensational.
        summary:
            'Bodycam footage walking through a real BolaWrap® 150 deployment — showing how remote entanglement let officers safely restrain a non-compliant subject from a distance, without strikes or pain compliance.',
        videoHref: 'https://www.youtube.com/watch?v=ZkdVZWtXjxU',
        image: 'assets/video/bodycam-indecent-exposure.jpg',
        placeholder: false,
    },
    // ── Video: ATF regulatory classification (news, not a deployment) ────────────
    // NOTE: touches a federal regulatory/legal classification (ATF Ruling 2026-2).
    // Copy states only what the ruling determined; route to IR/legal if reworded.
    {
        id: 'atf-ruling-not-a-firearm',
        type: 'video',
        pinned: 2,
        kicker: 'Announcement',
        source: 'WRAP®',
        date: '',
        title: 'ATF Rules the BolaWrap® 150 Is Not a Firearm or Weapon',
        summary:
            'An overview of the ATF determination classifying the BolaWrap® 150 as an instrument of restraint rather than a firearm or weapon under federal law, and what that means for agencies.',
        videoHref: 'https://www.youtube.com/shorts/J7uZydRMLPg',
        image: 'assets/video/atf-ruling.jpg',
        placeholder: false,
    },
    // ── Video: capability demonstration ──────────────────────────────────────────
    {
        id: 'controlled-disruption-distance',
        type: 'video',
        kicker: 'How It Works',
        source: 'WRAP®',
        date: '',
        title: 'Controlled Disruption From Outside Contact Distance',
        summary:
            'A short demonstration of what sets the BolaWrap® 150 apart: rather than closing distance, it lets officers gain control from beyond contact range — creating time and space to de-escalate.',
        videoHref: 'https://www.youtube.com/shorts/TfowY2LIYcs',
        image: 'assets/video/controlled-disruption.jpg',
        placeholder: false,
    },
    // ── Video: training ──────────────────────────────────────────────────────────
    {
        id: 'training-when-how-why',
        type: 'video',
        kicker: 'Training',
        source: 'WRAP®',
        date: '',
        title: 'Why Training Is What Makes It Stick',
        summary:
            'On the WRAP® approach to training: learning when to deploy, how to deploy, and why — the judgment layer that turns a tool into a reliable, policy-aligned response option.',
        videoHref: 'https://www.youtube.com/shorts/cNbFsipvG5Q',
        image: 'assets/video/training.jpg',
        placeholder: false,
    },
];

const TYPE_META = {
    press: { label: 'Article', cta: 'Read the full article' },
    'thought-leadership': { label: 'Thought Leadership', cta: 'Read the full article' },
    video: { label: 'Video', cta: 'Watch the video' },
    testimonial: { label: 'Testimonial', cta: 'Read the story' },
    'field-report': { label: 'Hero Story', cta: 'Read the deployment' },
};

// Visibility flag: a card is shown unless it explicitly sets published:false.
// Lets any entry be hidden without deleting it (e.g. if an agency withdraws
// consent). Also gates deep-links so a hidden card's URL falls back to the list.
function isPublished(item) {
    return item && item.published !== false;
}
// Only these entries are ever rendered / deep-linkable.
const VISIBLE_CASE_STUDIES = CASE_STUDIES.filter(isPublished);

// Ordering for the "All" view:
//  1. Pinned entries first, in their explicit `pinned` order (1,2,3…).
//  2. Then the rest, interlaced so types alternate instead of clustering.
//     Videos are front-loaded; 2023 field reports are pushed toward the end.
// Type-specific filters keep source order (no reordering within a single type).
function orderStories(list) {
    const pinned = list.filter(c => c.pinned).sort((a, b) => a.pinned - b.pinned);
    const rest = list.filter(c => !c.pinned);

    // Bucket the remainder by type, preserving each type's source order.
    const buckets = {};
    rest.forEach(c => { (buckets[c.type] = buckets[c.type] || []).push(c); });

    // Round-robin drain in this type priority (videos first; field-report last so
    // the 2023 reports trail the fresher content). Interlacing = one from each
    // type per pass, so the grid alternates instead of showing five reports in a row.
    const order = ['video', 'thought-leadership', 'testimonial', 'press', 'field-report'];
    const woven = [];
    let remaining = true;
    while (remaining) {
        remaining = false;
        for (const t of order) {
            if (buckets[t] && buckets[t].length) { woven.push(buckets[t].shift()); remaining = true; }
        }
    }
    return [...pinned, ...woven];
}
const ORDERED_CASE_STUDIES = orderStories(VISIBLE_CASE_STUDIES);

// ─── STRUCTURED DATA (JSON-LD) ────────────────────────────────────────────────
// Emits schema.org markup so search engines AND AI/LLM crawlers can index the
// Real Stories as structured entities. Injected into <head> at runtime.
//
// IMPORTANT (honest limitation): this site is a client-side hash-routed SPA, so
// JS-injected JSON-LD is read by crawlers that execute JavaScript (e.g. Google)
// but NOT by crawlers that only parse raw HTML, and hash-route detail pages are
// not indexed as separate URLs. Full per-page structured data / SSR lands with
// the planned Next.js migration. Until then this gives the one real indexable
// URL (the list page) a complete, machine-readable manifest of every story.
const SITE_ORIGIN = 'https://www.wrap.com';
const STORIES_URL = SITE_ORIGIN + '/#/case-studies';

function storyToSchema(item) {
    const url = `${SITE_ORIGIN}/#/case-studies/${item.id}`;
    const image = item.image ? `${SITE_ORIGIN}/${item.image}` : undefined;
    const base = { '@type': 'CreativeWork', name: item.title, headline: item.title,
        description: item.summary, url, image, inLanguage: 'en-US',
        isPartOf: { '@type': 'CollectionPage', name: 'Real Stories', url: STORIES_URL } };

    if (item.type === 'video') {
        return { ...base, '@type': 'VideoObject',
            thumbnailUrl: image, contentUrl: item.videoHref, embedUrl: item.videoHref,
            uploadDate: '2026-01-01', publisher: { '@type': 'Organization', name: 'WRAP Technologies' } };
    }
    if (item.type === 'thought-leadership' || item.type === 'press') {
        return { ...base, '@type': 'Article',
            author: item.author ? { '@type': 'Person', name: item.author } : undefined,
            publisher: { '@type': 'Organization', name: item.source },
            mainEntityOfPage: item.href || url };
    }
    if (item.type === 'testimonial') {
        return { '@type': 'Review', url, name: item.title,
            reviewBody: item.quote || item.summary,
            author: { '@type': 'Organization', name: item.source },
            itemReviewed: { '@type': 'Product', name: 'BolaWrap 150',
                brand: { '@type': 'Brand', name: 'WRAP Technologies' } } };
    }
    if (item.type === 'field-report') {
        return { ...base, '@type': 'Report',
            about: { '@type': 'Product', name: 'BolaWrap 150' },
            sourceOrganization: { '@type': 'Organization', name: item.source },
            spatialCoverage: item.agencyDetail && item.agencyDetail.location };
    }
    return base;
}

function buildRealStoriesJsonLd() {
    const itemList = {
        '@context': 'https://schema.org', '@type': 'ItemList',
        name: 'WRAP Real Stories — Deployments, Testimonials & Coverage',
        description: 'Real-world BolaWrap 150 deployments, agency testimonials, thought leadership, and independent coverage of the WRAP Non-Lethal Response System.',
        url: STORIES_URL,
        numberOfItems: ORDERED_CASE_STUDIES.length,
        itemListElement: ORDERED_CASE_STUDIES.map((item, i) => ({
            '@type': 'ListItem', position: i + 1, url: `${SITE_ORIGIN}/#/case-studies/${item.id}`,
            item: storyToSchema(item),
        })),
    };
    return itemList;
}

// Inject (or replace) a JSON-LD <script> in <head> keyed by id; removed on cleanup.
function useJsonLd(id, dataFactory, deps) {
    useCSEffect(() => {
        const el = document.createElement('script');
        el.type = 'application/ld+json';
        el.id = id;
        el.text = JSON.stringify(dataFactory());
        const prev = document.getElementById(id);
        if (prev) prev.remove();
        document.head.appendChild(el);
        return () => { const n = document.getElementById(id); if (n) n.remove(); };
    }, deps || []);
}

// ─── SMALL UI HELPERS ─────────────────────────────────────────────────────────
// Parse a YouTube watch/shorts URL into a privacy-enhanced embed URL.
// youtube-nocookie.com sets no tracking cookies until the user hits play, which
// keeps the embed out of the cookie-consent gate. Returns null if the URL isn't
// a recognized YouTube link (so we fall back to a plain outbound link).
// NOTE: the youtube-nocookie.com origin must be allowed in the frame-src
// directive of the Content-Security-Policy in vercel.json, or the iframe is
// silently blocked in production.
function parseYouTube(url) {
    if (!url) return null;
    let id = null, isShort = false;
    try {
        const u = new URL(url);
        if (!/(^|\.)youtube\.com$/.test(u.hostname) && u.hostname !== 'youtu.be') return null;
        if (u.hostname === 'youtu.be') {
            id = u.pathname.slice(1);
        } else if (u.pathname.startsWith('/shorts/')) {
            id = u.pathname.split('/')[2];
            isShort = true;
        } else if (u.pathname === '/watch') {
            id = u.searchParams.get('v');
        } else if (u.pathname.startsWith('/embed/')) {
            id = u.pathname.split('/')[2];
        }
    } catch (e) {
        return null;
    }
    if (!id || !/^[A-Za-z0-9_-]{6,}$/.test(id)) return null;
    return { embedUrl: `https://www.youtube-nocookie.com/embed/${id}`, isShort };
}

function TypeTag({ type }) {
    const meta = TYPE_META[type] || { label: 'Story' };
    return (
        <span style={{
            display: 'inline-block', fontSize: 11, fontWeight: 700, letterSpacing: '0.12em',
            textTransform: 'uppercase', color: 'var(--accent)', border: '1px solid rgba(255,181,5,0.4)',
            borderRadius: 3, padding: '4px 9px', fontFamily: 'DM Sans',
        }}>{meta.label}</span>
    );
}

function CardThumb({ item }) {
    // Uniform 16:9 media area. Uses a real image when provided, otherwise a
    // branded placeholder block (no invented imagery).
    return (
        <div style={{
            position: 'relative', width: '100%', aspectRatio: '16 / 9',
            background: item.image ? `#000 url("${item.image}") center/cover no-repeat` : 'var(--bg-3)',
            borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
            {!item.image && (
                <span style={{ fontSize: 12, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--text-muted)', fontFamily: 'DM Sans' }}>
          {item.type === 'video' ? '▶  Video' : 'WRAP®'}
        </span>
            )}
            {item.type === 'video' && item.image && (
                <span style={{
                    position: 'absolute', width: 54, height: 54, borderRadius: '50%',
                    background: 'rgba(255,181,5,0.92)', color: '#060c1c', display: 'flex',
                    alignItems: 'center', justifyContent: 'center', fontSize: 20,
                }}>▶</span>
            )}
        </div>
    );
}

// Author byline with circular avatar. `size` controls avatar diameter; falls
// back to initials on a branded disc if no image is provided.
function AuthorByline({ name, image, size = 28, nameStyle, prefix }) {
    if (!name) return null;
    const initials = name.replace(/,.*$/, '').split(/\s+/).filter(Boolean).slice(0, 2).map(w => w[0]).join('').toUpperCase();
    return (
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            {image ? (
                <img src={image} alt={name} width={size} height={size}
                     style={{ width: size, height: size, borderRadius: '50%', objectFit: 'cover', border: '1px solid var(--border)', flexShrink: 0 }} />
            ) : (
                <span style={{ width: size, height: size, borderRadius: '50%', background: 'var(--bg-3)', color: 'var(--accent)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: Math.round(size * 0.38), fontWeight: 700, fontFamily: 'DM Sans', flexShrink: 0 }}>{initials}</span>
            )}
            <span style={nameStyle || { fontSize: 13, color: 'var(--text-sub)', fontFamily: 'DM Sans' }}>{prefix ? `${prefix} ${name}` : name}</span>
        </div>
    );
}

// ─── LIST CARD ────────────────────────────────────────────────────────────────
function CSCard({ item, onClick }) {
    const [hover, setHover] = useCSState(false);
    return (
        <div
            onClick={() => onClick(item.id)}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            style={{
                background: 'var(--card-bg)', border: '1px solid',
                borderColor: hover ? 'rgba(255,181,5,0.4)' : 'var(--border)',
                borderRadius: 6, overflow: 'hidden', cursor: 'pointer',
                transition: 'border-color 0.2s, transform 0.2s',
                transform: hover ? 'translateY(-2px)' : 'none', display: 'flex', flexDirection: 'column',
            }}
        >
            <CardThumb item={item} />
            <div style={{ padding: '20px 22px 24px', display: 'flex', flexDirection: 'column', flex: 1 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
                    <TypeTag type={item.type} />
                    <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'DM Sans' }}>{item.source}</span>
                </div>
                <div style={{ fontFamily: 'Barlow Condensed', fontWeight: 700, fontSize: 24, lineHeight: 1.05, textTransform: 'uppercase', color: 'var(--text)', marginBottom: 10 }}>{item.title}</div>
                <div style={{ fontSize: 14, color: 'var(--text-sub)', lineHeight: 1.55, marginBottom: 16, flex: 1 }}>{item.summary}</div>
                {item.author && (
                    <div style={{ marginBottom: 14 }}>
                        <AuthorByline name={item.author} image={item.authorImage} size={26} nameStyle={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'DM Sans' }} />
                    </div>
                )}
                <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--accent)', fontFamily: 'DM Sans' }}>{(TYPE_META[item.type] || {}).cta} →</div>
            </div>
        </div>
    );
}

// ─── DETAIL PAGE ──────────────────────────────────────────────────────────────
function CSDetail({ csId, navigate, isMobile }) {
    const item = VISIBLE_CASE_STUDIES.find(c => c.id === csId);
    useCSEffect(() => {
        if (item) document.title = `${item.title} — WRAP® Non-Lethal Response™`;
    }, [item]);

    if (!item) {
        // Unknown id → fall back to the list, matching the site's routing philosophy.
        return <CaseStudiesList navigate={navigate} isMobile={isMobile} />;
    }

    const meta = TYPE_META[item.type] || {};
    const outHref = item.href || item.videoHref;
    const yt = item.type === 'video' ? parseYouTube(item.videoHref) : null;

    return (
        <main style={{ paddingTop: 72 }}>
            <section style={{ background: 'var(--bg)', padding: isMobile ? '48px 20px' : '80px 48px', borderBottom: '1px solid var(--border)', position: 'relative', overflow: 'hidden' }}>
                <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 3, background: 'linear-gradient(to right, var(--accent), transparent)' }} />
                <div style={{ maxWidth: 900, margin: '0 auto' }}>
                    <button onClick={() => navigate('case-studies')} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: 13, fontFamily: 'DM Sans', padding: 0, marginBottom: 32 }}>← All Real Stories</button>

                    <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20, flexWrap: 'wrap' }}>
                        <TypeTag type={item.type} />
                        <span style={{ fontSize: 13, color: 'var(--text-muted)', fontFamily: 'DM Sans' }}>{item.source}{item.date ? ` · ${item.date}` : ''}</span>
                    </div>

                    <h1 style={{ fontFamily: 'Barlow Condensed', fontWeight: 900, fontSize: 'clamp(40px, 6vw, 76px)', textTransform: 'uppercase', color: 'var(--text)', lineHeight: 0.92, marginBottom: 24 }}>{item.title}</h1>

                    {item.author && (
                        <div style={{ marginBottom: 24 }}>
                            <AuthorByline name={item.author} image={item.authorImage} size={40} prefix="By" nameStyle={{ fontSize: 14, color: 'var(--text-sub)', fontFamily: 'DM Sans' }} />
                        </div>
                    )}

                    {/* Hero image — shown for non-video items (press, testimonial) that
              have one. Video detail pages skip this: the embedded player below
              is the visual, so a static poster would be redundant. */}
                    {item.image && item.type !== 'video' && (
                        <div style={{
                            width: '100%', aspectRatio: '16 / 9', margin: '0 0 32px',
                            background: `#000 url("${item.image}") center/cover no-repeat`,
                            border: '1px solid var(--border)', borderRadius: 8,
                        }} role="img" aria-label={item.title} />
                    )}

                    <p style={{ fontSize: 18, lineHeight: 1.7, color: 'var(--text-sub)', maxWidth: 680, marginBottom: 32 }}>{item.summary}</p>

                    {/* Testimonial quote block */}
                    {item.type === 'testimonial' && item.quote && (
                        <div style={{ borderLeft: '3px solid var(--accent)', paddingLeft: 22, margin: '0 0 32px' }}>
                            <div style={{ fontStyle: 'italic', fontSize: 20, lineHeight: 1.6, color: 'var(--text)' }}>"{item.quote}"</div>
                            {item.attribution && <div style={{ fontSize: 13, color: 'var(--text-muted)', marginTop: 12, fontFamily: 'DM Sans' }}>— {item.attribution}</div>}
                        </div>
                    )}

                    {/* Field report — agency facts panel */}
                    {item.agencyDetail && (
                        <div style={{ background: 'var(--card-bg)', border: '1px solid var(--border)', borderRadius: 8, padding: isMobile ? '22px' : '26px 30px', marginBottom: 32 }}>
                            <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 16 }}>
                                {[
                                    ['Agency', item.agencyDetail.agency],
                                    ['Location', item.agencyDetail.location],
                                    ['Nature of call', item.agencyDetail.natureOfCall],
                                    ['Outcome', item.agencyDetail.outcome],
                                ].filter(([, v]) => v).map(([label, value]) => (
                                    <div key={label}>
                                        <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--accent)', marginBottom: 4, fontFamily: 'DM Sans' }}>{label}</div>
                                        <div style={{ fontSize: 15, color: 'var(--text)', fontFamily: 'DM Sans', lineHeight: 1.5 }}>{value}</div>
                                    </div>
                                ))}
                            </div>
                            {item.agencyDetail.reportedBy && (
                                <div style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'DM Sans', marginTop: 18, borderTop: '1px solid var(--border)', paddingTop: 14 }}>{item.agencyDetail.reportedBy}</div>
                            )}
                        </div>
                    )}

                    {/* Field report — individual incident narratives */}
                    {item.incidents && item.incidents.length > 0 && (
                        <div style={{ marginBottom: 32 }}>
                            <h3 style={{ fontFamily: 'Barlow Condensed', fontWeight: 800, fontSize: 24, textTransform: 'uppercase', color: 'var(--text)', marginBottom: 16 }}>The Deployments</h3>
                            {item.incidents.map((text, i) => (
                                <div key={i} style={{ display: 'flex', gap: 14, marginBottom: 16 }}>
                                    <div style={{ flexShrink: 0, width: 3, background: 'var(--accent)', borderRadius: 2 }} />
                                    <p style={{ fontSize: 15, lineHeight: 1.65, color: 'var(--text-sub)', margin: 0 }}>{text}</p>
                                </div>
                            ))}
                        </div>
                    )}

                    {/* Stat — shown ONLY with its publication attribution */}
                    {item.attributedStat && (
                        <div style={{ background: 'var(--card-bg)', border: '1px solid var(--border)', borderRadius: 8, padding: isMobile ? '24px' : '28px 32px', marginBottom: 32, display: 'flex', gap: 20, alignItems: 'center', flexWrap: 'wrap' }}>
                            <div style={{ fontFamily: 'Barlow Condensed', fontWeight: 900, fontSize: 56, color: 'var(--accent)', lineHeight: 1 }}>{item.attributedStat.value}</div>
                            <div>
                                <div style={{ fontSize: 15, color: 'var(--text)', fontFamily: 'DM Sans', marginBottom: 4 }}>{item.attributedStat.claim}</div>
                                <div style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'DM Sans' }}>{item.attributedStat.credit}</div>
                            </div>
                        </div>
                    )}

                    {/* Inline video player (YouTube, privacy-enhanced nocookie embed).
              Shorts are vertical (9:16) and capped in width so they don't
              dominate; standard videos are 16:9 full-width. */}
                    {yt && (
                        <div style={{
                            position: 'relative',
                            width: '100%',
                            maxWidth: yt.isShort ? 360 : '100%',
                            aspectRatio: yt.isShort ? '9 / 16' : '16 / 9',
                            margin: '0 0 32px',
                            background: '#000',
                            border: '1px solid var(--border)',
                            borderRadius: 8,
                            overflow: 'hidden',
                        }}>
                            <iframe
                                src={yt.embedUrl}
                                title={item.title}
                                loading="lazy"
                                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
                                allowFullScreen
                                referrerPolicy="strict-origin-when-cross-origin"
                                style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', border: 0 }}
                            />
                        </div>
                    )}

                    {/* Outbound link — used for press items, and as a fallback for any
              video whose URL we couldn't parse into an embed. rel prevents
              reverse-tabnabbing. */}
                    {outHref && !item.placeholder && !yt && (
                        <a href={outHref} target="_blank" rel="noopener noreferrer" style={{ display: 'inline-block', background: 'var(--accent)', color: '#060c1c', textDecoration: 'none', padding: '14px 30px', borderRadius: 4, fontFamily: 'DM Sans', fontWeight: 700, fontSize: 15 }}>{meta.cta} →</a>
                    )}

                    {/* For embedded videos, still offer a "watch on YouTube" escape hatch. */}
                    {yt && item.videoHref && (
                        <a href={item.videoHref} target="_blank" rel="noopener noreferrer" style={{ display: 'inline-block', color: 'var(--accent)', textDecoration: 'none', fontFamily: 'DM Sans', fontWeight: 600, fontSize: 14 }}>Watch on YouTube →</a>
                    )}
                    {item.placeholder && (
                        <div style={{ fontSize: 13, color: 'var(--text-muted)', fontStyle: 'italic', fontFamily: 'DM Sans' }}>Placeholder entry — add approved content and a real link, then remove the placeholder flag.</div>
                    )}
                </div>
            </section>

            {/* More field results */}
            <section style={{ background: 'var(--bg-2)', padding: isMobile ? '56px 20px' : '80px 48px', borderTop: '1px solid var(--border)' }}>
                <div style={{ maxWidth: 1200, margin: '0 auto' }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 36 }}>
                        <h3 style={{ fontFamily: 'Barlow Condensed', fontWeight: 800, fontSize: 36, textTransform: 'uppercase', color: 'var(--text)' }}>More Real Stories</h3>
                        <button onClick={() => navigate('case-studies')} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--accent)', fontSize: 14, fontWeight: 600, fontFamily: 'DM Sans' }}>View All →</button>
                    </div>
                    <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 20 }}>
                        {ORDERED_CASE_STUDIES.filter(c => c.id !== item.id).slice(0, 3).map(c => (
                            <CSCard key={c.id} item={c} onClick={id => navigate('case-study', id)} />
                        ))}
                    </div>
                </div>
            </section>

            <Footer navigate={navigate} />
        </main>
    );
}

// ─── LIST PAGE ────────────────────────────────────────────────────────────────
function CaseStudiesList({ navigate, isMobile }) {
    const [filter, setFilter] = useCSState('all');
    const [searchOpen, setSearchOpen] = useCSState(false);
    const [query, setQuery] = useCSState('');
    // Only offer filters for types that actually have visible entries.
    const typeLabels = [
        ['field-report', 'Hero Stories'], ['testimonial', 'Testimonials'],
        ['thought-leadership', 'Thought Leadership'], ['video', 'Videos'],
        ['press', 'Articles'],
    ];
    const presentTypes = new Set(VISIBLE_CASE_STUDIES.map(c => c.type));
    const filters = [['all', 'All'], ...typeLabels.filter(([k]) => presentTypes.has(k))];
    const byType = filter === 'all' ? ORDERED_CASE_STUDIES : VISIBLE_CASE_STUDIES.filter(c => c.type === filter);
    const q = query.trim().toLowerCase();
    const visible = !q ? byType : byType.filter(c => {
        const hay = [c.title, c.summary, c.source, c.quote, c.attribution, (c.agencyDetail && c.agencyDetail.location)]
            .filter(Boolean).join(' ').toLowerCase();
        return hay.includes(q);
    });

    return (
        <main style={{ paddingTop: 72 }}>
            <section style={{ background: 'var(--bg)', padding: isMobile ? '48px 20px 48px' : '80px 48px 60px', borderBottom: '1px solid var(--border)' }}>
                <div style={{ maxWidth: 1200, margin: '0 auto' }}>
                    <div style={{ fontSize: 12, fontWeight: 600, letterSpacing: '0.14em', color: 'var(--accent)', textTransform: 'uppercase', marginBottom: 16, fontFamily: 'DM Sans' }}>Safer Outcomes in the Field</div>
                    <h1 style={{ fontFamily: 'Barlow Condensed', fontWeight: 900, fontSize: 'clamp(56px, 9vw, 112px)', textTransform: 'uppercase', color: 'var(--text)', lineHeight: 0.9, marginBottom: 24 }}>Real Stories</h1>
                    <p style={{ fontSize: 18, color: 'var(--text-sub)', maxWidth: 680, lineHeight: 1.65, fontFamily: 'DM Sans' }}>
                        Real deployments, agency testimonials, thought leadership, and independent coverage of the WRAP® Non-Lethal Response™ System — the encounters where officers and subjects both went home safely.
                    </p>
                </div>
            </section>

            {/* Filter bar + search toggle */}
            <section style={{ background: 'var(--bg)', padding: isMobile ? '0 20px' : '0 48px' }}>
                <div style={{ maxWidth: 1200, margin: '0 auto', paddingTop: 8 }}>
                    <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
                        {filters.map(([key, label]) => (
                            <button key={key} onClick={() => setFilter(key)} style={{
                                background: filter === key ? 'var(--accent)' : 'transparent',
                                color: filter === key ? '#060c1c' : 'var(--text-sub)',
                                border: '1px solid', borderColor: filter === key ? 'var(--accent)' : 'var(--border-strong)',
                                cursor: 'pointer', padding: '9px 18px', borderRadius: 4, fontFamily: 'DM Sans',
                                fontWeight: 600, fontSize: 13,
                            }}>{label}</button>
                        ))}
                        {/* Search icon */}
                        <button
                            onClick={() => { setSearchOpen(o => { const n = !o; if (!n) setQuery(''); return n; }); }}
                            aria-label="Search real stories"
                            aria-expanded={searchOpen}
                            style={{
                                background: searchOpen ? 'var(--accent)' : 'transparent',
                                border: '1px solid', borderColor: searchOpen ? 'var(--accent)' : 'var(--border-strong)',
                                cursor: 'pointer', width: 38, height: 38, borderRadius: 4,
                                display: 'flex', alignItems: 'center', justifyContent: 'center', marginLeft: 'auto',
                            }}>
                            <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke={searchOpen ? '#060c1c' : 'var(--text-sub)'} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                                <circle cx="11" cy="11" r="7" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
                            </svg>
                        </button>
                    </div>

                    {/* Full-width search bar (revealed) */}
                    {searchOpen && (
                        <div style={{ marginTop: 14 }}>
                            <input
                                type="text"
                                autoFocus
                                value={query}
                                onChange={e => setQuery(e.target.value)}
                                placeholder="Search by agency, location, keyword…"
                                style={{
                                    width: '100%', boxSizing: 'border-box', background: 'var(--card-bg)',
                                    border: '1px solid var(--border-strong)', borderRadius: 6,
                                    padding: '15px 18px', color: 'var(--text)', fontFamily: 'DM Sans',
                                    fontSize: 16, outline: 'none',
                                }}
                            />
                        </div>
                    )}
                </div>
            </section>

            <section style={{ background: 'var(--bg)', padding: isMobile ? '32px 20px 80px' : '48px 48px 120px' }}>
                <div style={{ maxWidth: 1200, margin: '0 auto' }}>
                    {visible.length > 0 ? (
                        <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 20 }}>
                            {visible.map(item => <CSCard key={item.id} item={item} onClick={id => navigate('case-study', id)} />)}
                        </div>
                    ) : (
                        <div style={{ padding: '60px 0', textAlign: 'center', color: 'var(--text-muted)', fontFamily: 'DM Sans', fontSize: 16 }}>
                            No stories match “{query}”. Try a different search or clear the filter.
                        </div>
                    )}
                </div>
            </section>

            {/* CTA band — matches UseCasesPage */}
            <section style={{ background: 'var(--accent)', padding: isMobile ? '56px 20px' : '80px 48px', textAlign: 'center' }}>
                <div style={{ maxWidth: 640, margin: '0 auto' }}>
                    <h2 style={{ fontFamily: 'Barlow Condensed', fontWeight: 900, fontSize: 'clamp(40px, 6vw, 72px)', textTransform: 'uppercase', color: '#060c1c', lineHeight: 0.92, marginBottom: 20 }}>Have a Story to Share?</h2>
                    <p style={{ fontSize: 17, color: 'rgba(6,12,28,0.65)', marginBottom: 36, lineHeight: 1.6, fontFamily: 'DM Sans' }}>If your agency has deployed the WRAP® system in the field, we'd like to hear about it.</p>
                    <button onClick={() => navigate('contact')} style={{ background: '#060c1c', color: '#fff', border: 'none', cursor: 'pointer', padding: '15px 40px', borderRadius: 4, fontFamily: 'DM Sans', fontWeight: 700, fontSize: 16 }}>Contact Us</button>
                </div>
            </section>

            <Footer navigate={navigate} />
        </main>
    );
}

// ─── ENTRY COMPONENT ──────────────────────────────────────────────────────────
function CaseStudiesPage({ navigate, csId }) {
    const isMobile = useMobile();
    useCSEffect(() => { if (!csId) document.title = 'Real Stories — WRAP® Non-Lethal Response™'; }, [csId]);
    // List view: full ItemList manifest. Detail view: that single story's schema.
    const detailItem = csId ? VISIBLE_CASE_STUDIES.find(c => c.id === csId) : null;
    useJsonLd(
        'wrap-real-stories-jsonld',
        () => (detailItem ? storyToSchema(detailItem) : buildRealStoriesJsonLd()),
        [csId]
    );
    if (csId) return <CSDetail csId={csId} navigate={navigate} isMobile={isMobile} />;
    return <CaseStudiesList navigate={navigate} isMobile={isMobile} />;
}

// Expose the visible entries so the router (App.jsx hashToState) can validate
// deep-links against only published cards — a hidden card's URL falls back to
// the list rather than rendering.
Object.assign(window, { CaseStudiesPage, CASE_STUDIES, VISIBLE_CASE_STUDIES });