// CPW Public Portal — shared primitives, icons, layout
// Designed for the anonymous public-facing surface (no Crew sidebar/chrome).
// Tenant theming via --theme-accent (M5-FR-01).

const { useState: useStateP, useEffect: useEffectP, useMemo: useMemoP } = React;

// ---------- Icons (Lucide-ish, 1.75 stroke) ----------
const Ico = ({ d, size = 18, fill = "none", ...rest }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill={fill} stroke="currentColor"
       strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...rest}>
    {d}
  </svg>
);
const IPin     = (p) => <Ico {...p} d={<><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></>} />;
const IClock   = (p) => <Ico {...p} d={<><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>} />;
const ICal     = (p) => <Ico {...p} d={<><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M16 3v4M8 3v4M3 10h18"/></>} />;
const ISearch  = (p) => <Ico {...p} d={<><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></>} />;
const IList    = (p) => <Ico {...p} d={<><path d="M8 6h13M8 12h13M8 18h13"/><circle cx="4" cy="6" r="1"/><circle cx="4" cy="12" r="1"/><circle cx="4" cy="18" r="1"/></>} />;
const IMap     = (p) => <Ico {...p} d={<><path d="M9 4 3 6v14l6-2 6 2 6-2V4l-6 2-6-2Z"/><path d="M9 4v14M15 6v14"/></>} />;
const IFilter  = (p) => <Ico {...p} d={<><path d="M3 5h18M6 12h12M10 19h4"/></>} />;
const IArrow   = (p) => <Ico {...p} d={<><path d="M5 12h14M13 5l7 7-7 7"/></>} />;
const IArrowL  = (p) => <Ico {...p} d={<><path d="M19 12H5M11 19l-7-7 7-7"/></>} />;
const ICheck   = (p) => <Ico {...p} d={<><path d="M5 13l4 4L19 7"/></>} />;
const IX       = (p) => <Ico {...p} d={<><path d="M6 6l12 12M18 6 6 18"/></>} />;
const IUsers   = (p) => <Ico {...p} d={<><circle cx="9" cy="8" r="3.5"/><path d="M3 20c0-3.3 2.7-6 6-6s6 2.7 6 6"/><circle cx="17" cy="7" r="3"/><path d="M21 20c0-2.7-2.3-5-5-5"/></>} />;
const IHelp    = (p) => <Ico {...p} d={<><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 1 1 4.5 1.5c-1 .8-2 1.3-2 2.5"/><path d="M12 17h.01"/></>} />;
const IMail    = (p) => <Ico {...p} d={<><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></>} />;
const IPhone   = (p) => <Ico {...p} d={<><path d="M5 4h4l2 5-3 2a12 12 0 0 0 5 5l2-3 5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2Z"/></>} />;
const IExt     = (p) => <Ico {...p} d={<><path d="M14 5h5v5"/><path d="M19 5 10 14"/><path d="M19 14v5H5V5h5"/></>} />;
const IShield  = (p) => <Ico {...p} d={<><path d="M12 3 4 6v6c0 5 4 8 8 9 4-1 8-4 8-9V6l-8-3Z"/></>} />;
const IGlobe   = (p) => <Ico {...p} d={<><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a13 13 0 0 1 0 18M12 3a13 13 0 0 0 0 18"/></>} />;
const IInfo    = (p) => <Ico {...p} d={<><circle cx="12" cy="12" r="9"/><path d="M12 8h.01M11 12h1v5h1"/></>} />;
const IUser    = (p) => <Ico {...p} d={<><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/></>} />;
const IMenu    = (p) => <Ico {...p} d={<><path d="M3 6h18M3 12h18M3 18h18"/></>} />;
const IHeart   = (p) => <Ico {...p} d={<><path d="M12 21s-7-4.5-9.5-9A5.5 5.5 0 0 1 12 6a5.5 5.5 0 0 1 9.5 6c-2.5 4.5-9.5 9-9.5 9Z"/></>} />;
const IEye     = (p) => <Ico {...p} d={<><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></>} />;
const IEyeOff  = (p) => <Ico {...p} d={<><path d="M3 3l18 18"/><path d="M10.6 6.1A10 10 0 0 1 12 6c6.5 0 10 7 10 7a17 17 0 0 1-3.4 4.3"/><path d="M6.6 7.6A17 17 0 0 0 2 12s3.5 7 10 7a10 10 0 0 0 4.4-1"/><path d="M9.5 9.5a3 3 0 0 0 4.2 4.2"/></>} />;
const IChevD   = (p) => <Ico {...p} d={<><path d="M6 9l6 6 6-6"/></>} />;
const ISprout  = (p) => <Ico {...p} d={<><path d="M7 20h10"/><path d="M12 20v-8"/><path d="M12 12a6 6 0 0 0-6-6c0 4 2 6 6 6Z"/><path d="M12 12a4 4 0 0 1 4-4c0 3-2 4-4 4Z"/></>} />;
const IBird    = (p) => <Ico {...p} d={<><path d="M16 6a3 3 0 1 0-3 3"/><path d="M13 9 5 17l3 3 7-3 4-9-3-3"/><path d="M19 5h2"/></>} />;
const ITarget  = (p) => <Ico {...p} d={<><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></>} />;
const IBook    = (p) => <Ico {...p} d={<><path d="M4 5a2 2 0 0 1 2-2h12v18H6a2 2 0 0 1-2-2V5Z"/><path d="M18 17H6"/></>} />;
const ITent    = (p) => <Ico {...p} d={<><path d="M4 20 12 4l8 16"/><path d="M4 20h16"/><path d="M12 4v16"/></>} />;
const ITrail   = (p) => <Ico {...p} d={<><path d="M5 20c4-4 4-8 0-12"/><path d="M19 20c-4-4-4-8 0-12"/><path d="M5 20h14"/></>} />;
const IFish    = (p) => <Ico {...p} d={<><path d="M6 12c2-4 6-6 10-6 4 0 6 2 6 4 0 2-2 6-6 6-4 0-8-2-10-4Z"/><path d="M22 10c-2 1-2 3 0 4"/><circle cx="9" cy="10" r=".8" fill="currentColor"/></>} />;

const programIconFor = (id) => ({ parks: ITent, wildlife: IBird, hunter: ITarget, education: IBook, stewardship: ISprout }[id] || IPin);

// ---------- Buttons ----------
const PBtn = ({ kind = "primary", size = "md", icon: Ic, iconRight: IcR, children, style, ...rest }) => {
  const base = {
    display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
    fontFamily: "var(--font-sans)", fontWeight: 500, lineHeight: 1, cursor: "pointer",
    border: "1px solid transparent", borderRadius: 10, transition: "background 160ms var(--ease-out), color 160ms var(--ease-out), border-color 160ms",
    whiteSpace: "nowrap",
  };
  const sizes = {
    sm: { fontSize: 12.5, padding: "8px 14px", minHeight: 32, borderRadius: 8 },
    md: { fontSize: 14,   padding: "11px 18px", minHeight: 42, borderRadius: 10 },
    lg: { fontSize: 15,   padding: "14px 22px", minHeight: 50, borderRadius: 12 },
  };
  const kinds = {
    primary:   { background: "var(--theme-accent)", color: "var(--theme-accent-on)", borderColor: "var(--theme-accent)" },
    secondary: { background: "var(--paper-soft)", color: "var(--ink)", borderColor: "var(--border)" },
    ghost:     { background: "transparent", color: "var(--ink)", borderColor: "transparent" },
    dark:      { background: "var(--ink)", color: "var(--paper)", borderColor: "var(--ink)" },
    outline:   { background: "transparent", color: "var(--theme-accent)", borderColor: "var(--theme-accent)" },
    link:      { background: "transparent", color: "var(--theme-accent)", borderColor: "transparent", padding: 0, minHeight: 0, fontWeight: 500, textDecoration: "underline", textUnderlineOffset: "3px" },
  };
  return (
    <button {...rest} style={{ ...base, ...sizes[size], ...kinds[kind], ...style }}
      onMouseEnter={(e) => { if (kind === "primary") e.currentTarget.style.background = "var(--theme-accent-hover)"; }}
      onMouseLeave={(e) => { if (kind === "primary") e.currentTarget.style.background = "var(--theme-accent)"; }}>
      {Ic && <Ic size={size === "lg" ? 18 : size === "sm" ? 14 : 16} />}
      {children}
      {IcR && <IcR size={size === "lg" ? 18 : size === "sm" ? 14 : 16} />}
    </button>
  );
};

// ---------- Cards / surfaces ----------
const PCard = ({ children, padded = true, style, hover, onClick, as: As = "div", ...rest }) => (
  <As onClick={onClick}
      style={{
        background: "var(--paper-soft)", border: "1px solid var(--border-soft)",
        borderRadius: 14, padding: padded ? 22 : 0,
        cursor: onClick || hover ? "pointer" : "default",
        transition: "background 160ms var(--ease-out), border-color 160ms",
        ...style,
      }}
      onMouseEnter={(e) => { if (onClick || hover) { e.currentTarget.style.background = "var(--paper-deep)"; e.currentTarget.style.borderColor = "var(--border)"; } }}
      onMouseLeave={(e) => { if (onClick || hover) { e.currentTarget.style.background = "var(--paper-soft)"; e.currentTarget.style.borderColor = "var(--border-soft)"; } }}
      {...rest}>
    {children}
  </As>
);

// ---------- Form inputs ----------
const PField = ({ label, hint, required, error, children }) => (
  <label style={{ display: "block", marginBottom: 16 }}>
    <div style={{ fontSize: 12, fontWeight: 500, color: "var(--fg-1)", marginBottom: 6 }}>
      {label}{required && <span style={{ color: "var(--theme-accent)", marginLeft: 4 }}>*</span>}
    </div>
    {children}
    {hint && !error && <div style={{ fontSize: 11.5, color: "var(--fg-3)", marginTop: 5 }}>{hint}</div>}
    {error && <div style={{ fontSize: 11.5, color: "var(--crimson-600)", marginTop: 5 }}>{error}</div>}
  </label>
);

const PInput = ({ style, ...rest }) => (
  <input {...rest}
    style={{
      width: "100%", boxSizing: "border-box",
      fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--ink)",
      background: "var(--paper-soft)",
      border: "1px solid var(--border)", borderRadius: 8,
      padding: "11px 14px", minHeight: 44,
      boxShadow: "var(--shadow-inset)",
      outline: "none",
      ...style,
    }}
    onFocus={(e) => { e.target.style.borderColor = "var(--theme-accent)"; e.target.style.boxShadow = "var(--shadow-inset), 0 0 0 3px var(--theme-accent-soft)"; rest.onFocus?.(e); }}
    onBlur={(e)  => { e.target.style.borderColor = "var(--border)"; e.target.style.boxShadow = "var(--shadow-inset)"; rest.onBlur?.(e); }}
  />
);

const PSelect = ({ options = [], style, ...rest }) => (
  <select {...rest}
    style={{
      width: "100%", boxSizing: "border-box",
      fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--ink)",
      background: "var(--paper-soft)",
      border: "1px solid var(--border)", borderRadius: 8,
      padding: "11px 14px", minHeight: 44,
      appearance: "none",
      backgroundImage: "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231F1B16' stroke-width='1.75'><path d='M6 9l6 6 6-6'/></svg>\")",
      backgroundRepeat: "no-repeat", backgroundPosition: "right 12px center", backgroundSize: 16,
      paddingRight: 36,
      ...style,
    }}>
    {options.map(o => typeof o === "string" ? <option key={o}>{o}</option> : <option key={o.value} value={o.value}>{o.label}</option>)}
  </select>
);

const PCheckbox = ({ checked, onChange, label, style }) => (
  <label style={{ display: "inline-flex", alignItems: "center", gap: 10, cursor: "pointer", fontSize: 13, color: "var(--fg-1)", ...style }}>
    <span style={{
      width: 18, height: 18, borderRadius: 5, border: "1.5px solid",
      borderColor: checked ? "var(--theme-accent)" : "var(--border)",
      background: checked ? "var(--theme-accent)" : "var(--paper-soft)",
      display: "inline-flex", alignItems: "center", justifyContent: "center",
      color: "var(--theme-accent-on)", flexShrink: 0,
    }}>
      {checked && <ICheck size={12} />}
    </span>
    <input type="checkbox" checked={!!checked} onChange={onChange} style={{ position: "absolute", opacity: 0, pointerEvents: "none" }} />
    {label && <span>{label}</span>}
  </label>
);

const PChip = ({ children, active, onClick, tone = "default" }) => {
  const tones = {
    default: { bg: active ? "var(--theme-accent)" : "var(--paper-soft)", fg: active ? "var(--theme-accent-on)" : "var(--fg-1)", border: active ? "var(--theme-accent)" : "var(--border)" },
    soft:    { bg: "var(--theme-accent-tint)", fg: "var(--theme-accent)", border: "transparent" },
    info:    { bg: "var(--iris-100)", fg: "var(--iris-600)", border: "transparent" },
    success: { bg: "var(--sage-100)", fg: "var(--sage-700)", border: "transparent" },
    warning: { bg: "var(--amber-100)", fg: "var(--amber-600)", border: "transparent" },
    danger:  { bg: "var(--crimson-100)", fg: "var(--crimson-600)", border: "transparent" },
    neutral: { bg: "var(--paper-deep)", fg: "var(--fg-1)", border: "transparent" },
  };
  const c = tones[tone] || tones.default;
  return (
    <button onClick={onClick} type="button" style={{
      display: "inline-flex", alignItems: "center", gap: 6, padding: "5px 11px",
      borderRadius: 999, fontSize: 12, fontWeight: 500, lineHeight: 1.4,
      background: c.bg, color: c.fg, border: `1px solid ${c.border}`,
      cursor: onClick ? "pointer" : "default", whiteSpace: "nowrap",
      fontFamily: "var(--font-sans)",
    }}>
      {children}
    </button>
  );
};

// status pill (open/full/closed/waitlist) for opportunities
const PStatus = ({ status }) => {
  const map = {
    open:     { tone: "success", label: "Open", dot: "var(--sage-700)" },
    full:     { tone: "warning", label: "Full · waitlist", dot: "var(--amber-600)" },
    closed:   { tone: "danger",  label: "Closed", dot: "var(--crimson-600)" },
    soon:     { tone: "info",    label: "Coming soon", dot: "var(--iris-600)" },
  };
  const s = map[status] || map.open;
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6, fontSize: 11.5, fontWeight: 500,
      padding: "4px 10px", borderRadius: 999,
      background: `var(--${s.tone === "success" ? "sage" : s.tone === "warning" ? "amber" : s.tone === "info" ? "iris" : "crimson"}-100)`,
      color: `var(--${s.tone === "success" ? "sage-700" : s.tone === "warning" ? "amber-600" : s.tone === "info" ? "iris-600" : "crimson-600"})`,
    }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, background: s.dot }} />
      {s.label}
    </span>
  );
};

