// GuestMaker CRM B2B · the twelve primitives, each with its own micro-viz.

const vizWrap = { display: "flex", flexDirection: "column", gap: 5 };
const chip = (bg, fg, bd) => ({ background: bg, color: fg, border: `1px solid ${bd}`, borderRadius: 4, padding: "2px 6px", fontFamily: MONO, fontSize: 8.5, fontWeight: 700, letterSpacing: "0.06em", whiteSpace: "nowrap" });

function VizFlows() {
  const rows = [
    { lbl: "CORPORATE / FIT", fields: ["RATE TYPE", "RN COMMIT", "ALLOTMENT", "PROMO CODE"], color: FUCHSIA },
    { lbl: "GROUPS & EVENTS", fields: ["EVENT DATES", "PAX", "MEETING SPACE", "PMS LOCATOR"], color: VIOLET },
  ];
  return (
    <div style={vizWrap}>
      {rows.map(r => (
        <div key={r.lbl} style={{ border: `1px solid ${RULE}`, borderLeft: `2.5px solid ${r.color}`, borderRadius: 5, padding: "6px 8px" }}>
          <Mono size={8.5} color={r.color} style={{ fontWeight: 700, letterSpacing: "0.14em" }}>{r.lbl}</Mono>
          <div style={{ display: "flex", gap: 4, flexWrap: "wrap", marginTop: 5 }}>
            {r.fields.map(f => <span key={f} style={chip(PAPER, DIM, RULE)}>{f}</span>)}
          </div>
        </div>
      ))}
      <Mono size={8.5} color={FAINT}>SAME BOARD · DIFFERENT DATA MODEL UNDERNEATH</Mono>
    </div>
  );
}
function VizGates() {
  const rows = [["CLOSE DATE", true], ["PRIMARY CONTACT", true], ["ROOM-NIGHT COMMITMENT", true], ["PROMO CODE", false], ["PAYMENT TERMS ACCEPTED", false]];
  return (
    <div style={vizWrap}>
      {rows.map(([l, ok]) => (
        <div key={l} style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ width: 13, height: 13, borderRadius: 3, background: ok ? "#E9F3EC" : "#FBEDED", border: `1px solid ${ok ? "#BFDDC9" : "#EFD2D2"}`, display: "grid", placeItems: "center" }}>
            <Icon name={ok ? "check" : "x"} size={8} color={ok ? GREEN : RED} strokeWidth={3} />
          </span>
          <Mono size={9} color={ok ? DIM : RED} style={{ fontWeight: ok ? 500 : 700 }}>{l}</Mono>
        </div>
      ))}
      <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 2, paddingTop: 5, borderTop: `1px dashed ${RULE}` }}>
        <Icon name="lock" size={10} color={RED} />
        <Mono size={8.5} color={RED} style={{ fontWeight: 700 }}>BLOCKED AT THE WON BOUNDARY · 2 PENDING</Mono>
      </div>
    </div>
  );
}
function VizApproval() {
  return (
    <div style={vizWrap}>
      {[["QUALIFICATION", false], ["PROPOSAL SENT", false], ["NEGOTIATION", true]].map(([l, on]) => (
        <div key={l} style={{ display: "flex", alignItems: "center", gap: 7, border: `1px solid ${on ? FUCHSIA_PALE : RULE}`, background: on ? FUCHSIA_PALE + "35" : "transparent", borderRadius: 5, padding: "5px 7px" }}>
          <Mono size={8.5} color={on ? FUCHSIA_DARK : DIM} style={{ fontWeight: 700, flex: 1 }}>{l}</Mono>
          {on ? <>
            <Icon name="lock" size={9} color={FUCHSIA_DARK} />
            <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}><Avatar who="ml" size={14} /><Mono size={8} color={FUCHSIA_DARK}>RUBÉN C.</Mono></span>
          </> : <Mono size={8} color={FAINT}>OPEN</Mono>}
        </div>
      ))}
      <Mono size={8.5} color={FAINT}>BACKWARDS AND LOST MOVES ARE NEVER GATED</Mono>
    </div>
  );
}
function VizRotting() {
  const rows = [["QUALIFICATION", 14, 9], ["PROPOSAL", 21, 19], ["NEGOTIATION", 30, 34]];
  return (
    <div style={vizWrap}>
      {rows.map(([l, limit, at]) => {
        const over = at > limit;
        return (
          <div key={l}>
            <div style={{ display: "flex", justifyContent: "space-between" }}>
              <Mono size={8.5} color={DIM} style={{ fontWeight: 600 }}>{l}</Mono>
              <Mono size={8.5} color={over ? AMBER : DIM}>{at}d / {limit}d</Mono>
            </div>
            <div style={{ height: 6, marginTop: 3, borderRadius: 999, background: PAPER, border: `1px solid ${RULE_SOFT}`, overflow: "hidden", position: "relative" }}>
              <div style={{ height: "100%", width: `${Math.min(100, (at / limit) * 100)}%`, background: over ? AMBER : GREEN }} />
            </div>
          </div>
        );
      })}
      <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 2 }}>
        <Icon name="wilt" size={10} color={AMBER} /><Mono size={8.5} color={AMBER} style={{ fontWeight: 700 }}>1 WILTING · ANY LOGGED ACTIVITY RESETS THE CLOCK</Mono>
      </div>
    </div>
  );
}
function VizAttribution() {
  return (
    <div style={vizWrap}>
      {MATCH_REASONS.map((r, i) => (
        <div key={r.k} style={{ display: "flex", alignItems: "center", gap: 7 }}>
          <span style={chip(r.color + "18", r.color, r.color + "55")}>{r.k}</span>
          <Mono size={8.5} color={DIM} style={{ flex: 1 }}>{r.label.toUpperCase()}</Mono>
          <Mono size={8} color={i === 3 ? AMBER : GREEN} style={{ fontWeight: 700 }}>{i === 3 ? "SUGGEST" : "AUTO-LINK"}</Mono>
        </div>
      ))}
      <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 2, paddingTop: 5, borderTop: `1px dashed ${RULE}` }}>
        <Icon name="refresh" size={10} color={FUCHSIA} />
        <Mono size={8.5} color={FUCHSIA} style={{ fontWeight: 700 }}>EVERY 10 MIN · 90-DAY ROLLING WINDOW</Mono>
      </div>
    </div>
  );
}
function VizCommissions() {
  return (
    <div style={vizWrap}>
      <div style={{ display: "flex", gap: 3 }}>
        {[["ACCRUED", FUCHSIA], ["INVOICED", VIOLET], ["SETTLED", GREEN]].map(([l, c], i) => (
          <div key={l} style={{ flex: 1, background: c + "14", border: `1px solid ${c}44`, borderRadius: 4, padding: "5px 6px", textAlign: "center" }}>
            <Mono size={8} color={c} style={{ fontWeight: 700 }}>{l}</Mono>
            <div><Mono size={10} color={INK} style={{ fontWeight: 700 }}>{eur([35200, 28600, 67100][i])}</Mono></div>
          </div>
        ))}
      </div>
      <div style={{ border: `1px solid ${RULE}`, borderRadius: 5, padding: "5px 7px" }}>
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <Mono size={8.5} color={DIM}>PMS-4271884 · CHECKED OUT</Mono>
          <Mono size={8.5} color={INK} style={{ fontWeight: 700 }}>€1,284</Mono>
        </div>
        <Mono size={8} color={FAINT}>BASE €10,700 NET × 12% INHERITED FROM NORDWIND</Mono>
      </div>
      <Mono size={8.5} color={FAINT}>ONE ENTRY PER RESERVATION · NEVER DOUBLED ON RETRY</Mono>
    </div>
  );
}
function VizHierarchy() {
  return (
    <div style={vizWrap}>
      <div style={{ display: "flex", alignItems: "center", gap: 6, border: `1px solid ${RULE}`, borderRadius: 5, padding: "5px 7px" }}>
        <AccountBadge id="tuig" size={16} />
        <Mono size={9} color={INK} style={{ fontWeight: 700, flex: 1 }}>NORDWIND</Mono>
        <span style={chip(FUCHSIA_PALE + "60", FUCHSIA_DARK, FUCHSIA_PALE)}>12%</span>
      </div>
      <div style={{ display: "flex", gap: 6, paddingLeft: 12 }}>
        <span style={{ width: 10, borderLeft: `1px solid ${RULE}`, borderBottom: `1px solid ${RULE}`, marginBottom: 12 }} />
        <div style={{ flex: 1, display: "flex", alignItems: "center", gap: 6, border: `1px solid ${RULE}`, borderRadius: 5, padding: "5px 7px" }}>
          <AccountBadge id="tuies" size={16} />
          <Mono size={9} color={INK} style={{ fontWeight: 600, flex: 1 }}>NORDWIND ESPAÑA</Mono>
          <span style={chip(PAPER, FUCHSIA, RULE)}>INHERITS 12%</span>
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
        <Icon name="arrow-u" size={10} color={VIOLET} />
        <Mono size={8.5} color={VIOLET} style={{ fontWeight: 700 }}>PRODUCTION ROLLS UP · 3 LEVELS DEEP</Mono>
      </div>
    </div>
  );
}
function VizProposal() {
  return (
    <div style={vizWrap}>
      <div style={{ display: "flex", gap: 2 }}>
        {["DRAFT", "SENT", "VIEWED 9×", "ACCEPTED"].map((s, i) => (
          <div key={s} style={{ flex: 1, background: i === 3 ? GREEN : FUCHSIA_PALE + "60", border: `1px solid ${i === 3 ? GREEN : FUCHSIA_PALE}`, padding: "4px 3px", textAlign: "center" }}>
            <Mono size={7.5} color={i === 3 ? "#fff" : FUCHSIA_DARK} style={{ fontWeight: 700 }}>{s}</Mono>
          </div>
        ))}
      </div>
      <div style={{ border: `1px solid ${RULE}`, borderRadius: 5, padding: "7px 8px" }}>
        <svg viewBox="0 0 160 26" style={{ width: "100%", height: 26 }}>
          <path d="M4 20 C18 4 26 24 38 14 C50 4 58 22 72 12 C86 2 96 20 110 14 C120 10 128 18 140 8"
            fill="none" stroke={INK} strokeWidth="1.6" strokeLinecap="round" />
        </svg>
        <Mono size={8} color={FAINT}>LENNART BRAUWE · IP 85.62.14.203 · SHA256:9F4C…E21B</Mono>
      </div>
      <Mono size={8.5} color={FAINT}>SIGNED IN THE BROWSER · PDF IDENTICAL FROM THE SAME TEMPLATE</Mono>
    </div>
  );
}
function VizLanguages() {
  const langs = ["EN","ES","DE","FR","IT","NL","PT","SV","DA","NO","PL","CA"];
  return (
    <div style={vizWrap}>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(6,1fr)", gap: 3 }}>
        {langs.map((l, i) => (
          <div key={l} style={{ background: i < 2 ? FUCHSIA : PAPER, border: `1px solid ${i < 2 ? FUCHSIA : RULE}`, borderRadius: 4, padding: "4px 0", textAlign: "center" }}>
            <Mono size={8.5} color={i < 2 ? "#fff" : DIM} style={{ fontWeight: 700 }}>{l}</Mono>
          </div>
        ))}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 2 }}>
        <Icon name="lang" size={10} color={FUCHSIA} />
        <Mono size={8.5} color={DIM}>HEADINGS · LABELS · SIGNATURE FORM · DATE &amp; CURRENCY FORMAT</Mono>
      </div>
      <Mono size={8.5} color={FAINT}>NAMES AND AMOUNTS ARE FORMATTED, NOT TRANSLATED</Mono>
    </div>
  );
}
function VizInbox() {
  return (
    <div style={vizWrap}>
      {[["groups@mareahotels.com", true], ["reception@mareahotels.com", false]].map(([m, on]) => (
        <div key={m} style={{ display: "flex", alignItems: "center", gap: 7, border: `1px solid ${on ? FUCHSIA_PALE : RULE}`, borderRadius: 5, padding: "5px 7px" }}>
          <Icon name="mail" size={10} color={on ? FUCHSIA : FAINT} />
          <Mono size={8.5} color={on ? INK : DIM} style={{ flex: 1 }}>{m}</Mono>
          <span style={{ width: 22, height: 12, borderRadius: 999, background: on ? FUCHSIA : "#ddd6cc", position: "relative" }}>
            <span style={{ position: "absolute", top: 1.5, left: on ? 11 : 1.5, width: 9, height: 9, borderRadius: "50%", background: "#fff" }} />
          </span>
        </div>
      ))}
      <div style={{ border: `1px dashed ${FUCHSIA_PALE}`, background: FUCHSIA_PALE + "28", borderRadius: 5, padding: "5px 7px" }}>
        <Mono size={8.5} color={FUCHSIA_DARK} style={{ fontWeight: 700 }}>DRAFT DEAL · HELVIA CONGRESS OCT 2026</Mono>
        <div style={{ display: "flex", gap: 4, marginTop: 4, flexWrap: "wrap" }}>
          {["320 PAX", "180 ROOMS", "2 DATE OPTIONS", "HALF BOARD"].map(t => <span key={t} style={chip(SURFACE, DIM, RULE)}>{t}</span>)}
        </div>
      </div>
      <Mono size={8.5} color={FAINT}>ALWAYS A DRAFT · IDEMPOTENT ON REPROCESS</Mono>
    </div>
  );
}
function VizOneRecord() {
  return (
    <div style={vizWrap}>
      <div style={{ border: `1px solid ${RULE}`, borderRadius: 5, padding: "6px 8px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ width: 16, height: 16, borderRadius: "50%", background: PAPER, border: `1px solid ${RULE}`, display: "grid", placeItems: "center" }}><Mono size={7.5} color={DIM} style={{ fontWeight: 700 }}>CR</Mono></span>
          <Mono size={9} color={INK} style={{ fontWeight: 700, flex: 1 }}>CLAUDIA ROSSI</Mono>
          <span style={chip(FUCHSIA_PALE + "60", FUCHSIA_DARK, FUCHSIA_PALE)}>TYPE · B2B</span>
        </div>
        <div style={{ display: "flex", gap: 3, marginTop: 6 }}>
          {["B2B", "BOOKINGS", "MEMORY", "LOYALTY"].map((t, i) => (
            <span key={t} style={{ flex: 1, textAlign: "center", padding: "3px 0", borderBottom: `1.5px solid ${i === 0 ? FUCHSIA : RULE}` }}>
              <Mono size={7.5} color={i === 0 ? FUCHSIA_DARK : FAINT} style={{ fontWeight: 700 }}>{t}</Mono>
            </span>
          ))}
        </div>
      </div>
      <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
        {["SEGMENTS", "CAMPAIGNS", "INBOX", "CONSENT", "CUSTOM FIELDS"].map(t => <span key={t} style={chip(PAPER, DIM, RULE)}>{t}</span>)}
      </div>
      <Mono size={8.5} color={FAINT}>ONE ROW · NO SECOND OBJECT TO SYNCHRONISE</Mono>
    </div>
  );
}
function VizPermissions() {
  const perms = ["VIEW", "ACCOUNTS", "DEALS", "ACTIVITIES", "EMAIL", "CONFIGURE", "EXPORT"];
  const roles = [["OWNER", 7], ["ADMIN", 7], ["MEMBER", 5], ["VIEWER", 1]];
  return (
    <div style={vizWrap}>
      <div style={{ display: "grid", gridTemplateColumns: "44px repeat(7,1fr)", gap: 2, alignItems: "center" }}>
        <span />
        {perms.map(p => <Mono key={p} size={6} color={FAINT} style={{ writingMode: "vertical-rl", transform: "rotate(180deg)", height: 34, fontWeight: 700 }}>{p}</Mono>)}
        {roles.map(([r, n]) => (
          <React.Fragment key={r}>
            <Mono size={8} color={DIM} style={{ fontWeight: 700 }}>{r}</Mono>
            {perms.map((p, i) => (
              <span key={p} style={{ height: 11, borderRadius: 2, background: i < n ? (n === 7 ? GREEN : FUCHSIA) : PAPER, border: `1px solid ${i < n ? "transparent" : RULE}`, opacity: i < n ? 0.85 : 1 }} />
            ))}
          </React.Fragment>
        ))}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 3, paddingTop: 5, borderTop: `1px dashed ${RULE}` }}>
        <Icon name="shield" size={10} color={FUCHSIA} />
        <Mono size={8.5} color={FUCHSIA} style={{ fontWeight: 700 }}>OWN-PORTFOLIO MODE ENFORCED SERVER-SIDE</Mono>
      </div>
    </div>
  );
}
function VizApi() {
  const rows = [["POST", "/v1/b2b/accounts", GREEN], ["POST", "/v1/b2b/accounts/bulk", GREEN], ["GET", "/v1/b2b/accounts?since=", VIOLET], ["POST", "/v1/b2b/deals", GREEN], ["POST", "/v1/b2b/contacts", GREEN]];
  return (
    <div style={vizWrap}>
      {rows.map(([m, p, c]) => (
        <div key={p} style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ ...chip(c + "16", c, c + "44"), width: 34, textAlign: "center" }}>{m}</span>
          <Mono size={8.5} color={DIM} style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p}</Mono>
        </div>
      ))}
      <div style={{ display: "flex", gap: 4, marginTop: 2 }}>
        <span style={chip(PAPER, INK, RULE)}>SCOPE b2b:read</span>
        <span style={chip(PAPER, INK, RULE)}>b2b:write</span>
      </div>
      <Mono size={8.5} color={FAINT}>IDEMPOTENT UPSERT BY EXTERNAL ID · TAX ID · IATA</Mono>
    </div>
  );
}

