// CPW Public Portal — discover + opportunity detail
// M5-FR-02 (responsive discovery), M5-FR-03 (combinable filters + list/calendar/map),
// M5-FR-04 (public detail, safe fields only)

const {
  PBtn, PCard, PField, PInput, PSelect, PCheckbox, PChip, PStatus, PProgress,
  PContainer, PortalOppCard,
  IPin, IClock, ICal, ISearch, IList, IMap, IFilter, IArrow, IArrowL, ICheck, IX,
  IUsers, IHelp, IMail, IPhone, IExt, IShield, IGlobe, IInfo, IUser, IChevD, IEye,
  oppDateChip,
} = window.Portal;

const { useState: useStateD, useMemo: useMemoD } = React;

// ====================== DISCOVER ======================
const PortalDiscover = ({ tenant, opps, initialView, initialFilters, onOpenOpp }) => {
  const [view, setView] = useStateD(initialView || "list");
  const [q, setQ] = useStateD("");
  const [zip, setZip] = useStateD("");
  const [dateRange, setDateRange] = useStateD("any");
  const [program, setProgram] = useStateD("all");
  const [interests, setInterests] = useStateD(new Set(initialFilters?.interests || []));
  const [age, setAge] = useStateD("any");
  const [family, setFamily] = useStateD(false);
  const [accessibility, setAccessibility] = useStateD(false);
  const [difficulty, setDifficulty] = useStateD("any");
  const [language, setLanguage] = useStateD("any");
  const [schedule, setSchedule] = useStateD("any");
  const [showFilters, setShowFilters] = useStateD(false);
  const [sort, setSort] = useStateD("date-asc");

  const toggleSet = (set, v) => {
    const next = new Set(set);
    next.has(v) ? next.delete(v) : next.add(v);
    return next;
  };

  // FILTER LOGIC — AND across categories, OR within multi-select chips (M5-FR-03)
  const filtered = useMemoD(() => {
    let list = opps.filter(o => o.visibility === "public");
    if (q) {
      const needle = q.toLowerCase();
      list = list.filter(o => (o.title + o.summary + o.site + o.location).toLowerCase().includes(needle));
    }
    if (zip && /^\d{3,5}$/.test(zip)) {
      list = list.filter(o => o.zips?.some(z => z.startsWith(zip.slice(0, 3))) || o.locationType === "virtual");
    }
    if (program !== "all") list = list.filter(o => o.program === program);
    if (interests.size) list = list.filter(o => o.interests.some(i => interests.has(i)));
    if (family) list = list.filter(o => o.familyFriendly);
    if (accessibility) list = list.filter(o => o.accessibility.length > 0);
    if (difficulty !== "any") list = list.filter(o => o.difficulty.toLowerCase() === difficulty);
    if (language !== "any") list = list.filter(o => o.language.includes(language));
    if (schedule !== "any") {
      const sm = { morning: "Morning", evening: "Evening", midday: "Mid-day", earlyMorning: "Early morning", allday: "All-day" };
      list = list.filter(o => o.time === sm[schedule] || (schedule === "weekends" && o.days.some(d => d === "Sat" || d === "Sun")));
    }
    if (dateRange !== "any") {
      const today = new Date("2026-05-23");
      const limits = { week: 7, month: 30, quarter: 90 };
      if (limits[dateRange]) {
        const cutoff = new Date(today.getTime() + limits[dateRange] * 86400000);
        list = list.filter(o => new Date(o.dateStart) <= cutoff && new Date(o.dateEnd) >= today);
      }
    }
    // sort
    if (sort === "date-asc") list = [...list].sort((a, b) => a.dateStart.localeCompare(b.dateStart));
    if (sort === "date-desc") list = [...list].sort((a, b) => b.dateStart.localeCompare(a.dateStart));
    if (sort === "title") list = [...list].sort((a, b) => a.title.localeCompare(b.title));
    return list;
  }, [opps, q, zip, dateRange, program, interests, age, family, accessibility, difficulty, language, schedule, sort]);

  const clearAll = () => {
    setQ(""); setZip(""); setDateRange("any"); setProgram("all"); setInterests(new Set());
    setAge("any"); setFamily(false); setAccessibility(false); setDifficulty("any"); setLanguage("any"); setSchedule("any");
  };

  const activeFilterCount = (q ? 1 : 0) + (zip ? 1 : 0) + (dateRange !== "any" ? 1 : 0) + (program !== "all" ? 1 : 0) + interests.size + (family ? 1 : 0) + (accessibility ? 1 : 0) + (difficulty !== "any" ? 1 : 0) + (language !== "any" ? 1 : 0) + (schedule !== "any" ? 1 : 0);

  const interestPool = ["outdoors","trail work","wildlife","birds","conservation","education","hospitality","camping","stewardship","families","fishing"];
  const programOpts = ["all", "State parks", "Wildlife", "Hunter education", "Education", "Stewardship"];

  return (
    <div style={{ background: "var(--paper)", paddingBottom: 80 }}>
      {/* Sub-hero search strip */}
      <div className="portal-discover-search">
        <PContainer>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 16, marginBottom: 14 }}>
            <div>
              <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 6 }}>
                {tenant.shortName} · Volunteer opportunities
              </div>
              <h1 style={{ fontFamily: "var(--font-display)", fontSize: "clamp(32px, 5vw, 48px)", letterSpacing: "-0.015em", color: "var(--ink)", margin: 0, lineHeight: 1.05 }}>
                Find a way to show up
              </h1>
            </div>
            <div style={{ fontSize: 13, color: "var(--fg-2)" }}>
              <span style={{ fontFamily: "var(--font-display)", fontSize: 24, color: "var(--ink)", marginRight: 6 }}>{filtered.length}</span>
              {filtered.length === 1 ? "opportunity" : "opportunities"} match
            </div>
          </div>

          <div className="portal-search-row">
            <div style={{ position: "relative", flex: "2 1 280px", minWidth: 200 }}>
              <ISearch size={16} style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", color: "var(--fg-3)" }} />
              <PInput placeholder="Search by park, activity, or keyword…" value={q} onChange={e => setQ(e.target.value)} style={{ paddingLeft: 40 }} />
            </div>
            <div style={{ position: "relative", flex: "1 1 130px", minWidth: 110, maxWidth: 180 }}>
              <IPin size={16} style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", color: "var(--fg-3)" }} />
              <PInput placeholder="ZIP code" value={zip} inputMode="numeric" maxLength={5} onChange={e => setZip(e.target.value.replace(/\D/g, ""))} style={{ paddingLeft: 38 }} />
            </div>
            <div style={{ flex: "1 1 160px", minWidth: 130, maxWidth: 200 }}>
              <PSelect value={dateRange} onChange={e => setDateRange(e.target.value)} options={[
                { value: "any", label: "Any date" },
                { value: "week", label: "This week" },
                { value: "month", label: "This month" },
                { value: "quarter", label: "Next 90 days" },
              ]}/>
            </div>
            <PBtn kind="primary" icon={ISearch}>Search</PBtn>
            <PBtn kind="secondary" icon={IFilter} onClick={() => setShowFilters(!showFilters)}>
              Filters{activeFilterCount ? ` · ${activeFilterCount}` : ""}
            </PBtn>
          </div>

          {/* interest chips */}
          <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 16 }}>
            <span style={{ fontSize: 11.5, fontWeight: 500, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--fg-3)", alignSelf: "center", marginRight: 4 }}>Interests:</span>
            {interestPool.slice(0, 8).map(t => (
              <PChip key={t} active={interests.has(t)} onClick={() => setInterests(toggleSet(interests, t))}>{t}</PChip>
            ))}
            {activeFilterCount > 0 && (
              <button onClick={clearAll} style={{ marginLeft: "auto", background: "transparent", border: "none", color: "var(--theme-accent)", fontSize: 12.5, fontFamily: "var(--font-sans)", cursor: "pointer", fontWeight: 500, padding: "5px 8px" }}>
                Clear all filters
              </button>
            )}
          </div>

          {/* View toggle + sort */}
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 22, gap: 16, flexWrap: "wrap" }}>
            <div className="portal-view-toggle" role="tablist">
              {[
                { id: "list", label: "List", icon: IList },
                { id: "calendar", label: "Calendar", icon: ICal },
                { id: "map", label: "Map", icon: IMap },
              ].map(v => (
                <button key={v.id} role="tab" aria-selected={view === v.id}
                  onClick={() => setView(v.id)}
                  className={`portal-view-btn ${view === v.id ? "is-active" : ""}`}>
                  <v.icon size={14} />
                  {v.label}
                </button>
              ))}
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 10, fontSize: 12.5, color: "var(--fg-2)" }}>
              <span>Sort by</span>
              <PSelect value={sort} onChange={e => setSort(e.target.value)} options={[
                { value: "date-asc", label: "Soonest" },
                { value: "date-desc", label: "Latest" },
                { value: "title", label: "Title (A–Z)" },
              ]} style={{ minHeight: 36, padding: "7px 30px 7px 12px", fontSize: 13, maxWidth: 160 }} />
            </div>
          </div>
        </PContainer>
      </div>

      {/* Expanded filter panel */}
      {showFilters && (
        <div style={{ background: "var(--paper-soft)", borderBottom: "1px solid var(--border-soft)" }}>
          <PContainer style={{ paddingTop: 24, paddingBottom: 24 }}>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 18 }}>
              <PField label="Program">
                <PSelect value={program} onChange={e => setProgram(e.target.value)}
                  options={programOpts.map(p => ({ value: p, label: p === "all" ? "All programs" : p }))} />
              </PField>
              <PField label="Difficulty">
                <PSelect value={difficulty} onChange={e => setDifficulty(e.target.value)} options={[
                  { value: "any", label: "Any" }, { value: "easy", label: "Easy" }, { value: "moderate", label: "Moderate" }, { value: "challenging", label: "Challenging" }
                ]} />
              </PField>
              <PField label="Language">
                <PSelect value={language} onChange={e => setLanguage(e.target.value)} options={[
                  { value: "any", label: "Any" }, { value: "English", label: "English" }, { value: "Spanish", label: "Spanish" }
                ]} />
              </PField>
              <PField label="Schedule">
                <PSelect value={schedule} onChange={e => setSchedule(e.target.value)} options={[
                  { value: "any", label: "Any time" },
                  { value: "morning", label: "Morning" },
                  { value: "midday", label: "Mid-day" },
                  { value: "evening", label: "Evening" },
                  { value: "weekends", label: "Weekends only" },
                  { value: "allday", label: "Full-day" },
                ]} />
              </PField>
              <PField label="Age suitability">
                <PSelect value={age} onChange={e => setAge(e.target.value)} options={[
                  { value: "any", label: "Any" }, { value: "all", label: "All ages" }, { value: "14", label: "14+" }, { value: "16", label: "16+" }, { value: "18", label: "18+" }, { value: "21", label: "21+" }
                ]} />
              </PField>
              <div>
                <div style={{ fontSize: 12, fontWeight: 500, color: "var(--fg-1)", marginBottom: 8 }}>Options</div>
                <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
                  <PCheckbox checked={family} onChange={() => setFamily(!family)} label="Family-friendly" />
                  <PCheckbox checked={accessibility} onChange={() => setAccessibility(!accessibility)} label="Accessibility accommodations" />
                </div>
              </div>
            </div>
          </PContainer>
        </div>
      )}

      {/* Results */}
      <PContainer style={{ paddingTop: 36 }}>
        {filtered.length === 0 ? (
          <div style={{ textAlign: "center", padding: "80px 20px" }}>
            <div style={{ width: 56, height: 56, borderRadius: 999, background: "var(--paper-soft)", display: "inline-flex", alignItems: "center", justifyContent: "center", color: "var(--fg-3)", marginBottom: 16 }}>
              <ISearch size={22} />
            </div>
            <h3 style={{ fontFamily: "var(--font-display)", fontSize: 28, margin: "0 0 8px", color: "var(--ink)" }}>No matches yet</h3>
            <p style={{ fontSize: 14, color: "var(--fg-2)", margin: "0 0 18px", maxWidth: 420, marginInline: "auto", lineHeight: 1.55 }}>
              Try widening the date range or removing a filter. Or sign up for new-opportunity emails — we'll let you know when something fits.
            </p>
            <PBtn kind="primary" onClick={clearAll}>Clear filters</PBtn>
          </div>
        ) : view === "list" ? (
          <div className="portal-opp-grid">
            {filtered.map(o => <PortalOppCard key={o.id} op={o} onClick={() => onOpenOpp(o.id)} />)}
          </div>
        ) : view === "calendar" ? (
          <PortalCalendar opps={filtered} onOpenOpp={onOpenOpp} />
        ) : (
          <PortalMap opps={filtered} tenant={tenant} onOpenOpp={onOpenOpp} />
        )}
      </PContainer>
    </div>
  );
};

