> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omni.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Library

> Browse Omni Apps, Skills, and Routines by line of business and vertical. Copy a rebuild prompt, skill YAML, or routine prompt straight into your own Omni instance.

export const ROUTINES = [{
  "id": "at_risk_membership_huddle",
  "type": "Routine",
  "name": "At-Risk Membership Huddle",
  "blurb": "A front-desk huddle list: MRR at risk, the members most likely to lapse, and the save action for each.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Customer Success",
  "cadence": "Daily",
  "demoTopic": "Membership subscriptions / billing, Visit history, Appointments"
}, {
  "id": "cold_chain_excursion_briefing",
  "type": "Routine",
  "name": "Cold Chain Excursion Briefing",
  "blurb": "Hourly watch on in-transit temperature-controlled shipments — flags loads predicted to breach range before they arrive.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Operations",
  "cadence": "Hourly",
  "demoTopic": "In-transit shipments, Trailer telemetry, Route weather, Driver hours-of-service"
}, {
  "id": "consignment_rebalance_alerts",
  "type": "Routine",
  "name": "Consignment Rebalance Alerts",
  "blurb": "Matches the next 14 days of surgical cases against site inventory and proposes lateral transfers from nearby hospitals with excess stock.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Operations",
  "cadence": "Daily",
  "demoTopic": "Scheduled surgical cases, Site consignment inventory, Facility locations"
}, {
  "id": "creative_stop_loss_alert",
  "type": "Routine",
  "name": "Creative Stop-Loss Alert",
  "blurb": "Daily flags zombie and fatigued ad creatives, totals the wasted spend, and names the fresh winners that should absorb the budget.",
  "vertical": "",
  "lineOfBusiness": "Marketing",
  "cadence": "Daily",
  "demoTopic": "Ad creative performance"
}, {
  "id": "daily_call_digest",
  "type": "Routine",
  "name": "Daily Call Digest",
  "blurb": "A direct, no-fluff digest of yesterday's recorded sales calls — what resonated, what's blocking, and a RAG read per deal.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "cadence": "Daily",
  "demoTopic": "Call recordings / transcripts, CRM opportunities"
}, {
  "id": "daily_call_prep_briefing",
  "type": "Routine",
  "name": "Daily Call Prep Briefing",
  "blurb": "Each morning, briefs you on every external call on today's calendar — deal status, recent context, and action items per call.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "cadence": "Daily",
  "demoTopic": "Calendar events, CRM opportunities, Meeting notes"
}, {
  "id": "daily_customer_health_check",
  "type": "Routine",
  "name": "Daily Customer Health Check",
  "blurb": "A per-account morning readout: seat overages, usage and AI trends, and activation gaps — ranked so the riskiest accounts come first.",
  "vertical": "SaaS",
  "lineOfBusiness": "Customer Success",
  "cadence": "Daily",
  "demoTopic": "Contract / order lines, Provisioned users, Product usage events"
}, {
  "id": "daily_labor_efficiency_brief",
  "type": "Routine",
  "name": "Daily Labor Efficiency Brief",
  "blurb": "Yesterday's labor percentage of sales vs target, and — when it runs hot — the shifts and roles that drove the variance.",
  "vertical": "Food Service",
  "lineOfBusiness": "Operations",
  "cadence": "Daily",
  "demoTopic": "POS sales, Labor / timeclock shifts"
}, {
  "id": "daily_transaction_summary",
  "type": "Routine",
  "name": "Daily Transaction Summary",
  "blurb": "Each morning, yesterday's transaction volume, fraud count, and average risk score broken down by merchant category.",
  "vertical": "Finance",
  "lineOfBusiness": "Risk & Compliance",
  "cadence": "Daily",
  "demoTopic": "Payment transactions"
}, {
  "id": "daily_volume_alert",
  "type": "Routine",
  "name": "Daily Volume Alert",
  "blurb": "Alerts when yesterday's total event volume drops sharply below the prior 7-day average — an early warning for tracking breakage.",
  "vertical": "",
  "lineOfBusiness": "Marketing",
  "cadence": "Daily",
  "demoTopic": "Web / ad events"
}, {
  "id": "demurrage_risk_alert",
  "type": "Routine",
  "name": "Demurrage Risk Alert",
  "blurb": "Containers within 72 hours of free-time expiry, with projected tiered demurrage penalties — showing only the moves where mitigation beats the fee.",
  "vertical": "Supply Chain & Logistics",
  "lineOfBusiness": "Operations",
  "cadence": "Daily",
  "demoTopic": "Container milestones / free time, Demurrage tariffs, Drayage costs"
}, {
  "id": "fleet_capacity_fatigue_briefing",
  "type": "Routine",
  "name": "Fleet Capacity & Fatigue Briefing",
  "blurb": "Morning read on available VPP yield for today's peak window and which device classes are fatigued enough to risk overrides.",
  "vertical": "Energy",
  "lineOfBusiness": "Operations",
  "cadence": "Daily",
  "demoTopic": "DER device fleet telemetry, Dispatch event history"
}, {
  "id": "menu_profitability_summary",
  "type": "Routine",
  "name": "Menu Profitability Summary",
  "blurb": "Monthly margin view of the menu — average margin by category and the best and worst items by estimated margin.",
  "vertical": "Food Service",
  "lineOfBusiness": "Operations",
  "cadence": "Monthly",
  "demoTopic": "Menu item sales, Item costs"
}, {
  "id": "monthly_market_summary",
  "type": "Routine",
  "name": "Monthly Market Summary",
  "blurb": "A monthly read on one short-term rental market: average nightly price, occupancy, and review momentum vs the prior month.",
  "vertical": "Travel & Hospitality",
  "lineOfBusiness": "Revenue Operations",
  "cadence": "Monthly",
  "demoTopic": "Listings / bookings, Reviews"
}, {
  "id": "monthly_new_user_signups",
  "type": "Routine",
  "name": "Monthly New User Signups",
  "blurb": "On the first of the month, last month's new signups broken down by country and traffic source.",
  "vertical": "",
  "lineOfBusiness": "Marketing",
  "cadence": "Monthly",
  "demoTopic": "User signups"
}, {
  "id": "network_vht_incident_brief",
  "type": "Routine",
  "name": "Network VHT & Incident Brief",
  "blurb": "Yesterday's vehicle-hours-travelled delay, the incidents that took longest to clear, and the VHT saved by active diversions.",
  "vertical": "Supply Chain & Logistics",
  "lineOfBusiness": "Operations",
  "cadence": "Daily",
  "demoTopic": "Traffic sensor / delay data, Incident logs, Diversion records"
}, {
  "id": "new_late_stage_opp_roster",
  "type": "Routine",
  "name": "New Late-Stage Opp Roster",
  "blurb": "Daily notice of every opportunity that reached your late stage in the past 24 hours, so services can start scoping early.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "cadence": "Daily",
  "demoTopic": "CRM opportunities"
}, {
  "id": "premium_user_nudge",
  "type": "Routine",
  "name": "Premium User Nudge",
  "blurb": "Lists premium-tier users who had a back-to-back-day streak last week but haven't visited this week — a ready-made re-engagement list.",
  "vertical": "Subscription",
  "lineOfBusiness": "Product",
  "cadence": "Weekly",
  "demoTopic": "Product usage events, User accounts / plan tiers"
}, {
  "id": "shelf_life_rsl_alert",
  "type": "Routine",
  "name": "Shelf-Life (RSL) Alert",
  "blurb": "Daily flag on batches below the remaining-shelf-life threshold — write-off exposure, the most critical batches, and the salvage math.",
  "vertical": "Supply Chain & Logistics",
  "lineOfBusiness": "Operations",
  "cadence": "Daily",
  "demoTopic": "Batch / lot inventory"
}, {
  "id": "site_safety_compliance_alert",
  "type": "Routine",
  "name": "Site Safety Compliance Alert",
  "blurb": "Flags personnel on active projects with missing, expired, or soon-to-expire mandatory certifications, grouped by project.",
  "vertical": "Construction",
  "lineOfBusiness": "Risk & Compliance",
  "cadence": "Weekly",
  "demoTopic": "Personnel assignments, Certification records, Active projects"
}, {
  "id": "stranded_value_briefing",
  "type": "Routine",
  "name": "Stranded-Value Briefing",
  "blurb": "Daily flags marketplace segments whose match rate slipped below baseline, sizes the revenue left on the table, and names the bottleneck.",
  "vertical": "Finance",
  "lineOfBusiness": "Operations",
  "cadence": "Daily",
  "demoTopic": "Lead / match funnel, Lender supply signals"
}, {
  "id": "top_products_monthly",
  "type": "Routine",
  "name": "Top Products of the Month",
  "blurb": "A monthly top-10 products report by revenue and units sold.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Sales",
  "cadence": "Monthly",
  "demoTopic": "Orders / order lines"
}, {
  "id": "weekly_demo_digest",
  "type": "Routine",
  "name": "Weekly Demo Digest",
  "blurb": "Recaps every demo from the latest internal demo/all-hands session — what it was, who gave it, and its ship status.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "cadence": "Weekly",
  "demoTopic": "Meeting recordings / transcripts"
}, {
  "id": "weekly_encounter_summary",
  "type": "Routine",
  "name": "Weekly Encounter Summary",
  "blurb": "A weekly roll-up of new patient encounters by encounter class and the top reasons for visit.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Operations",
  "cadence": "Weekly",
  "demoTopic": "Patient encounters"
}, {
  "id": "weekly_new_opp_quality",
  "type": "Routine",
  "name": "Weekly New-Opp Quality",
  "blurb": "Reviews last week's BDR-created opportunities and scores the discovery notes 0-5, with gaps and next steps for coaching.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "cadence": "Weekly",
  "demoTopic": "CRM opportunities"
}, {
  "id": "weekly_support_feedback",
  "type": "Routine",
  "name": "Weekly Support Feedback",
  "blurb": "An executive summary of last week's support and feedback requests — themes tracked, plus verbatim examples worth reading yourself.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "cadence": "Weekly",
  "demoTopic": "Support tickets / feedback requests"
}, {
  "id": "what_shipped_yesterday",
  "type": "Routine",
  "name": "What Shipped Yesterday?",
  "blurb": "Each morning, a by-area rundown of PRs merged in the last 24 hours — big features first — so you know what needs testing.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "cadence": "Daily",
  "demoTopic": "Merged pull requests"
}];