const B2B_PRIMITIVES = [
  { idx: "01", name: "TWO NATIVE FLOWS", title: "Corporate and MICE are not the same deal",
    body: "Corporate / FIT prices negotiated rates against a room-night commitment, an ADR and an allotment with a release window. Groups & Events prices event dates, pax, meeting space and board. Same board, different data model, and different gates.",
    bullets: ["Up to 20 active pipelines per group", "Stage names, colours and order are yours", "Five stage categories underneath, fixed by design"],
    quote: "A generalist CRM has one deal object. A hotel needs two.", viz: VizFlows },
  { idx: "02", name: "STAGE GATES", title: "Required fields at each boundary",
    body: "Every time a deal crosses a stage category forward, the server checks the fields that category demands. Not a client-side hint. The move is refused and the card stays where it was, with the missing fields named.",
    bullets: ["Checked on every crossed category, not just the target", "Won needs the promo code and accepted payment terms", "MICE needs the PMS locator and a signed proposal"],
    quote: "The deal does not become Won because someone dragged it.", viz: VizGates },
  { idx: "03", name: "NAMED APPROVALS", title: "The price does not move alone",
    body: "Any stage can require approval, with named people authorised to release it. Skipping over a gated stage does not evade it. Going backwards and marking a deal lost never need approval. The control sits where the commercial risk is.",
    bullets: ["Approver list per stage, set in the interface", "Client acceptance never crosses a gated stage", "Requests, releases and refusals all land in the audit log"],
    quote: "Revenue signs off, or the rate does not exist.", viz: VizApproval },
  { idx: "04", name: "ROTTING CLOCK", title: "Deals that wilt in plain sight",
    body: "Each stage carries its own idle threshold, 14 days in qualification, 21 in proposal, 30 in negotiation by default. Any logged activity resets the clock. The card shows days in stage and whether a next action exists at all.",
    bullets: ["Per-stage thresholds, configurable per pipeline", "Rotting-only filter on the board toolbar", "No next action is itself a visible state"],
    quote: "Pipeline hygiene as a property of the board, not a weekly nag.", viz: VizRotting },
  { idx: "05", name: "ATTRIBUTION ENGINE", title: "Four priorities, no invented links",
    body: "Reservations arrive from the PMS, the booking engine and the channel manager and are matched to the account that produced them: agency code, promo or rate code, engine agency id, and email domain as a suggestion only.",
    bullets: ["Highest priority wins · ties never auto-link", "A manual attribution is never overwritten", "Unknown codes with 5+ bookings become a create-account suggestion"],
    quote: "Production you can audit, not production the agency reports.", viz: VizAttribution },
  { idx: "06", name: "COMMISSION LEDGER", title: "Accrued without anyone typing",
    body: "When an attributed reservation checks out and a commission rate applies, one ledger entry is written, one, even if the job runs twice. Accrued, invoiced, settled: forward only, cancellable from the first two states.",
    bullets: ["Base is the net value when present, total otherwise", "Deal override, else account rate, else inherited rate", "Cancelled bookings reverse their own entry"],
    quote: "The commission argument ends with a filterable ledger.", viz: VizCommissions },
  { idx: "07", name: "ACCOUNT HIERARCHY", title: "Parent, child, consolidated",
    body: "Accounts nest up to three levels. Production consolidates upwards with a single toggle, and commercial terms are inherited from the nearest ancestor that has them. Configure the master agreement once; each national subsidiary follows.",
    bullets: ["Tax ID unique per group · duplicates refused", "Similar-name warning before saving", "Eight account types, two commercial profiles"],
    quote: "One master contract, nine subsidiaries, no re-typing.", viz: VizHierarchy },
  { idx: "08", name: "PROPOSALS & SIGNATURE", title: "A page the client signs, not an attachment",
    body: "A hosted, group-branded document with view tracking, handwritten acceptance and an identical PDF from the same template. The sales rep edits on top of the finished document, not in a form.",
    bullets: ["Six factory templates plus a block library", "Signature, timestamp, IP, user agent, content hash", "Reopening the link after signing shows the receipt"],
    quote: "Accepted, with proof of exactly what was accepted.", viz: VizProposal },
  { idx: "09", name: "TWELVE LANGUAGES", title: "One variant per recipient",
    body: "Mark the target languages and a full variant of the proposal is generated for each: content, headings, labels, the signature form, the acceptance notice, dates and currency format. The language the client signed in is recorded.",
    bullets: ["Each recipient's link carries their language", "Names and amounts are formatted, never translated", "The sales rep reviews every translation before it goes out"],
    quote: "A Swedish incentive house reads Swedish. Nobody re-typed it.", viz: VizLanguages },
  { idx: "10", name: "INBOX ROUTING", title: "The RFP that files itself",
    body: "Connected mailboxes sync both ways. Per mailbox, group and MICE enquiries can be routed to the B2B CRM: classified, structured, dates, alternatives, rooms, pax, event type, board, contact, and turned into a draft deal for review.",
    bullets: ["Groups mailbox routes · reception does not", "Always a draft, never a confirmed deal", "Client replies rethread themselves and close the follow-up task"],
    quote: "The enquiry becomes a deal before anyone opens the mailbox.", viz: VizInbox },
  { idx: "11", name: "ONE CONTACT RECORD", title: "The agent who is also a guest",
    body: "A B2B contact is a row of the same contacts table with type = b2b. Inbox, segments, campaigns, custom fields and consent all keep working, and a person can be an agency buyer and a returning guest without two records to reconcile.",
    bullets: ["A dynamic B2B segment is created when the module turns on", "Segment by account, type, profile, primary flag, manager", "Importers never downgrade a B2B contact to guest"],
    quote: "Two objects for one person is where CRM data goes to die.", viz: VizOneRecord },
  { idx: "12", name: "ACCESS & PORTFOLIO", title: "Seven permissions, three workspaces",
    body: "Each user gets both CRMs, guest-only or B2B-only. Seven granular permissions per role. Switch the group to own-portfolio mode and a rep sees their accounts, their deals and the unassigned pool, enforced in the server, not hidden in the UI.",
    bullets: ["View · accounts · deals · activities · email · configure · export", "Unassigned records work as a shared pool", "A record outside your portfolio answers as if it did not exist"],
    quote: "A groups rep has no business in the guest database.", viz: VizPermissions },
];