// progress bar
const PProgress = ({ value, max, color }) => {
  const pct = Math.min(100, Math.round((value / Math.max(1, max)) * 100));
  return (
    <div style={{ height: 6, background: "var(--paper-deep)", borderRadius: 999, overflow: "hidden" }}>
      <div style={{ height: "100%", width: `${pct}%`, background: color || "var(--theme-accent)", borderRadius: 999 }} />
    </div>
  );
};

// ---------- Header / Footer ----------
const PortalHeader = ({ tenant, route, onNav, onSignin }) => {
  const [open, setOpen] = useStateP(false);
  const links = [
    { id: "discover", label: "Find opportunities" },
    { id: "calendar", label: "Calendar" },
    { id: "programs", label: "Programs" },
    { id: "help",     label: "Help" },
  ];
  return (
    <header className="portal-header">
      <div className="portal-header-inner">
        <button onClick={() => onNav("home")} className="portal-wordmark" aria-label={`${tenant.name} volunteer portal home`}>
          <span className="portal-glyph">{tenant.glyph}</span>
          <span className="portal-wordmark-text">
            <span className="portal-wordmark-line1">{tenant.wordmark}</span>
            <span className="portal-wordmark-line2">Volunteer portal</span>
          </span>
        </button>
        <nav className="portal-nav-desktop">
          {links.map(l => (
            <button key={l.id} onClick={() => onNav(l.id)}
              className={`portal-nav-link ${route === l.id ? "is-active" : ""}`}>
              {l.label}
            </button>
          ))}
        </nav>
        <div className="portal-nav-actions">
          <PBtn kind="ghost" size="sm" onClick={onSignin}>Sign in</PBtn>
          <PBtn kind="primary" size="sm" onClick={() => onNav("apply")}>Apply now</PBtn>
        </div>
        <button className="portal-menu-btn" onClick={() => setOpen(!open)} aria-label="Menu"><IMenu /></button>
      </div>
      {open && (
        <div className="portal-mobile-menu">
          {links.map(l => (
            <button key={l.id} onClick={() => { onNav(l.id); setOpen(false); }} className="portal-mobile-link">{l.label}</button>
          ))}
          <button onClick={() => { onSignin(); setOpen(false); }} className="portal-mobile-link">Sign in</button>
          <button onClick={() => { onNav("apply"); setOpen(false); }} className="portal-mobile-link portal-mobile-link-primary">Apply now</button>
        </div>
      )}
    </header>
  );
};