// ====================== CALENDAR VIEW ======================
const PortalCalendar = ({ opps, onOpenOpp }) => {
  // Simple month calendar for Jun 2026
  const [monthOffset, setMonthOffset] = useStateD(0); // 0 = current (May), 1 = Jun
  const base = new Date("2026-05-01");
  base.setMonth(base.getMonth() + monthOffset);
  const year = base.getFullYear();
  const month = base.getMonth();
  const monthName = ["January","February","March","April","May","June","July","August","September","October","November","December"][month];
  const firstDay = new Date(year, month, 1).getDay();
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < firstDay; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);
  while (cells.length % 7 !== 0) cells.push(null);

  const oppsByDay = (d) => {
    if (!d) return [];
    const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
    return opps.filter(o => o.dateStart === iso || (o.dateStart <= iso && o.dateEnd >= iso && o.days.includes(["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][new Date(iso + "T12:00").getDay()])));
  };

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 18, flexWrap: "wrap", gap: 12 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
          <button onClick={() => setMonthOffset(monthOffset - 1)} className="portal-cal-nav" aria-label="Previous month">‹</button>
          <h2 style={{ fontFamily: "var(--font-display)", fontSize: 32, margin: 0, color: "var(--ink)", letterSpacing: "-0.01em", minWidth: 230 }}>{monthName} {year}</h2>
          <button onClick={() => setMonthOffset(monthOffset + 1)} className="portal-cal-nav" aria-label="Next month">›</button>
        </div>
        <button onClick={() => setMonthOffset(0)} style={{ fontSize: 12.5, fontFamily: "var(--font-sans)", color: "var(--theme-accent)", background: "transparent", border: "none", cursor: "pointer", fontWeight: 500 }}>
          Back to today
        </button>
      </div>

      <div style={{ background: "var(--paper-soft)", border: "1px solid var(--border-soft)", borderRadius: 14, overflow: "hidden" }}>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", borderBottom: "1px solid var(--border-soft)" }}>
          {["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].map(d => (
            <div key={d} style={{ padding: "12px 14px", fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--fg-3)" }}>{d}</div>
          ))}
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)" }}>
          {cells.map((d, i) => {
            const dayOpps = oppsByDay(d);
            const isToday = d === 23 && month === 4 && year === 2026; // pretend today is May 23, 2026
            return (
              <div key={i} className="portal-cal-cell" style={{ minHeight: 130, padding: 8, borderRight: (i + 1) % 7 !== 0 ? "1px solid var(--border-soft)" : "none", borderBottom: i < cells.length - 7 ? "1px solid var(--border-soft)" : "none", background: d ? "var(--paper-soft)" : "var(--paper-deep)", opacity: d ? 1 : 0.3 }}>
                {d && (
                  <>
                    <div style={{
                      display: "inline-flex", alignItems: "center", justifyContent: "center",
                      width: 26, height: 26, borderRadius: 999,
                      fontSize: 13, fontWeight: isToday ? 600 : 400,
                      fontVariantNumeric: "tabular-nums",
                      background: isToday ? "var(--theme-accent)" : "transparent",
                      color: isToday ? "var(--theme-accent-on)" : "var(--ink)",
                      marginBottom: 6,
                    }}>{d}</div>
                    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                      {dayOpps.slice(0, 3).map(o => (
                        <button key={o.id} onClick={() => onOpenOpp(o.id)}
                          className="portal-cal-event"
                          style={{
                            background: `var(--${o.media.tone}-100, var(--theme-accent-soft))`,
                            color: `var(--${o.media.tone === "sage" ? "sage-700" : o.media.tone === "coral" ? "coral-700" : o.media.tone === "iris" ? "iris-600" : "amber-600"})`,
                            border: "none", borderRadius: 6, padding: "3px 6px", fontSize: 11, fontWeight: 500, lineHeight: 1.3,
                            textAlign: "left", cursor: "pointer", fontFamily: "var(--font-sans)",
                            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", width: "100%",
                          }}>
                          {o.title}
                        </button>
                      ))}
                      {dayOpps.length > 3 && <span style={{ fontSize: 10, color: "var(--fg-3)" }}>+{dayOpps.length - 3} more</span>}
                    </div>
                  </>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
};

// ====================== MAP VIEW ======================
const PortalMap = ({ opps, tenant, onOpenOpp }) => {
  const [selected, setSelected] = useStateD(opps.find(o => o.coords)?.id);
  const sel = opps.find(o => o.id === selected);
  const inPerson = opps.filter(o => o.coords);

  return (
    <div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1.4fr) minmax(280px, 1fr)", gap: 16 }} className="portal-map-grid">
      <div style={{ position: "relative", borderRadius: 14, overflow: "hidden", border: "1px solid var(--border-soft)", minHeight: 540, background: "linear-gradient(180deg, #EAF1E8 0%, #E5EDDD 100%)" }}>
        {/* Stylized Colorado-ish map */}
        <svg width="100%" height="100%" viewBox="0 0 800 540" preserveAspectRatio="xMidYMid slice" style={{ position: "absolute", inset: 0 }}>
          <defs>
            <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
              <path d="M40 0H0V40" fill="none" stroke="var(--border-soft)" strokeWidth="0.5"/>
            </pattern>
          </defs>
          <rect width="800" height="540" fill="url(#grid)" opacity="0.5"/>
          {/* terrain shapes */}
          <path d="M100 100 Q220 80 340 130 T560 110 T740 150 L760 280 Q620 320 480 290 T240 300 T120 280 Z" fill="var(--sage-100)" opacity="0.65"/>
          <path d="M180 320 Q300 290 420 320 T620 330 T760 360 L760 480 Q580 500 420 480 T200 470 Z" fill="var(--amber-100)" opacity="0.5"/>
          {/* rivers */}
          <path d="M150 50 Q200 200 280 280 T420 480" fill="none" stroke="var(--iris-500)" strokeWidth="2" strokeOpacity="0.4" strokeDasharray="3 4"/>
          {/* state outline */}
          <rect x="80" y="60" width="660" height="430" fill="none" stroke="var(--stone-300)" strokeWidth="1.5" rx="6"/>
          <text x="100" y="90" fontFamily="var(--font-sans)" fontSize="11" fill="var(--fg-3)" letterSpacing="0.14em">COLORADO · ILLUSTRATIVE</text>
          {/* city dots */}
          <g fill="var(--stone-500)">
            <circle cx="475" cy="225" r="2.5"/><text x="482" y="228" fontFamily="var(--font-sans)" fontSize="10" fill="var(--fg-2)">Denver</text>
            <circle cx="450" cy="155" r="2.5"/><text x="457" y="158" fontFamily="var(--font-sans)" fontSize="10" fill="var(--fg-2)">Fort Collins</text>
            <circle cx="420" cy="265" r="2.5"/><text x="427" y="268" fontFamily="var(--font-sans)" fontSize="10" fill="var(--fg-2)">Colorado Springs</text>
          </g>
        </svg>

        {/* pins */}
        {inPerson.map(o => {
          const x = 80 + (o.coords.x * 660);
          const y = 60 + (o.coords.y * 430);
          const isSel = o.id === selected;
          return (
            <button key={o.id}
              onClick={() => setSelected(o.id)}
              className="portal-map-pin"
              style={{
                position: "absolute",
                left: `${(x / 800) * 100}%`, top: `${(y / 540) * 100}%`,
                transform: `translate(-50%, -100%) scale(${isSel ? 1.15 : 1})`,
                zIndex: isSel ? 5 : 2,
              }}
              aria-label={o.title}>
              <span style={{
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                width: 34, height: 34, borderRadius: "50% 50% 50% 0", transform: "rotate(-45deg)",
                background: isSel ? "var(--theme-accent)" : "var(--ink)",
                color: isSel ? "var(--theme-accent-on)" : "var(--paper)",
                fontSize: 11, fontWeight: 600, border: "2px solid var(--paper-soft)",
                boxShadow: "0 4px 12px rgba(31,27,22,0.25)",
              }}>
                <span style={{ transform: "rotate(45deg)" }}>{o.signups}</span>
              </span>
            </button>
          );
        })}

        <div style={{ position: "absolute", top: 14, right: 14, background: "var(--paper-soft)", borderRadius: 8, padding: "6px 10px", fontSize: 11, color: "var(--fg-2)", border: "1px solid var(--border)" }}>
          {inPerson.length} of {opps.length} shown · {opps.length - inPerson.length} virtual
        </div>
      </div>

      <div style={{ display: "flex", flexDirection: "column", gap: 12, maxHeight: 540, overflowY: "auto" }}>
        {sel && (
          <PCard style={{ borderColor: "var(--theme-accent)", borderWidth: 1.5 }}>
            <div style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--theme-accent)", marginBottom: 8 }}>Selected</div>
            <h3 style={{ fontFamily: "var(--font-sans)", fontSize: 16, fontWeight: 500, margin: "0 0 8px", color: "var(--ink)" }}>{sel.title}</h3>
            <div style={{ fontSize: 12.5, color: "var(--fg-2)", display: "flex", flexDirection: "column", gap: 4, marginBottom: 12 }}>
              <span><IClock size={12} /> {sel.dateLabel}</span>
              <span><IPin size={12} /> {sel.location}</span>
            </div>
            <PBtn size="sm" kind="primary" onClick={() => onOpenOpp(sel.id)} iconRight={IArrow}>See details</PBtn>
          </PCard>
        )}
        {opps.map(o => (
          <button key={o.id} onClick={() => o.coords ? setSelected(o.id) : onOpenOpp(o.id)}
            className="portal-map-list-item"
            style={{
              textAlign: "left", background: o.id === selected ? "var(--theme-accent-tint)" : "var(--paper-soft)",
              border: `1px solid ${o.id === selected ? "var(--theme-accent)" : "var(--border-soft)"}`,
              borderRadius: 10, padding: 12, cursor: "pointer", fontFamily: "var(--font-sans)",
            }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 500, color: "var(--ink)", marginBottom: 4 }}>{o.title}</div>
                <div style={{ fontSize: 11.5, color: "var(--fg-3)" }}>{o.dateLabel}</div>
              </div>
              {!o.coords && <span style={{ fontSize: 10, padding: "3px 7px", borderRadius: 999, background: "var(--iris-100)", color: "var(--iris-600)", fontWeight: 500 }}>online</span>}
            </div>
          </button>
        ))}
      </div>
    </div>
  );
};

// ====================== OPPORTUNITY DETAIL (public) ======================
// M5-FR-04 — public detail with only public-safe fields. Apply CTA preserves return URL.
const PortalOppDetail = ({ tenant, op, onBack, onApply, onSignin, related }) => {
  const dc = oppDateChip(op);
  const tone = op.media.tone;
  const toneBg = {
    sage:   "linear-gradient(135deg, #6B8B6B, #3D5C3F)",
    coral:  "linear-gradient(135deg, #E8794D, #C24A20)",
    iris:   "linear-gradient(135deg, #4356A1, #2B3F7A)",
    amber:  "linear-gradient(135deg, #C8852E, #9A6A1B)",
  }[tone];
  const isFull = op.status === "full";
  const isClosed = op.status === "closed";

  return (
    <div style={{ background: "var(--paper)", paddingBottom: 60 }}>
      <div style={{ background: "var(--paper-soft)", borderBottom: "1px solid var(--border-soft)" }}>
        <PContainer style={{ paddingTop: 22, paddingBottom: 22 }}>
          <button onClick={onBack} style={{ background: "transparent", border: "none", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 6, color: "var(--fg-2)", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500, padding: 0 }}>
            <IArrowL size={14} /> Back to opportunities
          </button>
        </PContainer>
      </div>

      <PContainer style={{ paddingTop: 32 }}>
        <div className="portal-detail-grid">
          <div>
            {/* Hero strip */}
            <div style={{ position: "relative", height: 260, background: toneBg, color: "#fff", borderRadius: 14, padding: 24, marginBottom: 26, overflow: "hidden" }}>
              <svg width="100%" height="100%" viewBox="0 0 600 260" preserveAspectRatio="xMidYMid slice" style={{ position: "absolute", inset: 0, opacity: 0.25 }}>
                <path d="M0 180 Q150 140 300 170 T600 160 L600 260 L0 260 Z" fill="rgba(255,255,255,0.3)"/>
                <path d="M0 220 Q200 200 400 215 T600 205 L600 260 L0 260 Z" fill="rgba(255,255,255,0.2)"/>
              </svg>
              <div style={{ position: "relative", display: "flex", justifyContent: "space-between", alignItems: "flex-start", flexWrap: "wrap", gap: 14 }}>
                <div>
                  <span style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.16em", textTransform: "uppercase", padding: "5px 11px", background: "rgba(255,255,255,0.18)", borderRadius: 999, backdropFilter: "blur(4px)" }}>
                    {op.program}
                  </span>
                </div>
                <PStatus status={op.status} />
              </div>
              <div style={{ position: "absolute", left: 24, right: 24, bottom: 24 }}>
                <h1 style={{ fontFamily: "var(--font-display)", fontSize: "clamp(28px, 4.6vw, 44px)", letterSpacing: "-0.01em", margin: "0 0 10px", lineHeight: 1.05, textWrap: "balance" }}>
                  {op.title}
                </h1>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 14, fontSize: 13.5, opacity: 0.94 }}>
                  <span style={{ display: "inline-flex", gap: 7, alignItems: "center" }}><IClock size={14} /> {op.dateLabel}</span>
                  <span style={{ display: "inline-flex", gap: 7, alignItems: "center" }}><IPin size={14} /> {op.site}</span>
                  {op.locationType === "virtual" && <span style={{ display: "inline-flex", gap: 7, alignItems: "center" }}><IGlobe size={14} /> Online</span>}
                </div>
              </div>
            </div>

            <h2 style={{ fontFamily: "var(--font-display)", fontSize: 26, margin: "0 0 12px", color: "var(--ink)", letterSpacing: "-0.01em" }}>
              About this {op.multiDay ? "role" : "shift"}
            </h2>
            <p style={{ fontSize: 15, lineHeight: 1.7, color: "var(--fg-1)", margin: "0 0 28px", textWrap: "pretty" }}>
              {op.summary}
            </p>

            {op.locationType === "virtual" && (
              <PCard style={{ marginBottom: 22, background: "var(--iris-100)", borderColor: "transparent" }}>
                <div style={{ display: "flex", gap: 12, alignItems: "flex-start" }}>
                  <IGlobe size={18} style={{ color: "var(--iris-600)", marginTop: 2, flexShrink: 0 }} />
                  <div>
                    <div style={{ fontSize: 13, fontWeight: 600, color: "var(--iris-600)", marginBottom: 4 }}>Online event</div>
                    <div style={{ fontSize: 13, color: "var(--ink)", lineHeight: 1.55 }}>The Zoom link is emailed 24 hours before the session, and again 30 minutes before start. Live captions are on by default.</div>
                  </div>
                </div>
              </PCard>
            )}

            <h3 style={{ fontFamily: "var(--font-display)", fontSize: 22, margin: "0 0 12px", color: "var(--ink)" }}>What to expect</h3>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))", gap: 14, marginBottom: 28 }}>
              <PCard padded style={{ background: "var(--paper)" }}>
                <div style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 8 }}>Difficulty</div>
                <div style={{ fontSize: 14, fontWeight: 500, color: "var(--ink)" }}>{op.difficulty}</div>
              </PCard>
              <PCard padded style={{ background: "var(--paper)" }}>
                <div style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 8 }}>Ages</div>
                <div style={{ fontSize: 14, fontWeight: 500, color: "var(--ink)" }}>{op.age}{op.familyFriendly ? " · family-friendly" : ""}</div>
              </PCard>
              <PCard padded style={{ background: "var(--paper)" }}>
                <div style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 8 }}>Languages</div>
                <div style={{ fontSize: 14, fontWeight: 500, color: "var(--ink)" }}>{op.language.join(", ")}</div>
              </PCard>
              {op.accessibility.length > 0 && (
                <PCard padded style={{ background: "var(--paper)" }}>
                  <div style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 8 }}>Accessibility</div>
                  <div style={{ fontSize: 13, color: "var(--ink)", lineHeight: 1.5 }}>{op.accessibility.join(" · ")}</div>
                </PCard>
              )}
            </div>

            {op.qualifications.length > 0 && (
              <>
                <h3 style={{ fontFamily: "var(--font-display)", fontSize: 22, margin: "0 0 12px", color: "var(--ink)" }}>What you'll need</h3>
                <PCard padded={false} style={{ marginBottom: 28 }}>
                  {op.qualifications.map((q, i, a) => (
                    <div key={q} style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: 12, alignItems: "center", padding: "14px 18px", borderBottom: i < a.length - 1 ? "1px solid var(--border-soft)" : "none" }}>
                      <span style={{ width: 26, height: 26, borderRadius: 999, background: "var(--theme-accent-tint)", color: "var(--theme-accent)", display: "inline-flex", alignItems: "center", justifyContent: "center" }}><ICheck size={13} /></span>
                      <span style={{ fontSize: 14, color: "var(--ink)" }}>{q}</span>
                    </div>
                  ))}
                </PCard>
              </>
            )}

            <PCard style={{ background: "var(--theme-accent-tint)", borderColor: "transparent" }}>
              <div style={{ display: "flex", gap: 14, alignItems: "flex-start" }}>
                <IInfo size={20} style={{ color: "var(--theme-accent)", marginTop: 2, flexShrink: 0 }} />
                <div style={{ fontSize: 13.5, color: "var(--fg-1)", lineHeight: 1.6 }}>
                  <strong style={{ fontWeight: 600, color: "var(--ink)" }}>Where to meet</strong> and what to bring is emailed two days before the shift. If you have an accessibility need or a question, reach out at <a href={`mailto:${tenant.contactEmail}`} style={{ color: "var(--theme-accent)", fontWeight: 500 }}>{tenant.contactEmail}</a> and we'll respond within one business day.
                </div>
              </div>
            </PCard>
          </div>

          {/* Sidebar */}
          <aside>
            <div style={{ position: "sticky", top: 100 }}>
              <PCard>
                {/* Capacity */}
                <div style={{ marginBottom: 18 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 6 }}>
                    <span style={{ fontFamily: "var(--font-display)", fontSize: 32, color: "var(--ink)", letterSpacing: "-0.01em", fontVariantNumeric: "tabular-nums" }}>
                      {Math.max(0, op.capacity - op.signups)}
                    </span>
                    <span style={{ fontSize: 12, color: "var(--fg-3)", fontVariantNumeric: "tabular-nums" }}>
                      of {op.capacity} spots
                    </span>
                  </div>
                  <div style={{ fontSize: 12.5, color: "var(--fg-2)", marginBottom: 10 }}>
                    {isFull ? "Full — waitlist open" : isClosed ? "Sign-ups closed" : "spots remaining"}
                  </div>
                  <PProgress value={op.signups} max={op.capacity} />
                </div>

                {/* Primary CTA */}
                {isClosed ? (
                  <PBtn kind="secondary" size="lg" style={{ width: "100%" }} disabled>
                    Sign-ups closed
                  </PBtn>
                ) : isFull && op.waitlist ? (
                  <PBtn kind="primary" size="lg" style={{ width: "100%" }} onClick={() => onApply(op)} iconRight={IArrow}>
                    Join the waitlist
                  </PBtn>
                ) : isFull ? (
                  <PBtn kind="secondary" size="lg" style={{ width: "100%" }} disabled>
                    Full — no waitlist
                  </PBtn>
                ) : (
                  <PBtn kind="primary" size="lg" style={{ width: "100%" }} onClick={() => onApply(op)} iconRight={IArrow}>
                    Apply now
                  </PBtn>
                )}

                <div style={{ fontSize: 11.5, color: "var(--fg-3)", textAlign: "center", marginTop: 10, lineHeight: 1.5 }}>
                  Have an account? <button onClick={onSignin} style={{ background: "transparent", border: "none", color: "var(--theme-accent)", fontFamily: "var(--font-sans)", fontSize: 11.5, cursor: "pointer", fontWeight: 500, padding: 0 }}>Sign in to apply faster</button>
                </div>

                <div style={{ borderTop: "1px solid var(--border-soft)", marginTop: 18, paddingTop: 18, display: "flex", flexDirection: "column", gap: 12, fontSize: 13 }}>
                  <DetailRow label="When">{op.dateLabel}</DetailRow>
                  <DetailRow label="Where">{op.site}</DetailRow>
                  <DetailRow label="Schedule">{op.schedule}</DetailRow>
                  <DetailRow label="Program">{op.program}</DetailRow>
                  {op.zips.length > 0 && <DetailRow label="ZIP">{op.zips.join(", ")}</DetailRow>}
                </div>
              </PCard>

              <PCard style={{ marginTop: 14 }}>
                <div style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 12 }}>Coordinator</div>
                <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                  <div style={{ width: 38, height: 38, borderRadius: 999, background: "var(--iris-100)", color: "var(--iris-600)", display: "inline-flex", alignItems: "center", justifyContent: "center", fontWeight: 500, fontSize: 13 }}>PS</div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 13, fontWeight: 500, color: "var(--ink)" }}>Priya Sandoval</div>
                    <div style={{ fontSize: 11.5, color: "var(--fg-3)" }}>{op.region} regional coordinator</div>
                  </div>
                </div>
                <PBtn kind="secondary" size="sm" icon={IMail} style={{ width: "100%", marginTop: 12 }}>Ask a question</PBtn>
              </PCard>
            </div>
          </aside>
        </div>

        {related.length > 0 && (
          <div style={{ marginTop: 56 }}>
            <h2 style={{ fontFamily: "var(--font-display)", fontSize: 28, margin: "0 0 18px", color: "var(--ink)" }}>Other ways to help</h2>
            <div className="portal-opp-grid">
              {related.slice(0, 3).map(r => <PortalOppCard key={r.id} op={r} onClick={() => { window.scrollTo(0, 0); onBack(); setTimeout(() => onApply.openOpp?.(r.id), 50); }} />)}
            </div>
          </div>
        )}
      </PContainer>
    </div>
  );
};

const DetailRow = ({ label, children }) => (
  <div style={{ display: "grid", gridTemplateColumns: "90px 1fr", gap: 12, alignItems: "baseline" }}>
    <span style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--fg-3)" }}>{label}</span>
    <span style={{ fontSize: 13.5, color: "var(--ink)" }}>{children}</span>
  </div>
);

window.PortalDiscover = PortalDiscover;
window.PortalCalendar = PortalCalendar;
window.PortalMap = PortalMap;
window.PortalOppDetail = PortalOppDetail;