export const APPS = [{
  "id": "cro-headcount-planner",
  "type": "App",
  "name": "CRO Headcount Planner",
  "blurb": "GTM headcount planning model seeded with live CRM and finance run rates — all planning math recalculates instantly client-side.",
  "owner": "Chris Daniel",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Operations", "Finance"],
  "screenshot": "/images/library/apps/cro-headcount-planner.webp?v=f5280880da",
  "screenshots": ["/images/library/apps/cro-headcount-planner.webp?v=f5280880da", "/images/library/apps/cro-headcount-planner-2.webp?v=3fb8f42dd4"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "74da658acb"
}, {
  "id": "patient-oop-burden",
  "type": "App",
  "name": "Patient Out-of-Pocket Burden",
  "blurb": "Copay and deductible burden by payer type and formulary tier, with a clickable heatmap and payer-level drill-down.",
  "owner": "Drea",
  "vertical": ["Healthcare & Life Sciences"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/patient-oop-burden.webp?v=e686a68e40",
  "screenshots": ["/images/library/apps/patient-oop-burden.webp?v=e686a68e40"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "0989088a20"
}, {
  "id": "funnel-analysis",
  "type": "App",
  "name": "Funnel Analysis",
  "blurb": "Drag-and-drop funnel builder over product event data, with per-step drop-off and user-level drill-down.",
  "owner": "Arielle",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Product", "Marketing"],
  "screenshot": "/images/library/apps/funnel-analysis.webp?v=b5fde9c09f",
  "screenshots": ["/images/library/apps/funnel-analysis.webp?v=b5fde9c09f"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "29dad1159e"
}, {
  "id": "org-chart",
  "type": "App",
  "name": "Org Chart",
  "blurb": "Interactive org chart with reporting hierarchy, headcount by team, and hiring trend.",
  "owner": "Ernesto",
  "vertical": [],
  "lineOfBusiness": ["Executive", "Operations"],
  "screenshot": "/images/library/apps/org-chart.webp?v=24a336f764",
  "screenshots": ["/images/library/apps/org-chart.webp?v=24a336f764", "/images/library/apps/org-chart-2.webp?v=376f90c4d4", "/images/library/apps/org-chart-3.webp?v=47d4cb1b7f"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "423dda996d"
}, {
  "id": "user-journey-sankey",
  "type": "App",
  "name": "User Journey Sankey",
  "blurb": "D3 Sankey of feature-to-feature navigation within sessions, with a click-through drawer per feature.",
  "owner": "Jade",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Product", "Marketing"],
  "screenshot": "/images/library/apps/user-journey-sankey.webp?v=8d1b81523b",
  "screenshots": ["/images/library/apps/user-journey-sankey.webp?v=8d1b81523b"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "4f19c70e44"
}, {
  "id": "retail-floor-analysis",
  "type": "App",
  "name": "Retail Floor Analysis",
  "blurb": "Store performance visualized on a 2D isometric floor plan.",
  "owner": "Anouar",
  "vertical": ["Retail & eCommerce"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/retail-floor-analysis.webp?v=88ca4b54c3",
  "screenshots": ["/images/library/apps/retail-floor-analysis.webp?v=88ca4b54c3", "/images/library/apps/retail-floor-analysis-2.webp?v=4cf327820a"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "6569b023bc"
}, {
  "id": "ft-style-banking",
  "type": "App",
  "name": "FT Style — Banking",
  "blurb": "Financial-press-style article with graph and commentary clickthrough.",
  "owner": "Conor",
  "vertical": ["Finance"],
  "lineOfBusiness": ["Finance"],
  "screenshot": "/images/library/apps/ft-style-banking.webp?v=c63d3f9d2d",
  "screenshots": ["/images/library/apps/ft-style-banking.webp?v=c63d3f9d2d"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "e3878198d2"
}, {
  "id": "pnl-scenario-lab",
  "type": "App",
  "name": "P&L Scenario Lab",
  "blurb": "Quarterly earnings prep and what-if scenario analysis.",
  "owner": "Ben",
  "vertical": ["Finance"],
  "lineOfBusiness": ["Finance", "Executive"],
  "screenshot": "/images/library/apps/pnl-scenario-lab.webp?v=3850aca2eb",
  "screenshots": ["/images/library/apps/pnl-scenario-lab.webp?v=3850aca2eb"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "3c5cffe7ed"
}, {
  "id": "gaming-analytics-explorer",
  "type": "App",
  "name": "Gaming Analytics Explorer",
  "blurb": "Metacritic-style explorer for game events, engagement, and revenue.",
  "owner": "Conor",
  "vertical": ["Gaming"],
  "lineOfBusiness": ["Product"],
  "screenshot": "/images/library/apps/gaming-analytics-explorer.webp?v=e5e547821d",
  "screenshots": ["/images/library/apps/gaming-analytics-explorer.webp?v=e5e547821d"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "830d201956"
}, {
  "id": "contact-center-heatmap",
  "type": "App",
  "name": "Contact Center Heatmap",
  "blurb": "Heatmap-driven view of contact-center volume and staffing.",
  "owner": "Drea",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Customer Experience", "Operations"],
  "screenshot": "/images/library/apps/contact-center-heatmap.webp?v=b3a8a4ab9c",
  "screenshots": ["/images/library/apps/contact-center-heatmap.webp?v=b3a8a4ab9c"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "11d996c2cb"
}, {
  "id": "last-mile-delivery-tracking",
  "type": "App",
  "name": "Last Mile Delivery Tracking",
  "blurb": "Real-time GPS route maps and on-time delivery performance.",
  "owner": "Joe Seib",
  "vertical": ["Retail & eCommerce", "Supply Chain & Logistics"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/last-mile-delivery-tracking.webp?v=03e0020d54",
  "screenshots": ["/images/library/apps/last-mile-delivery-tracking.webp?v=03e0020d54"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "024a4274c0"
}, {
  "id": "quarterly-board-deck",
  "type": "App",
  "name": "Quarterly Board Deck",
  "blurb": "A board deck assembled from live multi-source data — build once, reuse every quarter.",
  "owner": "Joe Seib",
  "vertical": [],
  "lineOfBusiness": ["Executive", "Finance"],
  "screenshot": "/images/library/apps/quarterly-board-deck.webp?v=69ad4bd87d",
  "screenshots": ["/images/library/apps/quarterly-board-deck.webp?v=69ad4bd87d", "/images/library/apps/quarterly-board-deck-2.webp?v=868f5d2ed6", "/images/library/apps/quarterly-board-deck-3.webp?v=e27c41f457", "/images/library/apps/quarterly-board-deck-4.webp?v=9a2ec05e7d", "/images/library/apps/quarterly-board-deck-5.webp?v=de8af91489", "/images/library/apps/quarterly-board-deck-6.webp?v=e14ed4cf1f"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "bee1afa88b"
}, {
  "id": "la-carte-de-performance",
  "type": "App",
  "name": "La Carte de Performance",
  "blurb": "Restaurant-group performance by store, item and category, with new-item and new-location forecast simulators.",
  "owner": "Thibault",
  "vertical": ["Food Service"],
  "lineOfBusiness": ["Revenue Operations", "Fun"],
  "screenshot": "/images/library/apps/la-carte-de-performance.webp?v=6b1f73b565",
  "screenshots": ["/images/library/apps/la-carte-de-performance.webp?v=6b1f73b565", "/images/library/apps/la-carte-de-performance-2.webp?v=0419b6cf31", "/images/library/apps/la-carte-de-performance-3.webp?v=b18d77a5f8", "/images/library/apps/la-carte-de-performance-4.webp?v=9b66214c10"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "f53652fb49"
}, {
  "id": "ad-campaign-weather",
  "type": "App",
  "name": "Ad Campaign Weather",
  "blurb": "Campaign health as a weather report — an instant read for marketers.",
  "owner": "Thibault Jauneau",
  "vertical": ["Advertising"],
  "lineOfBusiness": ["Marketing"],
  "screenshot": "/images/library/apps/ad-campaign-weather.webp?v=de3b9c3473",
  "screenshots": ["/images/library/apps/ad-campaign-weather.webp?v=de3b9c3473", "/images/library/apps/ad-campaign-weather-2.webp?v=042c45cc91"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "0b638c8a4f"
}, {
  "id": "parent-subsidiary-overview",
  "type": "App",
  "name": "Parent & Subsidiary Overview",
  "blurb": "Group-wide rollup across subsidiaries with drill into any division.",
  "owner": "Fish",
  "vertical": [],
  "lineOfBusiness": ["Executive", "Finance"],
  "screenshot": "/images/library/apps/parent-subsidiary-overview.webp?v=ad754cc769",
  "screenshots": ["/images/library/apps/parent-subsidiary-overview.webp?v=ad754cc769", "/images/library/apps/parent-subsidiary-overview-2.webp?v=bded4b31cd"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "205a1af355"
}, {
  "id": "ads-campaign-console",
  "type": "App",
  "name": "Ads Campaign Console",
  "blurb": "Console for spotting and fixing under-delivering ad campaigns.",
  "owner": "Fish",
  "vertical": ["Advertising"],
  "lineOfBusiness": ["Marketing"],
  "screenshot": "/images/library/apps/ads-campaign-console.webp?v=f21043ba05",
  "screenshots": ["/images/library/apps/ads-campaign-console.webp?v=f21043ba05"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "9f281af425"
}, {
  "id": "shipment-penalties-defender",
  "type": "App",
  "name": "Shipment Penalties Defender",
  "blurb": "Models demurrage and detention risk — cost of waiting vs. mitigation.",
  "owner": "Fish",
  "vertical": ["Supply Chain & Logistics"],
  "lineOfBusiness": ["Operations", "Finance"],
  "screenshot": "/images/library/apps/shipment-penalties-defender.webp?v=321a87f032",
  "screenshots": ["/images/library/apps/shipment-penalties-defender.webp?v=321a87f032", "/images/library/apps/shipment-penalties-defender-2.webp?v=2c20846b97"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "b94481639b"
}, {
  "id": "dc-network-explorer",
  "type": "App",
  "name": "DC Network Explorer",
  "blurb": "Drop-a-pin site planning against distribution-center shipping times.",
  "owner": "Adrien",
  "vertical": ["Supply Chain & Logistics"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/dc-network-explorer.webp?v=d69894ca93",
  "screenshots": ["/images/library/apps/dc-network-explorer.webp?v=d69894ca93"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "0cff396a0d"
}, {
  "id": "construction-project-management",
  "type": "App",
  "name": "Construction Project Management",
  "blurb": "Branded project-management workspace for construction programs.",
  "owner": "Chris Daniel",
  "vertical": ["Construction"],
  "lineOfBusiness": ["Project Management", "Operations"],
  "screenshot": "/images/library/apps/construction-project-management.webp?v=945ec8d520",
  "screenshots": ["/images/library/apps/construction-project-management.webp?v=945ec8d520", "/images/library/apps/construction-project-management-2.webp?v=809e9dc1d3", "/images/library/apps/construction-project-management-3.webp?v=c02e22507b"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "122b727729"
}, {
  "id": "baseball-stadium-heatmap",
  "type": "App",
  "name": "Baseball Ticket Sales",
  "blurb": "Clickable stadium-section heatmap of ticket sales with detail popups.",
  "owner": "Isaac Hergott",
  "vertical": ["Sports"],
  "lineOfBusiness": ["Sales"],
  "screenshot": "/images/library/apps/baseball-stadium-heatmap.webp?v=53423e97f8",
  "screenshots": ["/images/library/apps/baseball-stadium-heatmap.webp?v=53423e97f8"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "4021e283f1"
}, {
  "id": "agent-csat-explorer",
  "type": "App",
  "name": "Agent CSAT Explorer",
  "blurb": "Top-agent CSAT exploration for contact-center leaders.",
  "owner": "Drea",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Customer Experience", "Operations"],
  "screenshot": "/images/library/apps/agent-csat-explorer.webp?v=ea75ce9fef",
  "screenshots": ["/images/library/apps/agent-csat-explorer.webp?v=ea75ce9fef"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "8555fb1794"
}, {
  "id": "schema-less-json-viewer",
  "type": "App",
  "name": "Schema-less JSON Viewer",
  "blurb": "Renders schema-less JSON payloads into a browsable display.",
  "owner": "Fish",
  "vertical": [],
  "lineOfBusiness": ["Engineering"],
  "screenshot": "/images/library/apps/schema-less-json-viewer.webp?v=90a19ad10f",
  "screenshots": ["/images/library/apps/schema-less-json-viewer.webp?v=90a19ad10f"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "ae71440e58"
}, {
  "id": "subscription-price-modeler",
  "type": "App",
  "name": "Subscription Price Change Modeler",
  "blurb": "Models the ARR impact of pricing changes with churn elasticity.",
  "owner": "Sláine",
  "vertical": ["SaaS", "Subscription"],
  "lineOfBusiness": ["Revenue Operations", "Finance"],
  "screenshot": "/images/library/apps/subscription-price-modeler.webp?v=66b6ead560",
  "screenshots": ["/images/library/apps/subscription-price-modeler.webp?v=66b6ead560"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "d7a9a3b4f2"
}, {
  "id": "retail-production-cycle",
  "type": "App",
  "name": "Retail Production Cycle",
  "blurb": "Sizes next cycle's production run from sell-through, stock cover, margin, and lead time.",
  "owner": "Fish",
  "vertical": ["Retail & eCommerce"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/retail-production-cycle.webp?v=2417548fcd",
  "screenshots": ["/images/library/apps/retail-production-cycle.webp?v=2417548fcd"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "f998696ee4"
}, {
  "id": "freight-command-center",
  "type": "App",
  "name": "Freight Command Center",
  "blurb": "Animated SLA speedometers and live ops view for a freight network.",
  "owner": "Will Clayton",
  "vertical": ["Supply Chain & Logistics"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/freight-command-center.webp?v=f848c9b1a0",
  "screenshots": ["/images/library/apps/freight-command-center.webp?v=f848c9b1a0"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "01381cee67"
}, {
  "id": "sales-performance-tracker",
  "type": "App",
  "name": "Sales Performance Tracker",
  "blurb": "Executive sales performance tracking across the retail org.",
  "owner": "William",
  "vertical": ["Retail & eCommerce"],
  "lineOfBusiness": ["Executive", "Sales"],
  "screenshot": "/images/library/apps/sales-performance-tracker.webp?v=888f521197",
  "screenshots": ["/images/library/apps/sales-performance-tracker.webp?v=888f521197"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "ee914969a2"
}, {
  "id": "snowflake-catalog-explorer",
  "type": "App",
  "name": "Snowflake Catalog Explorer",
  "blurb": "Warehouse catalog explorer — schema and table stats with 30-day query usage, drill-down and cross-schema search.",
  "owner": "Colin",
  "vertical": [],
  "lineOfBusiness": ["Engineering"],
  "screenshot": "/images/library/apps/snowflake-catalog-explorer.webp?v=dea2de2124",
  "screenshots": ["/images/library/apps/snowflake-catalog-explorer.webp?v=dea2de2124"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "ad51e43749"
}, {
  "id": "blob-breaker",
  "type": "App",
  "name": "Blob Breaker",
  "blurb": "The Blob Breaker game, as an Omni app.",
  "owner": "Alisa",
  "vertical": [],
  "lineOfBusiness": ["Fun"],
  "screenshot": "/images/library/apps/blob-breaker.webp?v=f2728707ec",
  "screenshots": ["/images/library/apps/blob-breaker.webp?v=f2728707ec", "/images/library/apps/blob-breaker-2.webp?v=ed8aa38cf2"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "e7538ca3b4"
}, {
  "id": "qbr-deck-builder",
  "type": "App",
  "name": "QBR Deck Builder",
  "blurb": "Per-seller QBR decks assembled from live CRM data — exports a native Google Slides deck and an editable PowerPoint.",
  "owner": "Kimberly",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Revenue Operations", "Sales"],
  "screenshot": "/images/library/apps/qbr-deck-builder.webp?v=f28bc4e12f",
  "screenshots": ["/images/library/apps/qbr-deck-builder.webp?v=f28bc4e12f", "/images/library/apps/qbr-deck-builder-2.webp?v=8f69b3a479"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": true,
  "flagged": null,
  "contentV": "9d59de4c07"
}, {
  "id": "claims-explorer",
  "type": "App",
  "name": "Claims Explorer",
  "blurb": "Healthcare claims analysis by therapeutic area and specialty.",
  "owner": "Drea",
  "vertical": ["Healthcare & Life Sciences"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/claims-explorer.webp?v=d6ab281ec5",
  "screenshots": ["/images/library/apps/claims-explorer.webp?v=d6ab281ec5", "/images/library/apps/claims-explorer-2.webp?v=bd6e3d1d13"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "bc83dbfc66"
}, {
  "id": "food-service-explorer",
  "type": "App",
  "name": "Food Service Explorer",
  "blurb": "Restaurant chain performance across stores, categories and day-of-week, with store and category drill-downs.",
  "owner": "Drea",
  "vertical": ["Food Service"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/food-service-explorer.webp?v=3d8d50490b",
  "screenshots": ["/images/library/apps/food-service-explorer.webp?v=3d8d50490b"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "70585a28f5"
}, {
  "id": "telemetryos",
  "type": "App",
  "name": "TelemetryOS",
  "blurb": "Engineering ops console — DORA metrics, PR queue, A/B readouts and a feature adoption funnel.",
  "owner": "Fish",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Engineering", "Product"],
  "screenshot": "/images/library/apps/telemetryos.webp?v=0dd6597c7f",
  "screenshots": ["/images/library/apps/telemetryos.webp?v=0dd6597c7f"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "97b86b8926"
}, {
  "id": "cohort-builder",
  "type": "App",
  "name": "Cohort Builder",
  "blurb": "Visual cohort builder with AND/OR/INTERSECT filter logic, saved cohorts, and retention analytics over ecommerce users.",
  "owner": "Jade",
  "vertical": ["Retail & eCommerce", "Subscription"],
  "lineOfBusiness": ["Product", "Marketing"],
  "screenshot": "/images/library/apps/cohort-builder.webp?v=b295475b70",
  "screenshots": ["/images/library/apps/cohort-builder.webp?v=b295475b70", "/images/library/apps/cohort-builder-2.webp?v=f1f9f63b82"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "d064008154"
}, {
  "id": "closed-won-iarr-waterfall",
  "type": "App",
  "name": "Closed Won iARR Waterfall",
  "blurb": "YoY waterfall stacking this year's closed-won iARR by segment on last year's baseline, with region filters.",
  "owner": "Will Savage",
  "vertical": ["SaaS", "Subscription"],
  "lineOfBusiness": ["Sales", "Revenue Operations"],
  "screenshot": "/images/library/apps/closed-won-iarr-waterfall.webp?v=2d9aa94d9d",
  "screenshots": ["/images/library/apps/closed-won-iarr-waterfall.webp?v=2d9aa94d9d"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "b567fba6dd"
}, {
  "id": "pdf-report-builder",
  "type": "App",
  "name": "PDF Report Builder",
  "blurb": "Drag-and-drop A4 report designer over live sales data with true-vector PDF and PowerPoint export.",
  "owner": "Andrew Barnish",
  "vertical": [],
  "lineOfBusiness": ["Executive", "Finance"],
  "screenshot": "/images/library/apps/pdf-report-builder.webp?v=fe4b2c80da",
  "screenshots": ["/images/library/apps/pdf-report-builder.webp?v=fe4b2c80da"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": true,
  "flagged": null,
  "contentV": "5e8f755380"
}, {
  "id": "order-spatial-explorer",
  "type": "App",
  "name": "Order Spatial Explorer",
  "blurb": "Interactive Leaflet map of order revenue by city with drill-in detail, radius-based proximity clusters, and spatial stats.",
  "owner": "Will Savage",
  "vertical": ["Retail & eCommerce"],
  "lineOfBusiness": ["Operations", "Sales"],
  "screenshot": "/images/library/apps/order-spatial-explorer.webp?v=f7b73fe8e8",
  "screenshots": ["/images/library/apps/order-spatial-explorer.webp?v=f7b73fe8e8"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "9117c51a50"
}, {
  "id": "hospital-network-operations",
  "type": "App",
  "name": "Hospital Network Operations",
  "blurb": "Hospital network command center over FHIR data — exec KPIs, benchmark trends, diagnostic drill-downs, and a filterable patient population.",
  "owner": "Jonathon Girvetz",
  "vertical": ["Healthcare & Life Sciences"],
  "lineOfBusiness": ["Operations", "Executive"],
  "screenshot": "/images/library/apps/hospital-network-operations.webp?v=11f7588050",
  "screenshots": ["/images/library/apps/hospital-network-operations.webp?v=11f7588050"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "675dbf77f8"
}, {
  "id": "saas-network-graph",
  "type": "App",
  "name": "SaaS Network Graph",
  "blurb": "Force-directed rep → account → product network sized by won ARR, with filters, hover highlighting, and pinned detail panels.",
  "owner": "Nolan Johnson",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Sales", "Revenue Operations"],
  "screenshot": "/images/library/apps/saas-network-graph.webp?v=0960168819",
  "screenshots": ["/images/library/apps/saas-network-graph.webp?v=0960168819"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "a1529ffb79"
}, {
  "id": "revenue-story",
  "type": "App",
  "name": "Revenue Story",
  "blurb": "Editable eight-slide revenue narrative — YoY growth, deal size, lost ARR, and segment drivers told scrollytelling-style from live data.",
  "owner": "Nolan Johnson",
  "vertical": ["SaaS", "Subscription"],
  "lineOfBusiness": ["Finance", "Project Management"],
  "screenshot": "/images/library/apps/revenue-story.webp?v=01b058059b",
  "screenshots": ["/images/library/apps/revenue-story.webp?v=01b058059b"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "f19ff46b5a"
}, {
  "id": "life-insurance-policies-at-risk",
  "type": "App",
  "name": "Life Insurance Policies at Risk",
  "blurb": "Product × channel persistency table with lapse-risk drill-down to individual policies and a segment reminder workflow.",
  "owner": "Fish Shinde",
  "vertical": ["Healthcare & Life Sciences"],
  "lineOfBusiness": ["Risk & Compliance"],
  "screenshot": "/images/library/apps/life-insurance-policies-at-risk.webp?v=0437eaccd3",
  "screenshots": ["/images/library/apps/life-insurance-policies-at-risk.webp?v=0437eaccd3"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "312aeb2535"
}, {
  "id": "ad-sales-inventory-matcher",
  "type": "App",
  "name": "Ad Sales Inventory Matcher",
  "blurb": "Match video ad inventory to an advertiser brief — demographic scoring, ranked title cards, and a one-click outreach email draft.",
  "owner": "Kate Fray",
  "vertical": ["Advertising", "Media & Entertainment"],
  "lineOfBusiness": ["Sales"],
  "screenshot": "/images/library/apps/ad-sales-inventory-matcher.webp?v=1d789bb437",
  "screenshots": ["/images/library/apps/ad-sales-inventory-matcher.webp?v=1d789bb437"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "b60d7f3618"
}, {
  "id": "reactor-operations-center",
  "type": "App",
  "name": "Reactor Operations Center",
  "blurb": "Sci-fi energy operations console — animated grid-flow canvases, five monitoring panels, KPI drill-modals, and simulated fleet telemetry.",
  "owner": "Jonathon Girvetz",
  "vertical": ["Energy"],
  "lineOfBusiness": ["Operations"],
  "screenshot": "/images/library/apps/reactor-operations-center.webp?v=dababf6afc",
  "screenshots": ["/images/library/apps/reactor-operations-center.webp?v=dababf6afc"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "cf80bc87e5"
}, {
  "id": "contact-center-sentiment-heatmap",
  "type": "App",
  "name": "Contact Center Sentiment Heatmap",
  "blurb": "Paired topic × channel heatmaps of conversation volume and customer sentiment with a click-to-drill comparison panel.",
  "owner": "Drea",
  "vertical": ["SaaS"],
  "lineOfBusiness": ["Customer Experience", "Operations"],
  "screenshot": "/images/library/apps/contact-center-sentiment-heatmap.webp?v=7e2d604913",
  "screenshots": ["/images/library/apps/contact-center-sentiment-heatmap.webp?v=7e2d604913"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "8655103452"
}, {
  "id": "momcorp-health-analytics",
  "type": "App",
  "name": "MomCorp Health Analytics",
  "blurb": "Branded health-plan analytics portal — member health, care management, claims and payers, and member retention, all drillable.",
  "owner": "Chris Daniel",
  "vertical": ["Healthcare & Life Sciences"],
  "lineOfBusiness": ["Executive", "Operations"],
  "screenshot": "/images/library/apps/momcorp-health-analytics.webp?v=e2aa399de0",
  "screenshots": ["/images/library/apps/momcorp-health-analytics.webp?v=e2aa399de0"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "24ef0c456e"
}, {
  "id": "loan-portfolio-manager",
  "type": "App",
  "name": "Loan Portfolio Manager",
  "blurb": "Commercial loan book monitor — covenant breaches, risk ratings, delinquency, and RM contact cadence with a full loan drill-over.",
  "owner": "Joe Seib",
  "vertical": ["Finance"],
  "lineOfBusiness": ["Risk & Compliance"],
  "screenshot": "/images/library/apps/loan-portfolio-manager.webp?v=10d5332c91",
  "screenshots": ["/images/library/apps/loan-portfolio-manager.webp?v=10d5332c91"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "884627273a"
}, {
  "id": "insurance-command-center",
  "type": "App",
  "name": "Insurance Command Center",
  "blurb": "Four-tab insurance portfolio console — lapse, claims severity, retention risk, and segment profitability with global cross-query filters.",
  "owner": "Fish Shinde",
  "vertical": ["Healthcare & Life Sciences"],
  "lineOfBusiness": ["Risk & Compliance", "Finance"],
  "screenshot": "/images/library/apps/insurance-command-center.webp?v=7289ca81f3",
  "screenshots": ["/images/library/apps/insurance-command-center.webp?v=7289ca81f3"],
  "hasHtml": true,
  "hasPrompt": true,
  "hasSetup": false,
  "flagged": null,
  "contentV": "3a9686b9b5"
}];

export const SKILLS = [{
  "id": "brand_market_share",
  "name": "Brand Market Share",
  "blurb": "Brand share within its market basket and whether it's gaining or just riding class growth.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Sales",
  "demoTopic": "Prescription (Rx) data",
  "integrations": [],
  "pack": "Pharma Commercial"
}, {
  "id": "brand_performance_review",
  "name": "Brand Performance Review",
  "blurb": "The monthly brand read: demand (TRx/NRx/NBRx & share), access, payer mix, and field activity in one view.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Sales",
  "demoTopic": "Prescription (Rx) data, Formulary / plan coverage, HCP calls",
  "integrations": [],
  "pack": "Pharma Commercial"
}, {
  "id": "formulary_access_review",
  "name": "Formulary Access Review",
  "blurb": "Covered lives with formulary access by tier and payer — and the biggest access gaps to close.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Sales",
  "demoTopic": "Formulary / plan coverage, Covered lives",
  "integrations": [],
  "pack": "Pharma Commercial"
}, {
  "id": "hcp_targeting_decile",
  "name": "HCP Targeting & Deciles",
  "blurb": "Prescriber deciles, reach and frequency against the target list, and where field coverage is thin.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Sales",
  "demoTopic": "Prescription (Rx) data, HCP calls",
  "integrations": [],
  "pack": "Pharma Commercial"
}, {
  "id": "trx_nrx_snapshot",
  "name": "TRx / NRx Snapshot",
  "blurb": "Total, new, and new-to-brand prescriptions with the trend — the quick brand demand pulse.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Sales",
  "demoTopic": "Prescription (Rx) data",
  "integrations": [],
  "pack": "Pharma Commercial"
}, {
  "id": "deal_risk_radar",
  "name": "Deal Risk Radar",
  "blurb": "Flags the open deals in the quarter that are stalling, past due, or going dark.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunities",
  "integrations": ["Google Calendar", "Slack", "Google Drive"],
  "pack": "RevOps"
}, {
  "id": "forecast_commit_accuracy",
  "name": "Forecast Commit Accuracy",
  "blurb": "The called number, category conversion, forecast bias vs. history, and what's slipping.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunities",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "pipeline_change_report",
  "name": "Pipeline Change Report",
  "blurb": "Diffs two daily pipeline snapshots to show exactly which deals moved, resized, closed, entered, or exited — with a field-history drill-down on the top movers.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunity history (daily snapshots), Opportunity field history",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "pipeline_coverage_review",
  "name": "Pipeline Coverage Review",
  "blurb": "Coverage ratio vs. gap-to-plan, stage funnel, and slippage — the weekly forecast-call read.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunities",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "pipeline_generation_review",
  "name": "Pipeline Generation Review",
  "blurb": "New pipeline created by source and rep, and coverage building toward the next target.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunities",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "plg_to_sales_conversion_agent",
  "name": "PLG-to-Sales Conversion Agent",
  "blurb": "Surfaces sales-ready self-serve accounts and checks whether the product-led motion is feeding pipeline.",
  "vertical": "SaaS",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "Product usage events, CRM opportunities",
  "integrations": ["Slack"],
  "pack": "RevOps"
}, {
  "id": "renewal_retention_forecast",
  "name": "Renewal & Retention Forecast",
  "blurb": "The upcoming renewal book, at-risk ARR, and projected gross/net retention for the cohort.",
  "vertical": "SaaS, Subscription",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "Subscriptions, CRM opportunities, Product usage, Support tickets",
  "integrations": ["Slack", "Google Drive"],
  "pack": "RevOps"
}, {
  "id": "rep_attainment_scorecard",
  "name": "Rep Attainment Scorecard",
  "blurb": "Quota attainment, pipeline created, and win rate by rep — the 1:1 and QBR read.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunities, Quotas",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "saas_bookings_mix_snapshot",
  "name": "SaaS Bookings Mix Snapshot",
  "blurb": "Where ARR growth came from — new / expansion / contraction / churn — with NRR and GRR.",
  "vertical": "SaaS",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "Subscriptions",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "saas_gtm_metrics_scorecard",
  "name": "SaaS GTM Metrics Scorecard",
  "blurb": "The board-deck GTM scorecard — growth, retention, and efficiency — computed only where data supports it.",
  "vertical": "SaaS",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "Subscriptions, CRM opportunities, Marketing spend",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "sales_velocity_diagnostic",
  "name": "Sales Velocity Diagnostic",
  "blurb": "Sales velocity and its four levers, with the stage where deals stall.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunities",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "win_loss_analysis",
  "name": "Win/Loss Analysis",
  "blurb": "Win rate by segment and stage, plus the top reasons deals are lost.",
  "vertical": "",
  "lineOfBusiness": "Revenue Operations",
  "demoTopic": "CRM opportunities",
  "integrations": [],
  "pack": "RevOps"
}, {
  "id": "advisor_book_of_business",
  "name": "Advisor Book of Business",
  "blurb": "AUM, revenue, households, and net new assets per advisor — productivity and at-risk or concentrated books.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "AUM / positions, Flows, Households",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "ar_aging_collections",
  "name": "AR Aging & DSO",
  "blurb": "Accounts-receivable aging buckets, DSO trend, and the overdue accounts to collect first.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "Accounts receivable",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "aum_flows_bridge",
  "name": "AUM Flows Bridge",
  "blurb": "Rolls AUM from beginning to end — separating net flows (organic growth) from market movement.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "AUM / positions, Flows",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "aum_snapshot",
  "name": "AUM Snapshot",
  "blurb": "AUM by strategy and channel with the period change — the quick assets-under-management pulse.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "AUM / positions",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "balance_sheet_flux_analysis",
  "name": "Balance Sheet Flux",
  "blurb": "Period-over-period balance-sheet movements, flagged and explained by likely driver.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "budget_vs_plan_variance",
  "name": "Budget vs Plan Variance",
  "blurb": "Actual vs. budget by department and account, with favorable/unfavorable drivers.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger, Budget/Plan",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "burn_runway_monitor",
  "name": "Burn & Runway Monitor",
  "blurb": "Net burn trend, months of runway, and burn multiple — with a scenario on hiring or spend.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger, Cash balances, Subscriptions",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "clean_claim_first_pass",
  "name": "Clean Claim & First-Pass",
  "blurb": "Clean claim rate, first-pass resolution, and where rework concentrates by facility and payer.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Finance",
  "demoTopic": "Claims / edits",
  "integrations": [],
  "pack": "Healthcare Revenue Cycle"
}, {
  "id": "commission_expense_accrual",
  "name": "Commission Expense",
  "blurb": "Commission expense by team, accrual-vs-payment timing, and capitalized-vs-expensed treatment.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger, CRM opportunities",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "rcm_denials_dashboard",
  "name": "Denials Dashboard",
  "blurb": "Denial rate by reason and payer, overturn rate, and the dollars at risk to work first.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Finance",
  "demoTopic": "Claims / remits, Denials",
  "integrations": [],
  "pack": "Healthcare Revenue Cycle"
}, {
  "id": "effective_fee_rate",
  "name": "Effective Fee Rate",
  "blurb": "Revenue yield in bps on average AUM by strategy — and whether fees are compressing over time.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "Fee revenue, AUM / positions",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "fpa_monthly_variance_review",
  "name": "FP&A Monthly Review",
  "blurb": "Period-over-period GL variance analysis — flags material swings, deep-dives each root cause, writes the exec summary.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger, CRM opportunities",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "headcount_payroll_cost",
  "name": "Headcount & Payroll",
  "blurb": "Headcount and comp cost by department, cost per head, comp as % of revenue, and plan vs. actual.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "Payroll, Headcount/HRIS",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "investor_concentration",
  "name": "Investor Concentration",
  "blurb": "Top clients or funds as a share of AUM — the key-client concentration risk, with top-N share and HHI.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "AUM / positions",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "journal_entry_anomaly_scan",
  "name": "JE Anomaly Scan",
  "blurb": "Flags unusual journal entries — large manual, round-dollar, backdated, duplicate — for review.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "net_collection_ar_review",
  "name": "Net Collection & AR",
  "blurb": "Net collection rate, AR days, and aged AR over 90 by payer — the cash-yield read.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Finance",
  "demoTopic": "Claims / remits, AR",
  "integrations": [],
  "pack": "Healthcare Revenue Cycle"
}, {
  "id": "net_flows_by_segment",
  "name": "Net Flows by Segment",
  "blurb": "Gross inflows, outflows, and net flows by strategy and channel — with redemption rate and where it concentrates.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "Flows, AUM / positions",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "opex_spend_review",
  "name": "Opex Spend Review",
  "blurb": "Operating spend by category, department, and vendor — trend, top movers, and new vendors.",
  "vertical": "",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger, Budget/Plan",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "payer_mix_snapshot",
  "name": "Payer Mix Snapshot",
  "blurb": "Charges and collections by payer and financial class — the quick revenue-cycle mix read.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Finance",
  "demoTopic": "Claims / remits",
  "integrations": [],
  "pack": "Healthcare Revenue Cycle"
}, {
  "id": "performance_vs_benchmark",
  "name": "Performance vs Benchmark",
  "blurb": "Net-of-fee excess return vs. benchmark over trailing periods, plus the share of AUM beating its benchmark.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "Returns, Benchmarks, AUM / positions",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "redemption_early_warning",
  "name": "Redemption Early Warning",
  "blurb": "Flags the funds, advisors, and clients bleeding assets — and sizes the AUM and revenue at risk.",
  "vertical": "Finance",
  "lineOfBusiness": "Finance",
  "demoTopic": "Flows, AUM / positions, Fee revenue",
  "integrations": [],
  "pack": "Asset & Wealth Management"
}, {
  "id": "rcm_scorecard",
  "name": "Revenue Cycle Scorecard",
  "blurb": "The monthly RCM read: clean claim, denials, AR days, net collection, and cost to collect vs. target.",
  "vertical": "Healthcare & Life Sciences",
  "lineOfBusiness": "Finance",
  "demoTopic": "Claims / remits, AR, Denials",
  "integrations": [],
  "pack": "Healthcare Revenue Cycle"
}, {
  "id": "saas_efficiency_scorecard",
  "name": "SaaS Efficiency Scorecard",
  "blurb": "The capital-efficiency scorecard — Rule of 40, burn multiple, magic number, CAC payback, margins.",
  "vertical": "SaaS",
  "lineOfBusiness": "Finance",
  "demoTopic": "General Ledger, Subscriptions",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "unit_economics_review",
  "name": "Unit Economics Review",
  "blurb": "CAC, CAC payback, LTV, and LTV:CAC by segment — with the assumptions stated.",
  "vertical": "SaaS, Subscription",
  "lineOfBusiness": "Finance",
  "demoTopic": "Subscriptions, General Ledger",
  "integrations": [],
  "pack": "Finance"
}, {
  "id": "account_360_lookup",
  "name": "Account 360 Lookup",
  "blurb": "One call-ready view of an account — plan and revenue, usage, support, and sentiment — in a single step.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "CRM, Product Usage, Support Tickets, NPS Scores",
  "integrations": ["Slack", "Google Drive", "Google Calendar"],
  "pack": "Customer Success"
}, {
  "id": "churn_risk_radar",
  "name": "Churn Risk Radar",
  "blurb": "Proactive early-warning: accounts with declining usage, support spikes, or negative sentiment — before renewal.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Product usage, Support tickets, Subscriptions",
  "integrations": ["Slack", "Google Drive"],
  "pack": "Customer Success"
}, {
  "id": "csm_book_of_business",
  "name": "CSM Book of Business",
  "blurb": "Accounts and ARR per CSM, health mix, and at-risk concentration — for coverage and rebalancing.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Subscriptions",
  "integrations": [],
  "pack": "Customer Success"
}, {
  "id": "customer_health_scorecard",
  "name": "Customer Health Scorecard",
  "blurb": "Scores the whole book green/yellow/red and ranks the at-risk ARR to work first.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Subscriptions, Product usage, Support tickets",
  "integrations": [],
  "pack": "Customer Success"
}, {
  "id": "embed_url_decoder",
  "name": "Embed URL Decoder",
  "blurb": "Paste an embed URL and get every parameter decoded, explained, and validated — without guessing at signature validity.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "A pasted embed URL (no warehouse data)",
  "integrations": [],
  "pack": ""
}, {
  "id": "expansion_upsell_finder",
  "name": "Expansion & Upsell Finder",
  "blurb": "Managed-book accounts ripe for expansion — seat utilization, usage growth, plan-limit pressure.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Subscriptions, Product usage",
  "integrations": [],
  "pack": "Customer Success"
}, {
  "id": "nps_promoter_analysis",
  "name": "NPS & Promoter Analysis",
  "blurb": "NPS score and trend, promoter/detractor breakdown, comment themes, and reference candidates.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "NPS survey responses",
  "integrations": [],
  "pack": "Customer Success"
}, {
  "id": "onboarding_time_to_value",
  "name": "Onboarding & Time-to-Value",
  "blurb": "Onboarding cohort progress, median time-to-value, and the stalled onboards to rescue.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Product usage, Subscriptions",
  "integrations": [],
  "pack": "Customer Success"
}, {
  "id": "product_adoption_report",
  "name": "Product Adoption Report",
  "blurb": "Feature adoption across accounts, underutilized high-value features, and low-adoption accounts to nudge.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Product usage",
  "integrations": [],
  "pack": "Customer Success"
}, {
  "id": "qbr_prep",
  "name": "QBR Prep",
  "blurb": "A value-realization QBR brief for one account — outcomes, adoption, health, and next-quarter goals.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Subscriptions, Product usage, Support tickets, NPS",
  "integrations": ["Google Drive", "Slack", "Google Calendar"],
  "pack": "Customer Success"
}, {
  "id": "support_ticket_trends",
  "name": "Support Ticket Trends",
  "blurb": "Ticket volume, resolution time, CSAT, escalations, and the accounts generating the most load.",
  "vertical": "",
  "lineOfBusiness": "Customer Success",
  "demoTopic": "Support tickets",
  "integrations": [],
  "pack": "Customer Success"
}, {
  "id": "bestsellers_slow_movers",
  "name": "Best Sellers & Slow Movers",
  "blurb": "Top sellers and slow movers by units, sales, and sell-through for a period — the quick merch read.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Sales, Inventory",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "gmroi_inventory_productivity",
  "name": "GMROI & Inventory Turns",
  "blurb": "Gross-margin return on inventory and turns by category — where inventory dollars are working or stuck.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Sales, Inventory, Cost",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "markdown_aged_inventory",
  "name": "Markdown & Aged Inventory",
  "blurb": "Aged and slow-moving stock, markdown-rate trend, and the clearance candidates with the most exposure.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Inventory, Sales",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "merch_business_review",
  "name": "Merch Business Review",
  "blurb": "The weekly merch read: sales vs plan and LY, top movers, sell-through/WOS flags, OOS, and markdown exposure.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Sales, Inventory, Merchandise plan",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "open_to_buy_check",
  "name": "Open-to-Buy Check",
  "blurb": "Plan vs. actual sales, receipts, and inventory — and how much open-to-buy budget is left by class.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Merchandise plan, Sales, Inventory, Purchase orders",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "out_of_stock_lost_sales",
  "name": "Out-of-Stock & Lost Sales",
  "blurb": "In-stock/OOS rate by product-location and an estimate of sales lost on out-of-stock best-sellers.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Inventory, Sales",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "sell_through_weeks_of_supply",
  "name": "Sell-Through & Weeks of Supply",
  "blurb": "Sell-through and weeks of supply by product — flags what to chase and what to mark down.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Sales, Inventory",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "size_curve_assortment_health",
  "name": "Size Curve Health",
  "blurb": "Finds styles with broken sizes — key selling sizes out of stock while the style still shows inventory.",
  "vertical": "Retail & eCommerce",
  "lineOfBusiness": "Operations",
  "demoTopic": "Sales, Inventory",
  "integrations": [],
  "pack": "Retail Merchandising"
}, {
  "id": "style_my_charts",
  "name": "Style My Charts",
  "blurb": "Applies a clean house style to every visualization on the current dashboard — axes, number formats, legends, and palette — in one pass.",
  "vertical": "",
  "lineOfBusiness": "Operations",
  "demoTopic": "Any dashboard",
  "integrations": [],
  "pack": ""
}, {
  "id": "user_activation_funnel",
  "name": "Activation Funnel",
  "blurb": "New-user activation to the aha moment, with the biggest drop-off step and activation rate.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Product usage events",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "dau_wau_mau_snapshot",
  "name": "DAU/WAU/MAU Snapshot",
  "blurb": "Active-user counts, DAU/MAU stickiness, and the trend — the quick engagement pulse.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Product usage events",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "dora_metrics_review",
  "name": "DORA Metrics Review",
  "blurb": "The four DORA keys — deploy frequency, lead time, change failure rate, and recovery time — with trend.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Deployments, Incidents",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "feature_launch_tracker",
  "name": "Feature Launch Tracker",
  "blurb": "Post-launch reach, adoption curve, and repeat use for a specific new feature — is it sticking?",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Product usage events",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "product_funnel_conversion",
  "name": "Funnel Conversion",
  "blurb": "Step-by-step conversion and drop-off for any ordered event funnel you define.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Product usage events",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "engagement_growth_accounting",
  "name": "Growth Accounting",
  "blurb": "Decomposes active-user change into new, retained, resurrected, and churned — plus the Quick Ratio.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Product usage events",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "product_metric_investigator",
  "name": "Metric Move Investigator",
  "blurb": "A core product metric moved — this agent finds the segment, release, or incident that explains it.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Product usage events, Deployments, Incidents",
  "integrations": ["GitHub", "Slack"],
  "pack": "Product & Engineering"
}, {
  "id": "retention_cohort_analysis",
  "name": "Retention Cohort Analysis",
  "blurb": "Cohort retention curves by signup period — and whether the curve is flattening or decaying.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Product usage events",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "sprint_delivery_review",
  "name": "Sprint Delivery Review",
  "blurb": "Throughput, cycle time, carryover, and scope change for a sprint — the retro prep.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Issues / tickets",
  "integrations": [],
  "pack": "Product & Engineering"
}, {
  "id": "whats_shipped_digest",
  "name": "What Shipped",
  "blurb": "Categorizes and summarizes the major things shipped in a period — a share-ready ship report from merged PRs.",
  "vertical": "",
  "lineOfBusiness": "Product",
  "demoTopic": "Merged pull requests",
  "integrations": ["GitHub", "Slack"],
  "pack": "Product & Engineering"
}];

export const LibraryGallery = ({skills = [], apps = [], routines = []}) => {
  const ITEMS = [...apps.map(a => ({
    ...a,
    type: "App"
  })), ...skills.map(s => ({
    ...s,
    type: s.type || "Skill"
  })), ...routines.map(r => ({
    ...r,
    type: "Routine"
  }))];
  const lobsOf = s => Array.isArray(s.lineOfBusiness) ? s.lineOfBusiness : s.lineOfBusiness ? [s.lineOfBusiness] : [];
  const vertsOf = s => Array.isArray(s.vertical) ? s.vertical : s.vertical ? String(s.vertical).split(",").map(x => x.trim()).filter(Boolean) : [];
  const FACETS = [{
    key: "type",
    label: "Type",
    get: s => [s.type],
    order: ["App", "Routine", "Skill"]
  }, {
    key: "vertical",
    label: "Vertical",
    get: vertsOf,
    order: ["Advertising", "Media & Entertainment", "Finance", "SaaS", "Retail & eCommerce"]
  }, {
    key: "lineOfBusiness",
    label: "Function",
    get: lobsOf
  }];
  const TYPE_BLURB = {
    App: "A live, embeddable Omni page — click a tile to see the screenshot and copy a rebuild prompt or the HTML source.",
    Skill: "A reusable Blobby prompt cartridge — copy the YAML into your own Omni instance's skills.",
    Routine: "A scheduled automation prompt — copy it into a routine to run on a cadence."
  };
  const PAGE_SIZE = 24;
  const TAG_PAGE_SIZE = 5;
  const PINK = "#FF5FA2";
  const TYPE_COLORS = {
    App: "#0F9BFF",
    Skill: PINK,
    Routine: "#80C501"
  };
  const BORDER = "1px solid rgba(128,128,128,0.28)";
  const MUTED = "var(--lib-muted, #6b7280)";
  const [sel, setSel] = useState({});
  const [q, setQ] = useState("");
  const [active, setActive] = useState(null);
  const [copied, setCopied] = useState(null);
  const [page, setPage] = useState(1);
  const [expanded, setExpanded] = useState({});
  const [panelWidth, setPanelWidth] = useState(680);
  const [shotIdx, setShotIdx] = useState(0);
  const [lightbox, setLightbox] = useState(false);
  const [exporting, setExporting] = useState(null);
  const [skillYaml, setSkillYaml] = useState(null);
  const [appContentCache, setAppContentCache] = useState({});
  const [routinePrompts, setRoutinePrompts] = useState(null);
  const slugOf = s => s.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
  const copy = (text, key) => {
    if (navigator.clipboard) navigator.clipboard.writeText(text);
    setCopied(key);
    setTimeout(() => setCopied(null), 1500);
  };
  const shotsOf = s => s && s.type === "App" ? s.screenshots && s.screenshots.length ? s.screenshots : s.screenshot ? [s.screenshot] : [] : [];
  const downloadBlob = (blob, name) => {
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = name;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 4000);
  };
  const makeZip = files => {
    const te = new TextEncoder();
    const crcTable = [];
    for (let n = 0; n < 256; n++) {
      let c = n;
      for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ c >>> 1 : c >>> 1;
      crcTable[n] = c >>> 0;
    }
    const crc32 = buf => {
      let c = 0xffffffff;
      for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ c >>> 8;
      return (c ^ 0xffffffff) >>> 0;
    };
    const num = (n, bytes) => {
      const out = new Uint8Array(bytes);
      for (let i = 0; i < bytes; i++) out[i] = n >>> 8 * i & 0xff;
      return out;
    };
    const chunks = [];
    const central = [];
    let offset = 0;
    let cdSize = 0;
    files.forEach(f => {
      const name = te.encode(f.name);
      const data = f.data instanceof Uint8Array ? f.data : te.encode(f.data);
      const crc = crc32(data);
      const common = [num(20, 2), num(0x0800, 2), num(0, 2), num(0, 2), num(0x21, 2), num(crc, 4), num(data.length, 4), num(data.length, 4), num(name.length, 2), num(0, 2)];
      const local = [num(0x04034b50, 4), ...common, name, data];
      const entry = [num(0x02014b50, 4), num(20, 2), ...common, num(0, 2), num(0, 2), num(0, 2), num(0, 4), num(offset, 4), name];
      chunks.push(...local);
      central.push(...entry);
      offset += local.reduce((n, p) => n + p.length, 0);
      cdSize += entry.reduce((n, p) => n + p.length, 0);
    });
    const end = [num(0x06054b50, 4), num(0, 2), num(0, 2), num(files.length, 2), num(files.length, 2), num(cdSize, 4), num(offset, 4), num(0, 2)];
    return new Blob([...chunks, ...central, ...end], {
      type: "application/zip"
    });
  };
  const fetchBin = url => fetch(url).then(r => r.arrayBuffer()).then(b => new Uint8Array(b));
  const fetchPng = url => new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      const c = document.createElement("canvas");
      c.width = img.naturalWidth;
      c.height = img.naturalHeight;
      c.getContext("2d").drawImage(img, 0, 0);
      c.toBlob(b => {
        if (!b) return reject(new Error("toBlob returned null"));
        b.arrayBuffer().then(ab => resolve(new Uint8Array(ab)), reject);
      }, "image/png");
    };
    img.onerror = () => reject(new Error(`could not load ${url}`));
    img.src = url;
  });
  const fetchShot = url => fetchPng(url).then(data => ({
    data,
    ext: "png"
  })).catch(() => fetchBin(url).then(data => ({
    data,
    ext: "webp"
  })));
  const exportZip = which => {
    if (!active || exporting) return;
    setExporting(which);
    const shotUrls = shotsOf(active);
    Promise.all(shotUrls.map(u => fetchShot(u))).then(shots => {
      const files = shots.map((s, i) => ({
        name: `${active.id}${i ? `-${i + 1}` : ""}.${s.ext}`,
        data: s.data
      }));
      if (which === "assets") {
        const content = appContentCache[active.id] || ({});
        if (content.prompt) files.push({
          name: `${active.id}-rebuild-prompt.txt`,
          data: content.prompt
        });
        if (content.setup) files.push({
          name: `${active.id}-setup-notes.txt`,
          data: content.setup
        });
        if (content.html) files.push({
          name: `${active.id}.html`,
          data: content.html
        });
      }
      if (files.length) downloadBlob(makeZip(files), `${active.id}-${which === "assets" ? "assets" : "screenshots"}.zip`);
    }).finally(() => setExporting(null));
  };
  const startDrag = e => {
    e.preventDefault();
    const onMove = ev => {
      const w = Math.min(Math.max(window.innerWidth - ev.clientX, 360), window.innerWidth * 0.95);
      setPanelWidth(w);
    };
    const onUp = () => {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
      document.body.style.userSelect = "";
    };
    document.body.style.userSelect = "none";
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  };
  useEffect(() => {
    let alive = true;
    const grab = (url, set) => fetch(url).then(r => r.ok ? r.json() : {}).then(data => {
      if (alive) set(data);
    }).catch(() => {
      if (alive) set({});
    });
    grab("/library/skills-yaml.json", setSkillYaml);
    grab("/library/routines-prompts.json", setRoutinePrompts);
    return () => {
      alive = false;
    };
  }, []);
  useEffect(() => {
    if (!active || active.type !== "App" || appContentCache[active.id] !== undefined) return;
    let alive = true;
    fetch(`/library/apps/content/${active.id}.json${active.contentV ? `?v=${active.contentV}` : ""}`).then(r => r.ok ? r.json() : {
      prompt: null,
      html: null
    }).then(data => {
      if (alive) setAppContentCache(c => ({
        ...c,
        [active.id]: data
      }));
    }).catch(() => {
      if (alive) setAppContentCache(c => ({
        ...c,
        [active.id]: {
          prompt: null,
          html: null
        }
      }));
    });
    return () => {
      alive = false;
    };
  }, [active]);
  useEffect(() => {
    const slug = decodeURIComponent((window.location.hash || "").replace("#", ""));
    if (slug) {
      const found = ITEMS.find(s => slugOf(s) === slug);
      if (found) setActive(found);
    }
  }, []);
  useEffect(() => {
    const onKey = e => {
      if (e.key === "Escape") setActive(null);
    };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = active ? "hidden" : "";
    if (active) {
      history.replaceState(null, "", "#" + slugOf(active));
    } else if (window.location.hash) {
      history.replaceState(null, "", window.location.pathname + window.location.search);
    }
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [active]);
  useEffect(() => {
    setShotIdx(0);
    setLightbox(false);
  }, [active]);
  const activeShots = shotsOf(active);
  const shownShot = Math.min(shotIdx, Math.max(0, activeShots.length - 1));
  const stepShot = dir => setShotIdx(i => (i + dir + activeShots.length) % activeShots.length);
  useEffect(() => {
    if (!lightbox) return;
    const onKey = e => {
      if (e.key === "Escape") {
        e.stopPropagation();
        setLightbox(false);
      }
      if (e.key === "ArrowRight" && activeShots.length > 1) stepShot(1);
      if (e.key === "ArrowLeft" && activeShots.length > 1) stepShot(-1);
    };
    document.addEventListener("keydown", onKey, true);
    return () => document.removeEventListener("keydown", onKey, true);
  }, [lightbox, activeShots.length]);
  const optionsFor = facet => {
    const counts = {};
    ITEMS.forEach(s => facet.get(s).forEach(v => {
      if (v) counts[v] = (counts[v] || 0) + 1;
    }));
    const rank = v => facet.order ? facet.order.indexOf(v) + 1 || Infinity : Infinity;
    return Object.keys(counts).sort((a, b) => rank(a) !== rank(b) ? rank(a) - rank(b) : a.localeCompare(b)).map(v => ({
      value: v,
      count: counts[v]
    }));
  };
  const isActive = (key, val) => (sel[key] || []).includes(val);
  const toggle = (key, val) => {
    setSel(prev => {
      const cur = prev[key] || [];
      const next = cur.includes(val) ? cur.filter(x => x !== val) : [...cur, val];
      return {
        ...prev,
        [key]: next
      };
    });
  };
  const clearAll = () => {
    setSel({});
    setQ("");
  };
  const activeCount = FACETS.reduce((n, f) => n + (sel[f.key] || []).length, 0) + (q.trim() ? 1 : 0);
  const filtered = ITEMS.filter(s => {
    if (q.trim()) {
      const hay = (s.name + " " + (s.blurb || "") + " " + (s.pack || "") + " " + (s.owner || "") + " " + s.type).toLowerCase();
      if (!hay.includes(q.toLowerCase().trim())) return false;
    }
    for (const f of FACETS) {
      const chosen = sel[f.key] || [];
      if (chosen.length === 0) continue;
      if (!f.get(s).some(v => chosen.includes(v))) return false;
    }
    return true;
  }).sort((a, b) => (a.flagged ? 1 : 0) - (b.flagged ? 1 : 0));
  const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const clampedPage = Math.min(page, pageCount);
  const paged = filtered.slice((clampedPage - 1) * PAGE_SIZE, clampedPage * PAGE_SIZE);
  useEffect(() => {
    setPage(1);
  }, [q, sel]);
  const chip = active => ({
    cursor: "pointer",
    border: active ? `1px solid ${PINK}` : BORDER,
    background: active ? PINK : "transparent",
    color: active ? "#fff" : "inherit",
    borderRadius: "999px",
    padding: "4px 12px",
    fontSize: "13px",
    lineHeight: "1.4",
    fontWeight: active ? 600 : 400
  });
  const badge = {
    border: BORDER,
    borderRadius: "6px",
    padding: "2px 8px",
    fontSize: "11px",
    color: MUTED,
    whiteSpace: "nowrap"
  };
  const typeBadge = t => ({
    ...badge,
    color: TYPE_COLORS[t] || MUTED,
    borderColor: TYPE_COLORS[t] || MUTED,
    fontWeight: 600
  });
  const shotArrow = side => ({
    position: "absolute",
    top: "50%",
    transform: "translateY(-50%)",
    [side]: "8px",
    width: "36px",
    height: "36px",
    borderRadius: "50%",
    border: "none",
    background: "rgba(0,0,0,0.55)",
    color: "#fff",
    fontSize: "22px",
    lineHeight: 1,
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    paddingBottom: "3px"
  });
  const copyBtn = (key, label, doneLabel, text) => <button type="button" onClick={() => copy(text, key)} style={{
    cursor: "pointer",
    background: "transparent",
    border: BORDER,
    borderRadius: "6px",
    padding: "4px 10px",
    fontSize: "12px",
    fontWeight: 600,
    color: "inherit"
  }}>
      {copied === key ? doneLabel : label}
    </button>;
  const codePane = text => <pre style={{
    margin: 0,
    padding: "12px",
    borderRadius: "8px",
    border: BORDER,
    background: "rgba(128,128,128,0.10)",
    fontSize: "12px",
    lineHeight: 1.5,
    maxHeight: "300px",
    overflow: "auto",
    whiteSpace: "pre-wrap"
  }}><code>{text}</code></pre>;
  const drawerSection = (title, actions, body) => <div style={{
    borderTop: BORDER,
    paddingTop: "16px"
  }}>
      <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "space-between",
    marginBottom: "8px"
  }}>
        <span style={{
    fontSize: "12px",
    fontWeight: 600,
    textTransform: "uppercase",
    letterSpacing: "0.04em",
    color: MUTED
  }}>{title}</span>
        {actions}
      </div>
      {body}
    </div>;
  const activeAppContent = active && active.type === "App" ? appContentCache[active.id] : null;
  const appContentLoading = active && active.type === "App" && appContentCache[active.id] === undefined;
  return <div className="lib-page" style={{
    marginTop: "1rem"
  }}>
      <div className="guides-layout">
        <aside className="guides-sidebar">
          <div className="guides-filters">
            {activeCount > 0 && <button type="button" onClick={clearAll} className="guides-clear-button">
                Clear all filters
              </button>}

            <div className="guides-filter-group">
              <label className="guides-filter-label">Search</label>
              <input type="text" value={q} onChange={e => setQ(e.target.value)} placeholder="Search apps, routines, skills…" className="guides-filter-input" />
            </div>

            {FACETS.map(f => {
    const options = optionsFor(f);
    const isExpanded = !!expanded[f.key];
    const chosen = sel[f.key] || [];
    const topN = options.slice(0, TAG_PAGE_SIZE);
    const hiddenSelected = options.filter(o => chosen.includes(o.value) && !topN.includes(o));
    const collapsedVisible = [...topN, ...hiddenSelected];
    const visible = isExpanded ? options : collapsedVisible;
    const remaining = options.length - collapsedVisible.length;
    return <div key={f.key} className="guides-filter-group">
                  <label className="guides-filter-label">{f.label}</label>
                  <div>
                    {visible.map(o => <button key={o.value} type="button" aria-pressed={isActive(f.key, o.value)} onClick={() => toggle(f.key, o.value)} title={f.key === "type" ? TYPE_BLURB[o.value] : undefined} className={`guides-filter-button ${isActive(f.key, o.value) ? "active" : ""}`}>
                        {o.value}
                      </button>)}
                  </div>
                  {(remaining > 0 || isExpanded) && <button type="button" aria-expanded={isExpanded} onClick={() => setExpanded(e => ({
      ...e,
      [f.key]: !e[f.key]
    }))} className="guides-filter-button" style={{
      color: PINK,
      fontWeight: 600
    }}>
                      {isExpanded ? "Show less" : `Show ${remaining} more…`}
                    </button>}
                </div>;
  })}
          </div>
        </aside>

        <main className="guides-content">
          <div className="guides-results-count">
            {filtered.length} {filtered.length === 1 ? "item" : "items"}
          </div>

          <div style={{
    display: "grid",
    gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
    gap: "16px"
  }}>
            {paged.map(s => <div key={s.type + s.id} role="button" tabIndex={0} onClickCapture={e => {
    e.stopPropagation();
    setActive(s);
  }} onKeyDown={e => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      setActive(s);
    }
  }} className="lib-card" style={{
    border: BORDER,
    borderRadius: "12px",
    overflow: "hidden",
    display: "flex",
    flexDirection: "column",
    cursor: "pointer"
  }}>
            {s.type === "App" && s.screenshot && <div style={{
    aspectRatio: "16/10",
    overflow: "hidden",
    borderBottom: BORDER,
    background: "rgba(128,128,128,0.06)"
  }}>
                <img src={s.screenshot} alt={s.name} loading="lazy" style={{
    width: "100%",
    height: "100%",
    objectFit: "cover",
    objectPosition: "top"
  }} />
              </div>}
            <div style={{
    padding: "16px",
    display: "flex",
    flexDirection: "column",
    gap: "10px",
    flex: 1
  }}>
              <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "6px"
  }}>
                <span style={typeBadge(s.type)} title={TYPE_BLURB[s.type]}>{s.type}</span>
                {vertsOf(s).map(v => <span key={v} style={badge}>{v}</span>)}
                {lobsOf(s).map(v => <span key={v} style={badge}>{v}</span>)}
                {s.cadence && <span style={badge}>{s.cadence}</span>}
              </div>
              <div style={{
    fontSize: "16px",
    fontWeight: 600
  }}>{s.name}</div>
              <div style={{
    fontSize: "14px",
    color: MUTED,
    flex: 1
  }}>{s.blurb}</div>
              {s.flagged && <div style={{
    fontSize: "12px",
    color: "#e8a030"
  }}>⚠ Screenshot & source pending — {s.flagged}</div>}
              {s.hasSetup && !s.flagged && <div style={{
    fontSize: "12px",
    color: "#a06020"
  }}>⚙ Requires extra setup — details inside</div>}
            </div>
          </div>)}
      </div>

      {filtered.length === 0 && <div style={{
    textAlign: "center",
    padding: "3rem 1rem",
    color: MUTED
  }}>
          No items match these filters. <button type="button" onClick={clearAll} style={{
    cursor: "pointer",
    background: "transparent",
    border: "none",
    color: PINK,
    fontWeight: 600,
    fontSize: "inherit",
    padding: 0
  }}>Clear all filters</button>
        </div>}

      {pageCount > 1 && <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "16px",
    margin: "2rem 0 1rem"
  }}>
          <button type="button" disabled={clampedPage === 1} onClick={() => setPage(p => Math.max(1, p - 1))} style={{
    cursor: clampedPage === 1 ? "default" : "pointer",
    opacity: clampedPage === 1 ? 0.4 : 1,
    background: "transparent",
    border: BORDER,
    borderRadius: "8px",
    padding: "6px 14px",
    fontSize: "14px",
    color: "inherit"
  }}>
            ← Prev
          </button>
          <span style={{
    fontSize: "14px",
    color: MUTED
  }}>Page {clampedPage} of {pageCount}</span>
          <button type="button" disabled={clampedPage === pageCount} onClick={() => setPage(p => Math.min(pageCount, p + 1))} style={{
    cursor: clampedPage === pageCount ? "default" : "pointer",
    opacity: clampedPage === pageCount ? 0.4 : 1,
    background: "transparent",
    border: BORDER,
    borderRadius: "8px",
    padding: "6px 14px",
    fontSize: "14px",
    color: "inherit"
  }}>
            Next →
          </button>
        </div>}
        </main>
      </div>

      <div onClick={() => setActive(null)} style={{
    position: "fixed",
    inset: 0,
    background: "rgba(0,0,0,0.45)",
    opacity: active ? 1 : 0,
    pointerEvents: active ? "auto" : "none",
    transition: "opacity 0.2s ease",
    zIndex: 9998
  }} />

      <div className="lib-panel" role="dialog" aria-modal="true" aria-hidden={!active} style={{
    position: "fixed",
    top: 0,
    right: 0,
    height: "100%",
    width: "min(" + panelWidth + "px, 95vw)",
    boxSizing: "border-box",
    padding: "24px",
    overflowY: "auto",
    transform: active ? "translateX(0)" : "translateX(100%)",
    transition: "transform 0.25s ease",
    zIndex: 9999,
    boxShadow: active ? "-8px 0 24px rgba(0,0,0,0.18)" : "none"
  }}>
        <div onMouseDown={startDrag} title="Drag to resize" style={{
    position: "absolute",
    left: 0,
    top: 0,
    height: "100%",
    width: "10px",
    cursor: "ew-resize",
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
          <div style={{
    width: "3px",
    height: "44px",
    borderRadius: "3px",
    background: "rgba(128,128,128,0.45)"
  }} />
        </div>
        {active && <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "16px"
  }}>
            <button type="button" onClick={() => setActive(null)} aria-label="Back to library" style={{
    cursor: "pointer",
    background: "transparent",
    border: "none",
    color: MUTED,
    fontSize: "14px",
    fontWeight: 600,
    padding: 0,
    display: "flex",
    alignItems: "center",
    gap: "6px",
    alignSelf: "flex-start"
  }}>
              <span style={{
    fontSize: "18px",
    lineHeight: 1
  }}>←</span> Back
            </button>
            <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "flex-start",
    gap: "12px"
  }}>
              <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "6px"
  }}>
                <span style={typeBadge(active.type)} title={TYPE_BLURB[active.type]}>{active.type}</span>
                {vertsOf(active).map(v => <span key={v} style={badge}>{v}</span>)}
                {lobsOf(active).map(v => <span key={v} style={badge}>{v}</span>)}
                {active.cadence && <span style={badge}>{active.cadence}</span>}
              </div>
              <div style={{
    display: "flex",
    alignItems: "center",
    gap: "12px",
    flexShrink: 0
  }}>
                {active.type === "App" && (activeShots.length > 0 || active.hasPrompt || active.hasHtml) && <button type="button" onClick={() => exportZip("assets")} disabled={!!exporting || appContentLoading} style={{
    cursor: exporting || appContentLoading ? "default" : "pointer",
    background: "transparent",
    border: BORDER,
    borderRadius: "8px",
    padding: "6px 12px",
    fontSize: "12px",
    fontWeight: 600,
    color: "inherit",
    opacity: appContentLoading ? 0.5 : 1,
    whiteSpace: "nowrap"
  }}>
                    {exporting === "assets" ? "Preparing zip…" : "⬇ Export all assets"}
                  </button>}
                <button type="button" onClick={() => setActive(null)} aria-label="Close" style={{
    cursor: "pointer",
    background: "transparent",
    border: "none",
    color: "inherit",
    fontSize: "22px",
    lineHeight: 1,
    padding: 0
  }}>×</button>
              </div>
            </div>
            <div style={{
    fontSize: "22px",
    fontWeight: 700
  }}>{active.name}</div>
            <div style={{
    fontSize: "15px",
    color: MUTED
  }}>{active.blurb}</div>

            {active.type === "App" && activeShots.length > 0 && <div>
                <div style={{
    position: "relative"
  }}>
                  <img src={activeShots[shownShot]} alt={`${active.name} — screenshot ${shownShot + 1} of ${activeShots.length}`} onClickCapture={e => {
    e.stopPropagation();
    setLightbox(true);
  }} title="Click to expand" style={{
    width: "100%",
    borderRadius: "10px",
    border: BORDER,
    cursor: "zoom-in",
    display: "block"
  }} />
                  {activeShots.length > 1 && <>
                      <button type="button" aria-label="Previous screenshot" onClick={e => {
    e.stopPropagation();
    stepShot(-1);
  }} style={shotArrow("left")}>‹</button>
                      <button type="button" aria-label="Next screenshot" onClick={e => {
    e.stopPropagation();
    stepShot(1);
  }} style={shotArrow("right")}>›</button>
                      <span style={{
    position: "absolute",
    bottom: "8px",
    right: "10px",
    background: "rgba(0,0,0,0.6)",
    color: "#fff",
    borderRadius: "999px",
    padding: "2px 10px",
    fontSize: "12px",
    fontWeight: 600
  }}>{shownShot + 1} / {activeShots.length}</span>
                    </>}
                </div>
                {activeShots.length > 1 && <button type="button" onClick={() => exportZip("shots")} disabled={!!exporting} style={{
    cursor: exporting ? "default" : "pointer",
    background: "transparent",
    border: "none",
    color: PINK,
    fontWeight: 600,
    fontSize: "13px",
    padding: "6px 0 0",
    alignSelf: "flex-start"
  }}>
                    {exporting === "shots" ? "Preparing zip…" : `Download all screenshots (${activeShots.length})`}
                  </button>}
              </div>}
            {active.type === "App" && active.flagged && <div style={{
    fontSize: "13px",
    color: "#a06020",
    background: "rgba(232,160,48,0.12)",
    border: "1px solid rgba(232,160,48,0.5)",
    borderRadius: "8px",
    padding: "10px 12px"
  }}>⚠ {active.flagged}</div>}

            <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "10px",
    borderTop: BORDER,
    paddingTop: "16px"
  }}>
              {[["Vertical", vertsOf(active).join(", ")], ["Line of business", lobsOf(active).join(", ")], ["Cadence", active.cadence], ["Data it needs", active.demoTopic], ["Works with", (active.integrations || []).length ? active.integrations.join(", ") : ""]].filter(([, v]) => v).map(([k, v]) => <div key={k} style={{
    display: "grid",
    gridTemplateColumns: "120px 1fr",
    gap: "12px",
    fontSize: "14px"
  }}>
                  <span style={{
    color: MUTED
  }}>{k}</span>
                  <span>{v}</span>
                </div>)}
            </div>

            {active.type === "Skill" && drawerSection("Skill YAML", skillYaml && skillYaml[active.id] && copyBtn("yaml", "Copy YAML", "Copied!", skillYaml[active.id]), skillYaml && skillYaml[active.id] ? codePane(skillYaml[active.id]) : <div style={{
    fontSize: "13px",
    color: MUTED,
    fontStyle: "italic"
  }}>{skillYaml === null ? "Loading YAML…" : "YAML unavailable"}</div>)}

            {active.type === "Routine" && drawerSection("Routine prompt", routinePrompts && routinePrompts[active.id] && copyBtn("prompt", "Copy prompt", "Copied!", routinePrompts[active.id]), routinePrompts && routinePrompts[active.id] ? codePane(routinePrompts[active.id]) : <div style={{
    fontSize: "13px",
    color: MUTED,
    fontStyle: "italic"
  }}>{routinePrompts === null ? "Loading prompt…" : "Prompt unavailable"}</div>)}

            {active.type === "App" && active.hasSetup && drawerSection("Setup required", null, activeAppContent && activeAppContent.setup ? <div style={{
    fontSize: "13px",
    lineHeight: 1.55,
    whiteSpace: "pre-wrap",
    border: "1px solid rgba(232,160,48,0.5)",
    background: "rgba(232,160,48,0.10)",
    borderRadius: "8px",
    padding: "12px",
    maxHeight: "300px",
    overflow: "auto"
  }}>{activeAppContent.setup}</div> : <div style={{
    fontSize: "13px",
    color: MUTED,
    fontStyle: "italic"
  }}>{appContentLoading ? "Loading…" : "Setup notes unavailable"}</div>)}

            {active.type === "App" && drawerSection("Rebuild prompt", activeAppContent && activeAppContent.prompt && copyBtn("prompt", "Copy prompt", "Copied!", activeAppContent.prompt), activeAppContent && activeAppContent.prompt ? codePane(activeAppContent.prompt) : <div style={{
    fontSize: "13px",
    color: MUTED,
    fontStyle: "italic"
  }}>{appContentLoading ? "Loading…" : "Prompt not available yet"}</div>)}

            {active.type === "App" && drawerSection("App HTML", activeAppContent && activeAppContent.html && copyBtn("html", "Copy HTML", "Copied!", activeAppContent.html), activeAppContent && activeAppContent.html ? codePane(activeAppContent.html.length > 20000 ? activeAppContent.html.slice(0, 20000) + "\n… (truncated preview — Copy HTML grabs the full source)" : activeAppContent.html) : <div style={{
    fontSize: "13px",
    color: MUTED,
    fontStyle: "italic"
  }}>{appContentLoading ? "Loading…" : "HTML not available yet"}</div>)}

            <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "10px",
    alignItems: "center",
    borderTop: BORDER,
    paddingTop: "16px"
  }}>
              <button type="button" onClick={() => copy(window.location.origin + window.location.pathname + "#" + slugOf(active), "link")} style={{
    cursor: "pointer",
    background: PINK,
    border: "none",
    borderRadius: "8px",
    padding: "10px 18px",
    fontSize: "14px",
    fontWeight: 600,
    color: "#fff"
  }}>
                {copied === "link" ? "Link copied!" : "Copy link to share"}
              </button>
            </div>
          </div>}
      </div>

      {lightbox && active && activeShots.length > 0 && <div onClick={() => setLightbox(false)} role="dialog" aria-modal="true" aria-label={`${active.name} screenshot`} style={{
    position: "fixed",
    inset: 0,
    zIndex: 10001,
    background: "rgba(0,0,0,0.88)",
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
          <img src={activeShots[shownShot]} alt={`${active.name} — screenshot ${shownShot + 1} of ${activeShots.length}`} onClickCapture={e => e.stopPropagation()} style={{
    maxWidth: "94vw",
    maxHeight: "90vh",
    borderRadius: "8px",
    boxShadow: "0 12px 48px rgba(0,0,0,0.5)"
  }} />
          <button type="button" onClick={e => {
    e.stopPropagation();
    setLightbox(false);
  }} aria-label="Close fullscreen" style={{
    position: "fixed",
    top: "18px",
    right: "22px",
    background: "rgba(0,0,0,0.55)",
    border: "none",
    borderRadius: "50%",
    width: "40px",
    height: "40px",
    color: "#fff",
    fontSize: "22px",
    lineHeight: 1,
    cursor: "pointer"
  }}>×</button>
          {activeShots.length > 1 && <>
              <button type="button" aria-label="Previous screenshot" onClick={e => {
    e.stopPropagation();
    stepShot(-1);
  }} style={{
    ...shotArrow("left"),
    position: "fixed",
    left: "20px",
    width: "46px",
    height: "46px",
    fontSize: "28px"
  }}>‹</button>
              <button type="button" aria-label="Next screenshot" onClick={e => {
    e.stopPropagation();
    stepShot(1);
  }} style={{
    ...shotArrow("right"),
    position: "fixed",
    right: "20px",
    width: "46px",
    height: "46px",
    fontSize: "28px"
  }}>›</button>
              <span style={{
    position: "fixed",
    bottom: "22px",
    left: "50%",
    transform: "translateX(-50%)",
    background: "rgba(0,0,0,0.6)",
    color: "#fff",
    borderRadius: "999px",
    padding: "4px 14px",
    fontSize: "13px",
    fontWeight: 600
  }}>{shownShot + 1} / {activeShots.length}</span>
            </>}
        </div>}
    </div>;
};

<div className="lib-page" style={{ maxWidth: "1100px", margin: "0 auto", padding: "2.5rem 1.5rem" }}>
  <h1 style={{ fontSize: "2.25rem", fontWeight: 700, lineHeight: 1.15, margin: "0 0 0.75rem", color: "inherit" }}>Library</h1>

  <p style={{ fontSize: "1.05rem", lineHeight: 1.5, color: "var(--lib-muted)", margin: "0 0 0.75rem" }}>
    Explore, discover, and deploy <a href="/visualize-present/apps" target="_blank" rel="noopener noreferrer">Apps</a>, <a href="/ai/routines" target="_blank" rel="noopener noreferrer">Routines</a>, and <a href="/ai/skills" target="_blank" rel="noopener noreferrer">Skills</a> to jumpstart your AI transformation.
  </p>

  <ul style={{ listStyle: "none", padding: 0, margin: "0 0 0.5rem", fontSize: "0.95rem", lineHeight: 1.55, color: "var(--lib-muted)", display: "flex", flexDirection: "column", gap: "0.35rem" }}>
    <li><strong style={{ color: "inherit" }}>Apps</strong> are fully custom HTML and JavaScript interfaces — their own layout, components, and interactivity — powered by live queries from your workbook.</li>
    <li><strong style={{ color: "inherit" }}>Routines</strong> run a prompt on a schedule and deliver the Omni Agent's written answer to the people you choose.</li>
    <li><strong style={{ color: "inherit" }}>Skills</strong> package a set of instructions into a button anyone on your team can click in AI chat.</li>
  </ul>

  <LibraryGallery skills={SKILLS} apps={APPS} routines={ROUTINES} />
</div>
