<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Relish / Engineering Log</title>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.2/babel.min.js"></script>

<style>

  *{box-sizing:border-box;margin:0;padding:0}

  body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f8fafc;min-height:100vh}

  textarea,input,select{font-family:inherit}

  ::-webkit-scrollbar{width:6px} ::-webkit-scrollbar-track{background:#f1f1f1} ::-webkit-scrollbar-thumb{background:#ccc;border-radius:3px}

</style>

</head>

<body>

<div id="root"></div>

<script type="text/babel">

const {useState,useRef} = React;


const G="#1a6b3a",GL="#e8f5ee";


const PAGES=["All","Partner Portal","Homepage","Case Studies","Creator Landing Page","Contact / Signup","Find Campaigns","Local Campaigns","Map","Dashboard","Campaign Manager","Discover"];

const PHASES=["All","Phase 1","Phase 2","Phase 3"];

const STATUSES=["Backlog","In Progress","Done"];


const PHASE_COLORS={

  "Phase 1":{bg:"#eff6ff",text:"#1d4ed8",border:"#bfdbfe"},

  "Phase 2":{bg:"#f5f3ff",text:"#7c3aed",border:"#ddd6fe"},

  "Phase 3":{bg:"#f0fdf4",text:"#16a34a",border:"#bbf7d0"}

};

const STATUS_COLORS={

  Backlog:     {bg:"#f8fafc",text:"#64748b",border:"#e2e8f0"},

  "In Progress":{bg:"#eff6ff",text:"#2563eb",border:"#bfdbfe"},

  Done:        {bg:"#f0fdf4",text:"#16a34a",border:"#bbf7d0"}

};


const ALL_REQUESTS=[

  // HOMEPAGE

  {id:1,reqNum:"R001",page:"Homepage",feature:"Tile/card motif reinforcement",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Current hero lacks the tile/card motif that defines Relish's brand language. Reference Hummingbirds card grids for inspiration, then differentiate.",images:[]},

  {id:2,reqNum:"R002",page:"Homepage",feature:"Card text: black font on white",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Bottom homepage cards use light/inverted text that reduces legibility. All cards on white background should use near-black (#1a1a2e). Quick token/CSS fix.",images:[]},

  {id:3,reqNum:"R003",page:"Homepage",feature:"Product differentiators section",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Need a section explaining why Relish is different — 'fully managed', 'hyper-local creators', 'foot traffic + sales focus'. Compare against Hummingbirds. Copy TBD — Michael to draft.",images:[]},

  {id:4,reqNum:"R004",page:"Homepage",feature:"Creator showcase grid",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Visual section showcasing real Relish creators — split 'everyday' vs 'hyperlocal'. Each tile: follower count, city tag, content sample. Source from creator database.",images:[]},

  {id:5,reqNum:"R005",page:"Homepage",feature:"Case study teasers on homepage",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Surface 2–3 top case study cards on the homepage. Each card: brand name, result headline (e.g. '6× engagement'), link to full case study.",images:[]},

  {id:6,reqNum:"R006",page:"Homepage",feature:"Map section showing active markets",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Show a map with city pins for active markets. Each pin labeled with number of active campaigns in that city.",images:[]},

  {id:7,reqNum:"R007",page:"Homepage",feature:"Dual CTA hero (brands + creators)",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Current hero has single brand CTA. Need split CTA — 'Run a Campaign' for brands + 'Join as a Creator' for creators.",images:[]},

  {id:45,reqNum:"R045",page:"Homepage",feature:"Footer: two Instagram links",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Footer should link to both @tryrelish (brand) and @localrelishers (creator-focused). These are separate audiences.",images:[]},

  {id:46,reqNum:"R046",page:"Homepage",feature:"Background tile depth effect",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Hero section should have a subtle tile/grid pattern in the background for depth. Use CSS background-pattern or subtle SVG tile motif.",images:[]},

  {id:71,reqNum:"R071",page:"Homepage",feature:"👋 Try me — Fix footer copyright year",priority:"Phase 1",status:"Backlog",notes:"Quick fix — footer still shows 2024. Update to 2026. Test task for Golfie to verify the engineering log is working.",images:[]},

  // CASE STUDIES

  {id:8,reqNum:"R008",page:"Case Studies",feature:"Filter by city",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Brands want to find results in their own city. Add city filter — requires adding 'city' metadata field to CMS schema.",images:[]},

  {id:9,reqNum:"R009",page:"Case Studies",feature:"Filter by brand type",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Add brand-type filter (Restaurant / CPG / Retail) to case studies.",images:[]},

  {id:10,reqNum:"R010",page:"Case Studies",feature:"Improved card visual distinction",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Cards feel flat and samey. Increase min-height, improve shadow/border, more visual breathing room.",images:[]},

  {id:47,reqNum:"R047",page:"Case Studies",feature:"Results metrics on cards",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Each card should show a headline result metric — e.g. '6× engagement', '2,400 visits'. Add metrics field to CMS.",images:[]},

  {id:48,reqNum:"R048",page:"Case Studies",feature:"Carousel or animation on desktop",priority:"Phase 1",status:"Backlog",notes:"From recording #1. On desktop, case studies grid could animate as a carousel. Prototype in Figma first.",images:[]},

  {id:49,reqNum:"R049",page:"Case Studies",feature:"Mobile card height fix",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Cards collapse to inconsistent heights on mobile. Fix to be uniform and properly padded across all mobile screen sizes.",images:[]},

  {id:72,reqNum:"R072",page:"Case Studies",feature:"Section bridge / visual dividers",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Add thin horizontal dividers between major sections for clear visual breaks and improved scannability on scroll.",images:[]},

  // CREATOR LANDING PAGE

  {id:11,reqNum:"R011",page:"Creator Landing Page",feature:"Build dedicated creator marketing page",priority:"Phase 1",status:"Backlog",notes:"From recording #1. No standalone page for creators yet. Build from scratch — what is a Relisher, how it works, earnings/perks, apply CTA.",images:[]},

  {id:12,reqNum:"R012",page:"Creator Landing Page",feature:"Fix creator portal link",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Nav link to creator portal is broken or routes wrong. Quick fix — update href to correct creator portal URL.",images:[]},

  {id:50,reqNum:"R050",page:"Creator Landing Page",feature:"Earnings/perk transparency section",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Creators need to understand what they'll get before applying — free meals, discounts, paid deals, etc.",images:[]},

  // CONTACT/SIGNUP

  {id:13,reqNum:"R013",page:"Contact / Signup",feature:"Clarify creator vs brand contact flow",priority:"Phase 1",status:"Backlog",notes:"From recording #1. Contact page doesn't differentiate brand vs creator flows. Add helper copy and routing logic so each visitor type lands in the right flow.",images:[]},

  // FIND CAMPAIGNS

  {id:14,reqNum:"R014",page:"Find Campaigns",feature:"Location filter",priority:"Phase 1",status:"Backlog",notes:"From recording #3. Creators need to filter campaigns by city or region. Build location filter and wire to campaign API.",images:[]},

  {id:15,reqNum:"R015",page:"Find Campaigns",feature:"Brand type filter",priority:"Phase 1",status:"Backlog",notes:"From recording #3. Add brand-type filter (restaurant / CPG / retail) so creators can find preferred campaign types.",images:[]},

  {id:51,reqNum:"R051",page:"Find Campaigns",feature:"Total campaign count display",priority:"Phase 1",status:"Backlog",notes:"From recording #3. Add count badge at top (e.g. '143 campaigns available'). Reinforces platform scale.",images:[]},

  {id:52,reqNum:"R052",page:"Find Campaigns",feature:"Animated/carousel campaign card images",priority:"Phase 1",status:"Backlog",notes:"From recording #3. Campaign cards could use subtle image carousel to show multiple images per campaign.",images:[]},

  {id:53,reqNum:"R053",page:"Find Campaigns",feature:"Mobile discovery layout improvements",priority:"Phase 1",status:"Backlog",notes:"From recording #3. Top banner takes too much vertical space on mobile. Compress banner, test side-scroll card layout.",images:[]},

  // LOCAL CAMPAIGNS

  {id:16,reqNum:"R016",page:"Local Campaigns",feature:"Auto-select top 6 states",priority:"Phase 1",status:"Backlog",notes:"From recording #3. 'Top Destinations' section should dynamically surface 6 states with most active campaigns.",images:[]},

  {id:54,reqNum:"R054",page:"Local Campaigns",feature:"Big map discovery at top",priority:"Phase 1",status:"Backlog",notes:"From transcript. Large interactive map at the top of the page as primary navigation surface.",images:[]},

  {id:55,reqNum:"R055",page:"Local Campaigns",feature:"Prevent duplicate state cards",priority:"Phase 1",status:"Backlog",notes:"From transcript. Same state sometimes appears twice due to deduplication bug. Add deduplication logic to state-level grouping.",images:[]},

  {id:56,reqNum:"R056",page:"Local Campaigns",feature:"Compress mobile breadcrumb banner",priority:"Phase 1",status:"Backlog",notes:"From transcript. Breadcrumb banner takes excessive vertical space on mobile. Compress or hide on small screens.",images:[]},

  {id:73,reqNum:"R073",page:"Local Campaigns",feature:"Rename to 'Top Destinations'",priority:"Phase 1",status:"Backlog",notes:"From transcript. Replace 'Featured US States & Other Markets' with 'Top Destinations' — more inviting.",images:[]},

  // MAP

  {id:17,reqNum:"R017",page:"Map",feature:"CPG: show all retail location dots",priority:"Phase 1",status:"Backlog",notes:"From recording #3. CPG Digital campaigns should show every retail store as a small dot, not just brand HQ.",images:[]},

  {id:18,reqNum:"R018",page:"Map",feature:"Restaurant/retail: larger distinct markers",priority:"Phase 1",status:"Backlog",notes:"From recording #3. Restaurant and retail locations need larger, more distinct markers vs small CPG store dots.",images:[]},

  {id:57,reqNum:"R057",page:"Map",feature:"CMS-editable map featured locations",priority:"Phase 1",status:"Backlog",notes:"From recording #3. Internal team should update featured market pins via CMS without dev work.",images:[]},

  // DASHBOARD

  {id:19,reqNum:"R019",page:"Dashboard",feature:"Fix card click hierarchy (campaigns vs chart)",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Clicking campaign count card opens trend chart — surprises users expecting campaign list.",images:[]},

  {id:20,reqNum:"R020",page:"Dashboard",feature:"Add reach, impressions & engagement KPIs",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Surface network-wide reach, impressions, engagement per brand and campaign.",images:[]},

  {id:21,reqNum:"R021",page:"Dashboard",feature:"Units sold / weekly sales / stores selling",priority:"Phase 2",status:"Backlog",notes:"From recording #2. CPG brands need units sold, weekly sales velocity, stores actively selling.",images:[]},

  {id:22,reqNum:"R022",page:"Dashboard",feature:"Account-level success metrics section",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Per-brand section: application velocity, scheduling speed, post reach, creators deployed.",images:[]},

  {id:23,reqNum:"R023",page:"Dashboard",feature:"Configurable weekly metrics by account manager",priority:"Phase 2",status:"Backlog",notes:"From recording #2. AMs need admin interface to submit weekly metrics per brand.",images:[]},

  {id:24,reqNum:"R024",page:"Dashboard",feature:"Creator sourcing metrics",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Pull Mailchimp data: emails sent, open rate, click rate, DM count, reply rate per brand.",images:[]},

  {id:25,reqNum:"R025",page:"Dashboard",feature:"Fix 'needs scheduling' metric (cumulative)",priority:"Phase 2",status:"Backlog",notes:"From recording #2. 'Needs scheduling' shows point-in-time daily count that drops to zero even when backlogged.",images:[]},

  {id:26,reqNum:"R026",page:"Dashboard",feature:"AI brand summary agent",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Auto-generate one-paragraph brand summary: what they do, active cities, creator count, what customers are saying.",images:[]},

  {id:27,reqNum:"R027",page:"Dashboard",feature:"Holiday/seasonal campaign nudge",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Track when brand last launched a campaign and surface nudge card when inactive.",images:[]},

  {id:28,reqNum:"R028",page:"Dashboard",feature:"Aggregate post comments for brand sentiment",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Pull creator post comments, run AI summarization for brand sentiment.",images:[]},

  {id:58,reqNum:"R058",page:"Dashboard",feature:"Screenshot/content preview in post cards",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Post cards should show thumbnail preview of creator's actual content.",images:[]},

  {id:59,reqNum:"R059",page:"Dashboard",feature:"Trend charts: comparative time context",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Charts should show delta — 'up 12 from last month'.",images:[]},

  {id:60,reqNum:"R060",page:"Dashboard",feature:"Improve mobile dashboard layout",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Dashboard not optimized for mobile — cards overlap, charts unreadable.",images:[]},

  {id:61,reqNum:"R061",page:"Dashboard",feature:"Pagination for appointments and posts",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Appointments and recent posts widgets show fixed short list. Add pagination/load more.",images:[]},

  {id:62,reqNum:"R062",page:"Dashboard",feature:"Application velocity signal",priority:"Phase 2",status:"Backlog",notes:"From recording #2. Surface 'applications per day' velocity metric per campaign.",images:[]},

  // CAMPAIGN MANAGER

  {id:29,reqNum:"R029",page:"Campaign Manager",feature:"Default sort by created date",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Campaign list should default to newest first.",images:[]},

  {id:30,reqNum:"R030",page:"Campaign Manager",feature:"Show cumulative scheduled creators",priority:"Phase 2",status:"Backlog",notes:"From recording #3. 'Scheduled' metric should show all-time cumulative total, not just currently in queue.",images:[]},

  {id:31,reqNum:"R031",page:"Campaign Manager",feature:"Average days-to-schedule metric",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Show avg days from application to scheduled visit, plus platform-wide benchmark comparison.",images:[]},

  {id:32,reqNum:"R032",page:"Campaign Manager",feature:"Stale creator flag (>14 days)",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Flag creator cards sitting in scheduling/completed >14 days with visual indicator.",images:[]},

  {id:33,reqNum:"R033",page:"Campaign Manager",feature:"Sales team notification on campaign submit",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Auto-notify sales team via Slack/email when a brand submits a new campaign.",images:[]},

  {id:34,reqNum:"R034",page:"Campaign Manager",feature:"Prominent 'Create Campaign' button",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Create Campaign CTA is easy to miss. Redesign to be bold, exciting.",images:[]},

  {id:35,reqNum:"R035",page:"Campaign Manager",feature:"Clarify pipeline stage definitions",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Stage labels like 'Assessing', 'Scheduling' are not self-explanatory.",images:[]},

  {id:36,reqNum:"R036",page:"Campaign Manager",feature:"Visualize stages as a flow/conveyor belt",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Pipeline should feel like a directional conveyor belt — Assessing → Scheduling → Completed → Posted.",images:[]},

  {id:37,reqNum:"R037",page:"Campaign Manager",feature:"AI one-sentence creator summary",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Each creator card needs AI-generated one-sentence summary.",images:[]},

  {id:38,reqNum:"R038",page:"Campaign Manager",feature:"Creator location & engagement metrics on card",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Creator cards missing: city/location, average engagement rate, follower count.",images:[]},

  {id:39,reqNum:"R039",page:"Campaign Manager",feature:"Campaign title AI suggestions",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Title field should suggest AI-generated options based on brand context + past high-performing titles.",images:[]},

  {id:40,reqNum:"R040",page:"Campaign Manager",feature:"Pre-fill campaign brief from past campaigns",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Brief and perks fields should auto-populate from past high-reach campaigns for that brand.",images:[]},

  {id:63,reqNum:"R063",page:"Campaign Manager",feature:"Break create campaign into stepped sections",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Creation form is one long page. Break into steps — Brief / Times / Locations.",images:[]},

  {id:64,reqNum:"R064",page:"Campaign Manager",feature:"Add location/city to campaign card title",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Campaign list cards don't show targeted location.",images:[]},

  {id:65,reqNum:"R065",page:"Campaign Manager",feature:"Bring campaign banner image into card",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Show campaign banner image on list cards. Makes list much more visually scannable.",images:[]},

  {id:66,reqNum:"R066",page:"Campaign Manager",feature:"Allow editing of auto-generated campaign URL",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Campaign URLs are auto-generated and not editable. Brands want custom URLs.",images:[]},

  {id:67,reqNum:"R067",page:"Campaign Manager",feature:"Remove redundant 'Scheduled' pill in scheduling view",priority:"Phase 2",status:"Backlog",notes:"From recording #3. 'Scheduled' pill in scheduling view is redundant.",images:[]},

  {id:68,reqNum:"R068",page:"Campaign Manager",feature:"Table view option for creator cards",priority:"Phase 2",status:"Backlog",notes:"From recording #3. AMs managing large creator lists need compact table view.",images:[]},

  {id:74,reqNum:"R074",page:"Campaign Manager",feature:"Campaign notes: less aggressive styling",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Campaign note component is too visually dominant. Restyle to subdued/muted appearance.",images:[]},

  {id:75,reqNum:"R075",page:"Campaign Manager",feature:"Improve 'no content yet' posted state",priority:"Phase 2",status:"Backlog",notes:"From recording #3. Empty state for the 'Posted' tab is bare and unhelpful.",images:[]},

  // DISCOVER

  {id:41,reqNum:"R041",page:"Discover",feature:"Rename 'Find Campaigns' to 'Discover'",priority:"Phase 2",status:"Backlog",notes:"From transcript. 'Find Campaigns' is too narrow — page is evolving into broader local discovery experience.",images:[]},

  {id:42,reqNum:"R042",page:"Discover",feature:"Partner business listings on Discover map",priority:"Phase 2",status:"Backlog",notes:"From transcript. Show InKind restaurants, Shopline retail, and other partners on the Discover map.",images:[]},

  {id:43,reqNum:"R043",page:"Discover",feature:"Relish campaigns prominently differentiated from partners",priority:"Phase 2",status:"Backlog",notes:"From transcript. Relish paid campaigns must dominate visual hierarchy. Partner pins secondary.",images:[]},

  {id:44,reqNum:"R044",page:"Discover",feature:"City-level discovery context and 'What's happening'",priority:"Phase 2",status:"Backlog",notes:"From transcript. Each city view should have editorial blurb: what's trending, which brands are active.",images:[]},

  {id:69,reqNum:"R069",page:"Discover",feature:"Local community events and volunteering",priority:"Phase 2",status:"Backlog",notes:"From transcript. Discover should include local community events and volunteering.",images:[]},

  {id:70,reqNum:"R070",page:"Discover",feature:"Discount/perk mechanics clearly explained",priority:"Phase 2",status:"Backlog",notes:"From transcript. Partner perks need clearer explanation.",images:[]},

  // PARTNER PORTAL

  {id:103,reqNum:"R-PP03",page:"Partner Portal",feature:"Nav — Links (Story, Investors)",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Navigation",notes:"Two nav links right of divider. 14px, 500 weight, #444, 6px/12px padding, 20px border radius. Hover: #f4f4f4. Active: #f0f0f0, color #1a1a2e. 'Story' → Story section. 'Investors 🔒' → gated section.",images:[]},

  {id:104,reqNum:"R-PP04",page:"Partner Portal",feature:"Hero — Welcome pill badge",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Hero Section",notes:"Rounded pill above headline. Border: 1.5px solid #1a7c5a. Text: #1a7c5a, 13px 500 weight. Padding: 5px 14px. Border-radius: 20px. Text: 'Welcome to the Relish Partner Portal'",images:[]},

  {id:105,reqNum:"R-PP05",page:"Partner Portal",feature:"Hero — Headline",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Hero Section",notes:"clamp(32px, 5vw, 52px), 800 weight, line-height 1.1, color #0f1f1a, letter-spacing -0.5px. Text: 'We're Building the Infrastructure for Local Commerce.'",images:[]},

  {id:106,reqNum:"R-PP06",page:"Partner Portal",feature:"Hero — Body copy (3 paragraphs)",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Hero Section",notes:"17px, #3d3d3d, line-height 1.75, max-width 720px, 18px gap between paragraphs.",images:[]},

  {id:107,reqNum:"R-PP07",page:"Partner Portal",feature:"'What Relish Is' — Section label + headline",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — What Relish Is",notes:"All-caps label: 11px 700 weight letter-spacing 0.12em #888. Heading: 'Powering Creator-Led Commerce for Retail, CPG, and Restaurants.'",images:[]},

  {id:108,reqNum:"R-PP08",page:"Partner Portal",feature:"'What Relish Is' — Three bullet points",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — What Relish Is",notes:"8×8px solid green (#1a7c5a) circle bullets. 16px 600 weight #1a1a2e. Items: Human Powered / Tech Enablement / Built for Scale.",images:[]},

  {id:109,reqNum:"R-PP09",page:"Partner Portal",feature:"FAQ block — 'What do we do?'",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — What Relish Is",notes:"Bold 17px heading #0f1f1a. Body: 15px #444 line-height 1.7.",images:[]},

  {id:110,reqNum:"R-PP10",page:"Partner Portal",feature:"FAQ block — 'Why the name Relish?'",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — What Relish Is",notes:"Two body paragraphs. P1: most powerful recommendations happen in moments captured by people who relish life.",images:[]},

  {id:111,reqNum:"R-PP11",page:"Partner Portal",feature:"FAQ block — 'What is a Relisher?' + stat chips",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — What Relish Is",notes:"Stat chips: bg #f3faf6, border #c5e8d6, 13px #1a7c5a 600 weight. Chips: 60,000+ Vetted Creators / 60+ Active Markets / US · Canada · DACH.",images:[]},

  {id:112,reqNum:"R-PP12",page:"Partner Portal",feature:"Our Values — Section heading + 3-column cards",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Our Values",notes:"3-column grid (auto-fit minmax 240px), 28px gap. Cards: 🤝 KEEP IT HUMAN / 💪 BUILD THINGS THAT LAST / 👍 RELISH EVERY MOMENT.",images:[]},

  {id:113,reqNum:"R-PP13",page:"Partner Portal",feature:"Useful Links — 7-card grid",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Useful Links",notes:"3-column grid, collapses to 2-col under 640px. Cards: Sales Deck / One Pager / Product Demo / Case Studies / Pitch Deck / Brand Guidelines / Positioning Strategy.",images:[]},

  {id:114,reqNum:"R-PP14",page:"Partner Portal",feature:"Strategic Programs — 4-card grid",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Strategic Programs",notes:"2-col grid, collapses to 1-col under 600px. Cards: Platform Enhancements / Attribution Technology / Ecosystem Infrastructure / Creator Financial Layer.",images:[]},

  {id:115,reqNum:"R-PP15",page:"Partner Portal",feature:"The Long Game — Dark card",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — The Long Game",notes:"Background #0f1f1a, border-radius 16px, padding 40px. Tag pills: Global Community Infrastructure / Proprietary Data Platform / Creator Financial Layer / Discovery Super App.",images:[]},

  {id:116,reqNum:"R-PP16",page:"Partner Portal",feature:"Section dividers, responsive layout & nav wiring",priority:"Phase 3",status:"Backlog",epic:"Partner Portal — Layout & Polish",notes:"1px #ebebeb dividers between sections. Links grid 3→2col at 640px. Programs grid 2→1col at 600px.",images:[]},

];


const RECORDINGS=[

  {id:1,num:1,date:"Mar 28, 2026",title:"Home Page Review",tags:["Homepage","Case Studies","Creator Page"],color:"#6366f1",icon:"🏠",url:"https://app.fyxer.com/share/call-recordings/a8987ee8-be11-4191-a3d1-e9eff94db950:huWuFc993CUgD6gJau3bJMNpMD93:1774716771903?ctx=eyJ0IjoiSG9tZSBQYWdlIFJldmlldyIsImgiOiJNaWNoYWVsIERpZWdvIiwiciI6IiIsImQiOiIyMDI2LTAzLTI4VDE2OjM2OjI1LjE4MVoiLCJwIjoxLCJpIjoiTWljaGFlbCBEaWVnbyJ9&utm_source=call-recording-share"},

  {id:2,num:2,date:"Mar 28, 2026",title:"Dashboard Review",tags:["Dashboard","Metrics"],color:"#10b981",icon:"📊",url:"https://app.fyxer.com/share/call-recordings/a8987ee8-be11-4191-a3d1-e9eff94db950:huWuFc993CUgD6gJau3bJMNpMD93:1774716931375?ctx=eyJ0IjoiRGFzaGJvYXJkIFJldmlldyIsImgiOiJNaWNoYWVsIERpZWdvIiwiciI6IiIsImQiOiIyMDI2LTAzLTI4VDE2OjM5OjI2LjYyMFoiLCJwIjoxLCJpIjoiTWljaGFlbCBEaWVnbyJ9&utm_source=call-recording-share"},

  {id:3,num:3,date:"Mar 28, 2026",title:"Campaign & App Manager Review",tags:["Campaigns","Creators","App Manager"],color:"#f97316",icon:"🗂️",url:"https://app.fyxer.com/share/call-recordings/a8987ee8-be11-4191-a3d1-e9eff94db950:huWuFc993CUgD6gJau3bJMNpMD93:1774717041976?ctx=eyJ0IjoiQ2FtcGFpZ24gJiBBcHAgTWFuYWdlciBSZXZpZXciLCJoIjoiTWljaGFlbCBEaWVnbyIsInIiOiIiLCJkIjoiMjAyNi0wMy0yOFQxNjozODowMC43NDlaIiwicCI6MSwiaSI6Ik1pY2hhZWwgRGllZ28ifQ%3D%3D&utm_source=call-recording-share"},

];


const INIT_POSTS=[{id:1,author:"Michael",date:"Mar 28, 2026",text:"Set up this log so we can stay aligned across our workflow while you're away — recordings, requests, and updates all in one place. Ping me here as you make progress or have questions. Really excited about v3!"}];

const INIT_DREAMS=[

  {id:1,author:"Michael",title:"Agentic Campaign Management",text:"Campaigns that run themselves — AI sets goals, schedules creators, monitors performance, and adjusts in real time. The brand just approves and watches results come in."},

  {id:2,author:"Michael",title:"CPG Command Center",text:"A purpose-built dashboard for CPG brands expanding into retail. Track creator performance by market, monitor in-store velocity, and coordinate campaign timing with retail launch windows — all in one view."},

];


const selStyle={border:"1px solid #e5e7eb",borderRadius:7,padding:"7px 10px",fontSize:13,color:"#374151",background:"#fff",outline:"none"};

const greenBtn={background:G,color:"#fff",border:"none",borderRadius:6,padding:"7px 14px",fontWeight:700,fontSize:13,cursor:"pointer"};

const linkBtn={background:"none",border:"none",padding:0,fontSize:13,color:"#6b7280",cursor:"pointer"};


function PhaseBadge({label}){

  const c=PHASE_COLORS[label]||PHASE_COLORS["Phase 3"];

  return <span style={{fontSize:11,fontWeight:600,padding:"2px 8px",borderRadius:99,background:c.bg,color:c.text,border:`1px solid ${c.border}`,whiteSpace:"nowrap"}}>{label}</span>;

}

function PageTag({label}){

  return <span style={{fontSize:11,fontWeight:600,padding:"2px 8px",borderRadius:99,background:"#f3e8ff",color:"#7c3aed",border:"1px solid #ddd6fe",whiteSpace:"nowrap"}}>{label}</span>;

}


function RequestCard({req,onChange,onStatus}){

  const [open,setOpen]=useState(false);

  const [noteDraft,setNoteDraft]=useState(req.notes||"");

  const [lightbox,setLightbox]=useState(null);

  const fileRef=useRef();

  const saveNote=()=>onChange(req.id,"notes",noteDraft);

  const handleFiles=(e)=>{

    Array.from(e.target.files).forEach(f=>{

      const r=new FileReader();

      r.onload=ev=>onChange(req.id,"addImage",{src:ev.target.result,name:f.name});

      r.readAsDataURL(f);

    });

    e.target.value="";

  };

  return (

    <>

      <div style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,overflow:"hidden",marginBottom:8}}>

        <div style={{padding:"12px 16px",cursor:"pointer",display:"flex",alignItems:"flex-start",gap:12}} onClick={()=>setOpen(o=>!o)}>

          <div style={{flex:1,minWidth:0}}>

            <div style={{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap",marginBottom:4}}>

              <span style={{fontSize:11,fontWeight:700,color:"#9ca3af",fontFamily:"monospace"}}>{req.reqNum}</span>

              {req.epic&&<span style={{fontSize:10,fontWeight:600,color:G,background:GL,border:"1px solid #a7f3d0",padding:"1px 7px",borderRadius:99}}>{req.epic}</span>}

              <PhaseBadge label={req.priority}/>

              <PageTag label={req.page}/>

              {req.notes&&req.notes.trim()&&<span style={{fontSize:11,color:"#9ca3af"}}>📝</span>}

              {req.images.length>0&&<span style={{fontSize:11,color:"#9ca3af"}}>🖼 {req.images.length}</span>}

            </div>

            <div style={{fontWeight:600,fontSize:14,color:"#111827"}}>{req.feature}</div>

          </div>

          <div style={{display:"flex",alignItems:"center",gap:8,flexShrink:0}}>

            <select value={req.status} onChange={e=>{e.stopPropagation();onStatus(req.id,e.target.value);}} onClick={e=>e.stopPropagation()}

              style={{fontSize:11,fontWeight:600,padding:"3px 6px",borderRadius:6,border:`1px solid ${STATUS_COLORS[req.status].border}`,background:STATUS_COLORS[req.status].bg,color:STATUS_COLORS[req.status].text,cursor:"pointer"}}>

              {STATUSES.map(s=><option key={s}>{s}</option>)}

            </select>

            <span style={{fontSize:12,color:"#9ca3af",display:"inline-block",transform:open?"rotate(180deg)":"rotate(0deg)",transition:"transform 0.2s"}}>▾</span>

          </div>

        </div>

        {open&&(

          <div style={{borderTop:"1px solid #f3f4f6",padding:"14px 16px"}}>

            <div style={{marginBottom:16}}>

              <div style={{fontSize:11,fontWeight:700,color:"#9ca3af",textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>Notes</div>

              <textarea value={noteDraft} onChange={e=>setNoteDraft(e.target.value)} onBlur={saveNote}

                placeholder="Add notes, context, links, or implementation details…" rows={5}

                style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:8,padding:"10px 12px",fontSize:13,color:"#374151",resize:"vertical",outline:"none",boxSizing:"border-box",lineHeight:1.6,fontFamily:"inherit"}}/>

              {noteDraft!==req.notes&&<button onClick={saveNote} style={{...greenBtn,marginTop:6,fontSize:12,padding:"5px 12px"}}>Save</button>}

            </div>

            <div>

              <div style={{fontSize:11,fontWeight:700,color:"#9ca3af",textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>Attachments</div>

              {req.images.length>0&&(

                <div style={{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(100px, 1fr))",gap:8,marginBottom:12}}>

                  {req.images.map((img,idx)=>(

                    <div key={idx} style={{position:"relative",borderRadius:8,overflow:"hidden",border:"1px solid #e5e7eb",aspectRatio:"1",cursor:"zoom-in"}} onClick={()=>setLightbox(img.src)}>

                      <img src={img.src} alt={img.name} style={{width:"100%",height:"100%",objectFit:"cover"}}/>

                      <button onClick={e=>{e.stopPropagation();onChange(req.id,"removeImage",idx);}} style={{position:"absolute",top:4,right:4,width:20,height:20,borderRadius:"50%",background:"rgba(0,0,0,0.6)",color:"#fff",border:"none",cursor:"pointer",fontSize:11,display:"flex",alignItems:"center",justifyContent:"center"}}>×</button>

                    </div>

                  ))}

                </div>

              )}

              <input ref={fileRef} type="file" accept="image/*" multiple style={{display:"none"}} onChange={handleFiles}/>

              <button onClick={()=>fileRef.current.click()} style={{display:"flex",alignItems:"center",gap:6,padding:"8px 14px",border:"1px dashed #d1d5db",borderRadius:8,background:"#f9fafb",color:"#6b7280",fontSize:13,fontWeight:500,cursor:"pointer"}}>

                <span style={{fontSize:16}}>+</span> Upload images

              </button>

            </div>

          </div>

        )}

      </div>

      {lightbox&&(

        <div onClick={()=>setLightbox(null)} style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.85)",zIndex:1000,display:"flex",alignItems:"center",justifyContent:"center",padding:24,cursor:"zoom-out"}}>

          <img src={lightbox} alt="" style={{maxWidth:"90vw",maxHeight:"90vh",borderRadius:10}}/>

          <button onClick={()=>setLightbox(null)} style={{position:"absolute",top:20,right:24,background:"rgba(255,255,255,0.15)",border:"none",color:"#fff",borderRadius:"50%",width:36,height:36,fontSize:18,cursor:"pointer"}}>×</button>

        </div>

      )}

    </>

  );

}


function RequestsPage({requests,setRequests}){

  const [filterPage,setFilterPage]=useState("All");

  const [filterPhase,setFilterPhase]=useState("All");

  const [filterStatus,setFilterStatus]=useState("All");

  const [search,setSearch]=useState("");

  const [groupBy,setGroupBy]=useState("phase");


  const filtered=requests.filter(r=>{

    if(filterPage!=="All"&&r.page!==filterPage)return false;

    if(filterPhase!=="All"&&r.priority!==filterPhase)return false;

    if(filterStatus!=="All"&&r.status!==filterStatus)return false;

    if(search&&!r.feature.toLowerCase().includes(search.toLowerCase())&&!r.reqNum.toLowerCase().includes(search.toLowerCase()))return false;

    return true;

  });


  const handleChange=(id,field,val)=>{

    setRequests(rs=>rs.map(r=>{

      if(r.id!==id)return r;

      if(field==="notes")return{...r,notes:val};

      if(field==="addImage")return{...r,images:[...r.images,val]};

      if(field==="removeImage")return{...r,images:r.images.filter((_,i)=>i!==val)};

      return r;

    }));

  };

  const changeStatus=(id,s)=>setRequests(rs=>rs.map(r=>r.id!==id?r:{...r,status:s}));


  let groups={};

  if(groupBy==="phase"){

    ["Phase 1","Phase 2","Phase 3"].forEach(p=>{const items=filtered.filter(r=>r.priority===p);if(items.length)groups[p]=items;});

  } else {

    filtered.forEach(r=>{const k=r.epic||r.page;if(!groups[k])groups[k]=[];groups[k].push(r);});

  }


  const p1=requests.filter(r=>r.priority==="Phase 1").length;

  const p2=requests.filter(r=>r.priority==="Phase 2").length;

  const inProg=requests.filter(r=>r.status==="In Progress").length;

  const done=requests.filter(r=>r.status==="Done").length;


  return (

    <div>

      <div style={{display:"flex",gap:12,marginBottom:20,flexWrap:"wrap"}}>

        {[{label:"Phase 1",val:p1,c:"#1d4ed8"},{label:"Phase 2",val:p2,c:"#7c3aed"},{label:"In Progress",val:inProg,c:"#2563eb"},{label:"Done ✅",val:done,c:G}].map(s=>(

          <div key={s.label} style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:"12px 20px",minWidth:90}}>

            <div style={{fontSize:26,fontWeight:800,color:s.c}}>{s.val}</div>

            <div style={{fontSize:11,color:"#9ca3af",marginTop:2}}>{s.label}</div>

          </div>

        ))}

      </div>

      <div style={{display:"flex",gap:8,marginBottom:12,flexWrap:"wrap"}}>

        <input value={search} onChange={e=>setSearch(e.target.value)} placeholder="Search requests…"

          style={{flex:1,minWidth:160,border:"1px solid #e5e7eb",borderRadius:7,padding:"7px 12px",fontSize:13,outline:"none"}}/>

        <select value={filterPage} onChange={e=>setFilterPage(e.target.value)} style={selStyle}>{PAGES.map(p=><option key={p}>{p}</option>)}</select>

        <select value={filterPhase} onChange={e=>setFilterPhase(e.target.value)} style={selStyle}>{PHASES.map(p=><option key={p}>{p}</option>)}</select>

        <select value={filterStatus} onChange={e=>setFilterStatus(e.target.value)} style={selStyle}><option>All</option>{STATUSES.map(s=><option key={s}>{s}</option>)}</select>

      </div>

      <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:14}}>

        <span style={{fontSize:12,color:"#9ca3af"}}>{filtered.length} request{filtered.length!==1?"s":""}</span>

        <div style={{display:"flex",background:"#f3f4f6",borderRadius:8,padding:2,gap:2}}>

          {[["phase","By Phase"],["epic","By Epic"]].map(([v,l])=>(

            <button key={v} onClick={()=>setGroupBy(v)}

              style={{padding:"5px 12px",borderRadius:6,border:"none",fontWeight:600,fontSize:12,cursor:"pointer",background:groupBy===v?"#111827":"transparent",color:groupBy===v?"#fff":"#6b7280"}}>{l}</button>

          ))}

        </div>

      </div>

      {Object.entries(groups).map(([key,reqs])=>(

        <div key={key} style={{marginBottom:28}}>

          <div style={{display:"flex",alignItems:"center",gap:10,marginBottom:10}}>

            {groupBy==="phase"&&<span style={{width:8,height:8,borderRadius:"50%",flexShrink:0,display:"inline-block",background:key==="Phase 1"?"#1d4ed8":key==="Phase 2"?"#7c3aed":"#16a34a"}}/>}

            <span style={{fontWeight:800,fontSize:13,color:"#111827"}}>{key}</span>

            <span style={{fontSize:11,color:"#9ca3af",background:"#f3f4f6",borderRadius:99,padding:"2px 8px"}}>{reqs.length}</span>

            <div style={{flex:1,height:1,background:"#e5e7eb"}}/>

          </div>

          {reqs.map(r=><RequestCard key={r.id} req={r} onChange={handleChange} onStatus={changeStatus}/>)}

        </div>

      ))}

    </div>

  );

}


function Section({title,action,children}){

  return (

    <div style={{marginBottom:32}}>

      <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:14}}>

        <div style={{fontWeight:800,fontSize:16,color:"#111827"}}>{title}</div>

        <div style={{display:"flex",gap:8,alignItems:"center"}}>{action}</div>

      </div>

      {children}

    </div>

  );

}


function HomePage({requests,posts,setPosts,dreams,setDreams,onNav}){

  const [postOpen,setPostOpen]=useState(false);

  const [postDraft,setPostDraft]=useState("");

  const [postName,setPostName]=useState("Michael");

  const [dreamForm,setDreamForm]=useState(false);

  const [dTitle,setDTitle]=useState("");

  const [dText,setDText]=useState("");

  const [dAuthor,setDAuthor]=useState("Michael");


  const p1=requests.filter(r=>r.priority==="Phase 1").length;

  const p2=requests.filter(r=>r.priority==="Phase 2").length;

  const p3=requests.filter(r=>r.priority==="Phase 3").length;

  const inProg=requests.filter(r=>r.status==="In Progress").length;

  const done=requests.filter(r=>r.status==="Done").length;

  const total=requests.length;


  const addPost=()=>{

    if(!postDraft.trim())return;

    setPosts(ps=>[...ps,{id:Date.now(),author:postName||"Anonymous",date:new Date().toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}),text:postDraft}]);

    setPostDraft("");setPostOpen(false);

  };

  const addDream=()=>{if(!dTitle||!dText)return;setDreams(ds=>[...ds,{id:Date.now(),author:dAuthor||"Anonymous",title:dTitle,text:dText}]);setDTitle("");setDText("");setDreamForm(false);};


  return (

    <div>

      <div style={{marginBottom:24}}>

        <h1 style={{fontSize:28,fontWeight:900,color:"#111827",margin:"0 0 8px",letterSpacing:-0.5}}>Engineering Command Center</h1>

        <p style={{fontSize:14,color:"#6b7280",margin:"0 0 4px",lineHeight:1.6}}>A shared workspace to continue building the core Relish app while remote — recordings, requests, and updates in one place.</p>

        <p style={{fontSize:14,color:"#6b7280",margin:0}}>Leave posts, view a full list of requested updates, and see dream functionality.</p>

      </div>

      <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:10,marginBottom:10}}>

        {[{label:"In Progress",val:inProg,c:"#2563eb"},{label:"Done ✅",val:done,c:G}].map(s=>(

          <div key={s.label} style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:"14px 16px",display:"flex",alignItems:"center",justifyContent:"space-between"}}>

            <div>

              <div style={{fontSize:28,fontWeight:800,color:s.c}}>{s.val}</div>

              <div style={{fontSize:11,color:"#9ca3af",marginTop:2}}>{s.label}</div>

            </div>

            <button onClick={()=>onNav("requests")} style={{fontSize:12,color:"#6366f1",background:"none",border:"none",padding:0,cursor:"pointer"}}>View →</button>

          </div>

        ))}

      </div>

      <div style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:"16px 18px",marginBottom:24}}>

        <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:14}}>

          <div style={{fontWeight:800,fontSize:15,color:"#111827"}}>All Requests <span style={{fontSize:13,fontWeight:500,color:"#9ca3af"}}>({total})</span></div>

          <button onClick={()=>onNav("requests")} style={{fontSize:12,color:"#6366f1",background:"none",border:"none",padding:0,cursor:"pointer"}}>View all →</button>

        </div>

        <div style={{display:"flex",flexDirection:"column",gap:10}}>

          {[

            {label:"Phase 1",val:p1,c:PHASE_COLORS["Phase 1"],desc:"Homepage, Case Studies, Creator Landing, Find Campaigns, Contact / Signup, Local Campaigns, Map"},

            {label:"Phase 2",val:p2,c:PHASE_COLORS["Phase 2"],desc:"Dashboard, Campaign Manager, Discover"},

            {label:"Phase 3",val:p3,c:PHASE_COLORS["Phase 3"],desc:"Partner Portal — all portal requests"},

          ].map(ph=>(

            <div key={ph.label} style={{display:"flex",alignItems:"center",gap:12}}>

              <div style={{width:10,height:10,borderRadius:"50%",background:ph.c.text,flexShrink:0}}/>

              <div style={{flex:1}}>

                <div style={{display:"flex",alignItems:"center",gap:8,marginBottom:2}}>

                  <span style={{fontWeight:700,fontSize:13,color:"#111827"}}>{ph.label}</span>

                  <span style={{fontSize:11,fontWeight:600,padding:"1px 7px",borderRadius:99,background:ph.c.bg,color:ph.c.text,border:`1px solid ${ph.c.border}`}}>{ph.val} requests</span>

                </div>

                <div style={{fontSize:11,color:"#9ca3af"}}>{ph.desc}</div>

              </div>

              <div style={{width:80,height:5,background:"#f3f4f6",borderRadius:99,overflow:"hidden",flexShrink:0}}>

                <div style={{width:`${Math.round((ph.val/total)*100)}%`,height:"100%",background:ph.c.text,borderRadius:99}}/>

              </div>

              <span style={{fontSize:11,color:"#9ca3af",width:32,textAlign:"right",flexShrink:0}}>{Math.round((ph.val/total)*100)}%</span>

            </div>

          ))}

        </div>

      </div>


      <Section title="📌 Posts" action={

        <><button onClick={()=>onNav("updates")} style={linkBtn}>View all →</button>&nbsp;&nbsp;

        <button style={greenBtn} onClick={()=>setPostOpen(o=>!o)}>{postOpen?"Cancel":"+ Post"}</button></>

      }>

        <div style={{fontSize:13,color:"#6b7280",marginBottom:12}}>Updates, questions, and progress notes.</div>

        {posts.slice(0,2).map(p=>(

          <div key={p.id} style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:"14px 16px",marginBottom:10}}>

            <div style={{display:"flex",gap:10,alignItems:"center",marginBottom:8}}>

              <div style={{width:32,height:32,borderRadius:"50%",background:G,color:"#fff",display:"flex",alignItems:"center",justifyContent:"center",fontWeight:700,fontSize:13,flexShrink:0}}>{p.author[0]}</div>

              <div><div style={{fontWeight:700,fontSize:13,color:G}}>{p.author}</div><div style={{fontSize:11,color:"#9ca3af"}}>{p.date}</div></div>

            </div>

            <p style={{fontSize:13,color:"#374151",margin:0,lineHeight:1.6}}>{p.text.length>160?p.text.slice(0,160)+"…":p.text}</p>

          </div>

        ))}

        {postOpen&&(

          <div style={{background:"#f9fafb",border:"1px solid #e5e7eb",borderRadius:10,padding:14,marginTop:4}}>

            <input value={postName} onChange={e=>setPostName(e.target.value)} placeholder="Your name"

              style={{border:"1px solid #e5e7eb",borderRadius:6,padding:"5px 10px",fontSize:12,width:120,outline:"none",marginBottom:8}}/>

            <textarea value={postDraft} onChange={e=>setPostDraft(e.target.value)} placeholder="Leave an update…" rows={3} autoFocus

              style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"7px 10px",fontSize:13,resize:"vertical",outline:"none",boxSizing:"border-box"}}/>

            <button onClick={addPost} style={{...greenBtn,marginTop:8}}>Post</button>

          </div>

        )}

      </Section>


      <Section title="🎬 Recordings" action={null}>

        <div style={{fontSize:13,color:"#6b7280",marginBottom:12}}>Watch in order for full context. Each maps to requests in the tracker.</div>

        {RECORDINGS.map(r=>(

          <div key={r.id} style={{display:"flex",alignItems:"center",gap:14,padding:"14px 0",borderTop:`3px solid ${r.color}`}}>

            <div style={{width:44,height:44,borderRadius:10,background:r.color,display:"flex",alignItems:"center",justifyContent:"center",fontSize:18,flexShrink:0}}>{r.icon}</div>

            <div style={{flex:1}}>

              <div style={{fontSize:11,color:"#9ca3af",marginBottom:2}}>#{r.num} · {r.date}</div>

              <div style={{fontWeight:700,fontSize:14,color:"#111827",marginBottom:4}}>{r.title}</div>

              <div style={{display:"flex",gap:6,flexWrap:"wrap"}}>{r.tags.map(t=><span key={t} style={{fontSize:11,background:"#f3f4f6",color:"#6b7280",borderRadius:99,padding:"2px 8px"}}>{t}</span>)}</div>

            </div>

            <a href={r.url} target="_blank" rel="noopener noreferrer"

              style={{background:"#6366f1",color:"#fff",border:"none",borderRadius:8,padding:"8px 16px",fontWeight:700,fontSize:13,cursor:"pointer",textDecoration:"none",whiteSpace:"nowrap"}}>

              Watch

            </a>

          </div>

        ))}

      </Section>


      <div style={{display:"flex",alignItems:"center",justifyContent:"center",gap:8,margin:"24px 0",color:"#d1d5db",fontSize:18}}>○ ○ ✦ ○ ○</div>


      <Section title="💭 Dreams" action={<button style={greenBtn} onClick={()=>setDreamForm(f=>!f)}>+ Add</button>}>

        <div style={{fontSize:13,color:"#6b7280",marginBottom:14}}>Big ideas and inspiration — pinned here so you know how I'm thinking!</div>

        {dreams.map(d=>(

          <div key={d.id} style={{display:"flex",alignItems:"flex-start",gap:12,marginBottom:14}}>

            <div style={{width:32,height:32,borderRadius:"50%",border:"2px solid #e5e7eb",flexShrink:0,marginTop:2}}/>

            <div style={{flex:1}}>

              <div style={{fontWeight:700,fontSize:14,color:"#111827",marginBottom:4}}>{d.title}</div>

              <div style={{fontSize:13,color:"#6b7280",lineHeight:1.5}}>{d.text.length>120?d.text.slice(0,120)+"…":d.text}</div>

            </div>

            <span style={{fontSize:16,color:"#9ca3af"}}>→</span>

          </div>

        ))}

        {dreamForm&&(

          <div style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:14,marginTop:8}}>

            <input value={dAuthor} onChange={e=>setDAuthor(e.target.value)} placeholder="Your name" style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"7px 10px",fontSize:13,marginBottom:8,boxSizing:"border-box",outline:"none"}}/>

            <input value={dTitle} onChange={e=>setDTitle(e.target.value)} placeholder="Dream title" style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"7px 10px",fontSize:13,marginBottom:8,boxSizing:"border-box",outline:"none"}}/>

            <textarea value={dText} onChange={e=>setDText(e.target.value)} placeholder="Describe the dream…" rows={3} style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",fontSize:13,resize:"vertical",outline:"none",boxSizing:"border-box"}}/>

            <div style={{display:"flex",gap:8,marginTop:8}}>

              <button onClick={addDream} style={greenBtn}>Add</button>

              <button onClick={()=>setDreamForm(false)} style={{background:"#f3f4f6",color:"#374151",border:"none",borderRadius:6,padding:"8px 16px",fontWeight:600,fontSize:13,cursor:"pointer"}}>Cancel</button>

            </div>

          </div>

        )}

      </Section>

    </div>

  );

}


function UpdatesPage({posts,setPosts}){

  const [draft,setDraft]=useState("");

  const [name,setName]=useState("Michael");

  const add=()=>{if(!draft.trim())return;setPosts(ps=>[...ps,{id:Date.now(),author:name||"Anonymous",date:new Date().toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}),text:draft}]);setDraft("");};

  return (

    <div>

      {posts.map(p=>(

        <div key={p.id} style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:"14px 16px",marginBottom:12}}>

          <div style={{display:"flex",gap:10,alignItems:"center",marginBottom:8}}>

            <div style={{width:34,height:34,borderRadius:"50%",background:G,color:"#fff",display:"flex",alignItems:"center",justifyContent:"center",fontWeight:700,fontSize:13,flexShrink:0}}>{p.author[0]}</div>

            <div><div style={{fontWeight:700,fontSize:13,color:G}}>{p.author}</div><div style={{fontSize:11,color:"#9ca3af"}}>{p.date}</div></div>

          </div>

          <p style={{fontSize:14,color:"#374151",margin:0,lineHeight:1.6}}>{p.text}</p>

        </div>

      ))}

      <div style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:16}}>

        <input value={name} onChange={e=>setName(e.target.value)} placeholder="Your name" style={{border:"1px solid #e5e7eb",borderRadius:6,padding:"6px 10px",fontSize:13,width:140,outline:"none",marginBottom:8}}/>

        <textarea value={draft} onChange={e=>setDraft(e.target.value)} placeholder="Write an update…" rows={3}

          style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",fontSize:13,resize:"vertical",outline:"none",boxSizing:"border-box"}}/>

        <button onClick={add} style={{...greenBtn,marginTop:8}}>Post Update</button>

      </div>

    </div>

  );

}


function DreamBoardPage({dreams,setDreams}){

  const [form,setForm]=useState(false);

  const [t,setT]=useState("");const [x,setX]=useState("");const [a,setA]=useState("Michael");

  const add=()=>{if(!t||!x)return;setDreams(ds=>[...ds,{id:Date.now(),author:a||"Anonymous",title:t,text:x}]);setT("");setX("");setForm(false);};

  return (

    <div>

      <div style={{display:"grid",gridTemplateColumns:"repeat(auto-fill,minmax(260px,1fr))",gap:14,marginBottom:16}}>

        {dreams.map(d=>(

          <div key={d.id} style={{background:"#0f1f1a",border:"1px solid #1e3a2a",borderRadius:12,padding:"18px 20px"}}>

            <div style={{fontSize:11,fontWeight:700,color:"#4ade80",textTransform:"uppercase",letterSpacing:1,marginBottom:8}}>Dream</div>

            <div style={{fontWeight:700,fontSize:16,color:"#fff",marginBottom:8}}>{d.title}</div>

            <p style={{fontSize:13,color:"#9ca3af",margin:0,lineHeight:1.6}}>{d.text}</p>

            <div style={{marginTop:12,fontSize:11,color:"#4b5563"}}>— {d.author}</div>

          </div>

        ))}

      </div>

      {!form

        ?<button onClick={()=>setForm(true)} style={{background:"transparent",border:`1px dashed ${G}`,color:G,borderRadius:8,padding:"8px 16px",fontWeight:600,fontSize:13,cursor:"pointer"}}>+ Add a Dream</button>

        :(

          <div style={{background:"#fff",border:"1px solid #e5e7eb",borderRadius:10,padding:16}}>

            <input value={a} onChange={e=>setA(e.target.value)} placeholder="Your name" style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"7px 10px",fontSize:13,marginBottom:8,boxSizing:"border-box",outline:"none"}}/>

            <input value={t} onChange={e=>setT(e.target.value)} placeholder="Dream title" style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"7px 10px",fontSize:13,marginBottom:8,boxSizing:"border-box",outline:"none"}}/>

            <textarea value={x} onChange={e=>setX(e.target.value)} placeholder="Describe the dream…" rows={3} style={{width:"100%",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",fontSize:13,resize:"vertical",outline:"none",boxSizing:"border-box"}}/>

            <div style={{display:"flex",gap:8,marginTop:8}}>

              <button onClick={add} style={greenBtn}>Add</button>

              <button onClick={()=>setForm(false)} style={{background:"#f3f4f6",color:"#374151",border:"none",borderRadius:6,padding:"8px 16px",fontWeight:600,fontSize:13,cursor:"pointer"}}>Cancel</button>

            </div>

          </div>

        )}

    </div>

  );

}


const NAV_ITEMS=[{id:"home",label:"Home"},{id:"requests",label:"Requests"},{id:"updates",label:"Updates"},{id:"dreams",label:"Dream Board"}];

const PAGE_TITLES={requests:"All Requests",updates:"Updates",dreams:"Dream Board"};


function App(){

  const [tab,setTab]=useState("home");

  const [requests,setRequests]=useState(ALL_REQUESTS);

  const [posts,setPosts]=useState(INIT_POSTS);

  const [dreams,setDreams]=useState(INIT_DREAMS);


  return (

    <div style={{fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",background:"#f8fafc",minHeight:"100vh"}}>

      <div style={{background:"#fff",borderBottom:"1px solid #e5e7eb",padding:"0 24px",position:"sticky",top:0,zIndex:10}}>

        <div style={{maxWidth:900,margin:"0 auto",display:"flex",alignItems:"center",justifyContent:"space-between",height:52}}>

          <div style={{display:"flex",alignItems:"center",gap:10,cursor:"pointer"}} onClick={()=>setTab("home")}>

            <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:3,width:20,height:20}}>

              {[0,1,2,3].map(i=><div key={i} style={{background:G,borderRadius:2}}/>)}

            </div>

            <span style={{fontWeight:800,fontSize:15,color:"#111827"}}>Relish</span>

            <span style={{color:"#d1d5db"}}>/</span>

            <span style={{fontWeight:600,fontSize:13,color:"#6b7280"}}>Engineering Log</span>

          </div>

          <nav style={{display:"flex",gap:2}}>

            {NAV_ITEMS.map(n=>(

              <button key={n.id} onClick={()=>setTab(n.id)}

                style={{padding:"6px 12px",borderRadius:6,border:"none",fontWeight:600,fontSize:13,cursor:"pointer",background:tab===n.id?GL:"transparent",color:tab===n.id?G:"#6b7280"}}>

                {n.label}

              </button>

            ))}

          </nav>

        </div>

      </div>

      {tab!=="home"&&(

        <div style={{background:"#fff",borderBottom:"1px solid #f3f4f6",padding:"16px 24px"}}>

          <div style={{maxWidth:900,margin:"0 auto"}}>

            <h2 style={{margin:0,fontSize:20,fontWeight:800,color:"#111827"}}>

              {PAGE_TITLES[tab]}{tab==="requests"?` (${requests.length})`:""}</h2>

          </div>

        </div>

      )}

      <div style={{maxWidth:900,margin:"0 auto",padding:"24px 16px"}}>

        {tab==="home"    &&<HomePage requests={requests} posts={posts} setPosts={setPosts} dreams={dreams} setDreams={setDreams} onNav={setTab}/>}

        {tab==="requests"&&<RequestsPage requests={requests} setRequests={setRequests}/>}

        {tab==="updates" &&<UpdatesPage posts={posts} setPosts={setPosts}/>}

        {tab==="dreams"  &&<DreamBoardPage dreams={dreams} setDreams={setDreams}/>}

      </div>

    </div>

  );

}


ReactDOM.createRoot(document.getElementById("root")).render(<App/>);

</script>

</body>

</html>