const PortalFooter = ({ tenant, onNav }) => (
  <footer className="portal-footer">
    <div className="portal-footer-inner">
      <div className="portal-footer-brand">
        <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 14 }}>
          <span className="portal-glyph" style={{ background: "var(--theme-accent)", color: "var(--theme-accent-on)" }}>{tenant.glyph}</span>
          <span style={{ fontFamily: "var(--font-display)", fontSize: 22, color: "var(--ink)", letterSpacing: "-0.01em" }}>{tenant.wordmark}</span>
        </div>
        <p style={{ fontSize: 13, color: "var(--fg-2)", lineHeight: 1.6, margin: 0, maxWidth: 340 }}>{tenant.footerBlurb}</p>
      </div>
      <div className="portal-footer-col">
        <div className="portal-footer-h">Volunteer</div>
        <button onClick={() => onNav("discover")} className="portal-footer-link">Find opportunities</button>
        <button onClick={() => onNav("calendar")} className="portal-footer-link">Calendar</button>
        <button onClick={() => onNav("apply")} className="portal-footer-link">Apply now</button>
        <button onClick={() => onNav("signin")} className="portal-footer-link">Sign in</button>
        <button onClick={() => onNav("claim")} className="portal-footer-link">Claim an existing record</button>
      </div>
      <div className="portal-footer-col">
        <div className="portal-footer-h">Help</div>
        <button onClick={() => onNav("help")} className="portal-footer-link">FAQ</button>
        <button onClick={() => onNav("help")} className="portal-footer-link">Accessibility</button>
        <a href={`mailto:${tenant.contactEmail}`} className="portal-footer-link">{tenant.contactEmail}</a>
        <a href={`tel:${tenant.contactPhone}`} className="portal-footer-link">{tenant.contactPhone}</a>
      </div>
      <div className="portal-footer-col">
        <div className="portal-footer-h">{tenant.shortName}</div>
        <div style={{ fontSize: 13, color: "var(--fg-2)", lineHeight: 1.6 }}>
          {tenant.address}
        </div>
      </div>
    </div>
    <div className="portal-footer-bottom">
      <div>© 2026 {tenant.name}. All rights reserved.</div>
      <div className="portal-footer-bottom-right">
        <button onClick={() => onNav("help")} className="portal-footer-bottom-link">Privacy</button>
        <button onClick={() => onNav("help")} className="portal-footer-bottom-link">Terms</button>
        <span className="portal-powered">Powered by <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, letterSpacing: "-0.03em" }}>crew</span></span>
      </div>
    </div>
  </footer>
);