function B2BPrimitiveCard({ p }) {
  return (
    <div style={{ background: PAPER, border: `1px solid ${RULE}`, borderRadius: 8, padding: "20px 22px 22px", display: "flex", flexDirection: "column" }}>
      <div style={{ marginBottom: 18, minHeight: 104 }}><p.viz /></div>
      <Mono size={10} color={FUCHSIA} style={{ letterSpacing: "0.2em", fontWeight: 700, marginBottom: 7 }}>{p.idx} · {p.name}</Mono>
      <h3 style={{ fontFamily: SERIF, fontSize: 19, fontWeight: 500, color: INK, letterSpacing: "-0.015em", lineHeight: 1.18, margin: "0 0 9px" }}>{p.title}</h3>
      <p style={{ fontSize: 12.5, color: DIM, lineHeight: 1.5, margin: "0 0 11px", textWrap: "pretty" }}>{p.body}</p>
      <ul style={{ listStyle: "none", padding: 0, margin: "0 0 14px" }}>
        {p.bullets.map((b, i) => (
          <li key={i} style={{ display: "flex", alignItems: "flex-start", gap: 7, fontSize: 12, color: INK, fontWeight: 500, marginBottom: 5, lineHeight: 1.45 }}>
            <span style={{ display: "inline-block", width: 4, height: 4, borderRadius: "50%", background: FUCHSIA, marginTop: 7, flexShrink: 0 }} />
            <span>{b}</span>
          </li>
        ))}
      </ul>
      <div style={{ flex: 1 }} />
      <div style={{ fontFamily: SERIF, fontSize: 12, fontStyle: "italic", color: DIM, lineHeight: 1.5, paddingTop: 12, borderTop: `1px dashed ${RULE}` }}>"{p.quote}"</div>
    </div>
  );
}

function B2BPrimitives() {
  return (
    <div style={{ maxWidth: 1100, margin: "0 auto" }}>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
        {B2B_PRIMITIVES.map(p => <B2BPrimitiveCard key={p.idx} p={p} />)}
      </div>
    </div>
  );
}

Object.assign(window, { B2BPrimitives, B2B_PRIMITIVES, B2BPrimitiveCard, VizApi });