// ---------- Container ----------
const PContainer = ({ children, narrow, style }) => (
  <div style={{ maxWidth: narrow ? 720 : 1240, margin: "0 auto", padding: "0 clamp(20px, 4vw, 48px)", ...style }}>
    {children}
  </div>
);

// ---------- Opportunity card ----------
const oppDateChip = (op) => {
  // small day/month tile pulled from dateLabel for visual scanning
  const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
  if (op.locationType === "virtual") return { day: "·", month: "ONLINE", weekday: "" };
  if (op.type === "ongoing" || op.schedule.startsWith("Ongoing")) return { day: "·", month: "ONGOING", weekday: "" };
  const d = new Date(op.dateStart + "T12:00");
  return {
    day: String(d.getDate()),
    month: months[d.getMonth()].toUpperCase(),
    weekday: ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()],
  };
};

const PortalOppCard = ({ op, onClick, layout = "default" }) => {
  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] || "linear-gradient(135deg, #6B8B6B, #3D5C3F)";

  return (
    <article onClick={onClick}
      className="portal-opp-card"
      style={{
        background: "var(--paper-soft)", border: "1px solid var(--border-soft)", borderRadius: 14,
        overflow: "hidden", cursor: "pointer", display: "flex", flexDirection: "column",
        transition: "border-color 160ms, transform 160ms",
      }}
      onMouseEnter={(e) => { e.currentTarget.style.borderColor = "var(--border-strong)"; e.currentTarget.style.transform = "translateY(-2px)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--border-soft)"; e.currentTarget.style.transform = "translateY(0)"; }}>
      <div style={{ position: "relative", height: 160, background: toneBg, color: "#fff", padding: 16, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 }}>
          <span style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", padding: "4px 9px", background: "rgba(255,255,255,0.18)", borderRadius: 999, backdropFilter: "blur(4px)" }}>
            {op.program}
          </span>
          {op.familyFriendly && <span title="Family-friendly" style={{ width: 28, height: 28, borderRadius: 999, background: "rgba(255,255,255,0.18)", display: "inline-flex", alignItems: "center", justifyContent: "center" }}><IUsers size={14} /></span>}
        </div>
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 12 }}>
          <div style={{ background: "var(--paper-soft)", color: "var(--ink)", borderRadius: 10, padding: "6px 10px", textAlign: "center", minWidth: 56 }}>
            <div style={{ fontSize: 9.5, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--fg-3)", fontWeight: 600 }}>{dc.weekday || dc.month}</div>
            <div style={{ fontFamily: "var(--font-display)", fontSize: 26, color: "var(--ink)", lineHeight: 1, marginTop: 2 }}>{dc.day}</div>
            {dc.weekday && <div style={{ fontSize: 9.5, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--fg-3)", fontWeight: 600 }}>{dc.month}</div>}
          </div>
        </div>
      </div>
      <div style={{ padding: 18, flex: 1, display: "flex", flexDirection: "column" }}>
        <h3 style={{ fontFamily: "var(--font-sans)", fontSize: 17, fontWeight: 500, color: "var(--ink)", margin: "0 0 8px", lineHeight: 1.3, textWrap: "balance" }}>{op.title}</h3>
        <div style={{ display: "flex", flexDirection: "column", gap: 5, fontSize: 12.5, color: "var(--fg-2)", marginBottom: 12 }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}><IClock size={13} /> {op.dateLabel}</span>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}><IPin size={13} /> {op.location}{op.locationType === "virtual" && " · online"}</span>
        </div>
        <p style={{ fontSize: 13, color: "var(--fg-2)", lineHeight: 1.55, margin: "0 0 14px", textWrap: "pretty", flex: 1 }}>
          {op.summary.length > 130 ? op.summary.slice(0, 128) + "…" : op.summary}
        </p>
        <div style={{ marginTop: "auto" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 11.5, color: "var(--fg-3)", marginBottom: 6, fontVariantNumeric: "tabular-nums" }}>
            <span>{op.signups} of {op.capacity} signed up</span>
            <PStatus status={op.status} />
          </div>
          <PProgress value={op.signups} max={op.capacity} />
        </div>
      </div>
    </article>
  );
};

// ---------- Hero placeholder ----------
const PortalHeroImage = ({ tenant }) => (
  <div className="portal-hero-image" style={{ background: tenant.accentSoft, color: tenant.accent }}>
    <svg width="100%" height="100%" viewBox="0 0 600 420" preserveAspectRatio="xMidYMid slice" style={{ position: "absolute", inset: 0 }}>
      <defs>
        <linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor="var(--paper-soft)" stopOpacity="0.85"/>
          <stop offset="1" stopColor={tenant.accentSoft} stopOpacity="0.4"/>
        </linearGradient>
        <linearGradient id="land" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor={tenant.accentPress}/>
          <stop offset="1" stopColor={tenant.accent}/>
        </linearGradient>
      </defs>
      <rect width="600" height="420" fill="url(#sky)"/>
      {/* distant ridge */}
      <path d="M0 230 L80 200 L160 220 L240 180 L320 210 L420 180 L520 215 L600 195 L600 280 L0 280 Z" fill={tenant.accent} opacity="0.18"/>
      {/* mid ridge */}
      <path d="M0 270 L60 240 L140 260 L230 220 L320 250 L430 215 L520 250 L600 235 L600 320 L0 320 Z" fill={tenant.accent} opacity="0.3"/>
      {/* close ridge */}
      <path d="M0 320 L80 290 L180 310 L260 280 L380 310 L470 285 L600 305 L600 420 L0 420 Z" fill="url(#land)"/>
      {/* sun */}
      <circle cx="450" cy="150" r="38" fill="var(--paper-soft)" opacity="0.85"/>
      {/* trees silhouette */}
      <g fill={tenant.accentPress} opacity="0.6">
        <path d="M70 380 L80 360 L82 340 L90 330 L95 350 L100 340 L108 350 L110 380 Z"/>
        <path d="M150 380 L160 350 L170 380 Z"/>
        <path d="M520 380 L530 355 L540 380 Z"/>
      </g>
    </svg>
    <div style={{ position: "absolute", bottom: 14, left: 16, fontSize: 11, fontWeight: 500, padding: "5px 10px", borderRadius: 999, background: "rgba(31,27,22,0.7)", color: "var(--paper-soft)", backdropFilter: "blur(4px)" }}>
      {tenant.heroImageLabel || "Replace with vendor photography"}
    </div>
  </div>
);

window.Portal = {
  PBtn, PCard, PField, PInput, PSelect, PCheckbox, PChip, PStatus, PProgress,
  PortalHeader, PortalFooter, PContainer, PortalOppCard, PortalHeroImage,
  IPin, IClock, ICal, ISearch, IList, IMap, IFilter, IArrow, IArrowL, ICheck, IX,
  IUsers, IHelp, IMail, IPhone, IExt, IShield, IGlobe, IInfo, IUser, IMenu, IHeart, IEye, IEyeOff, IChevD,
  ISprout, IBird, ITarget, IBook, ITent, ITrail, IFish,
  programIconFor, oppDateChip,
};
