// GuestMaker CRM B2B · the proposal studio: live template generator, editor, design panel, content library.

const MERGE_VALUES = {
  "contact.first_name": "Lennart", "contact.name": "Lennart Brauwe", "contact.email": "l.brauwe@nordwind.example",
  "account.name": "Nordwind Travel Group", "hotel.name": "Marea Sierra Blanca", "deal.name": "Nordwind Travel Group, 2027 master contract",
  "deal.value": "€148,000", "deal.dates": "1 Jan – 31 Dec 2027", "deal.rooms": "60 rooms",
  "deal.room_nights": "9,000 room nights", "valid_until": "30 Sep 2026", "quote.number": "B2B-40351DCF",
  "quote.date": "28 Jun 2026", "rep.name": "Adriana Vestri", "deal.board": "Bed & breakfast", "deal.pax": "320 attendees",
};
const MERGE_GROUPS = [
  ["CLIENT", [["Contact first name", "contact.first_name"], ["Contact full name", "contact.name"], ["Contact email", "contact.email"], ["Account / company", "account.name"]]],
  ["DEAL", [["Deal / reference name", "deal.name"], ["Deal value", "deal.value"], ["Valid-until date", "valid_until"]]],
  ["QUOTE", [["Quote / contract number", "quote.number"], ["Quote issue date", "quote.date"]]],
  ["STAY", [["Rooms required", "deal.rooms"], ["Room-nights", "deal.room_nights"], ["Board", "deal.board"], ["Property", "hotel.name"]]],
];

const PALETTES = [
  { id: "forest",   primary: "#143A33", accent: "#B5853A", page: "#F4F0E8", cards: "#FBF9F4", text: "#1E1B17" },
  { id: "ink",      primary: "#14161B", accent: "#C9A227", page: "#F5F3EE", cards: "#FFFFFF", text: "#16181D" },
  { id: "rust",     primary: "#7A3A21", accent: "#C4703A", page: "#F7F1EA", cards: "#FFFDFA", text: "#221610" },
  { id: "navy",     primary: "#1B2A4A", accent: "#7C5AC2", page: "#F2F4F8", cards: "#FFFFFF", text: "#141A26" },
  { id: "plum",     primary: "#4A1F3F", accent: "#B5853A", page: "#F7F1F4", cards: "#FFFDFC", text: "#1F141B" },
  { id: "cocoa",    primary: "#3E2B21", accent: "#A98055", page: "#F6F1EA", cards: "#FFFDF9", text: "#1E1712" },
  { id: "olive",    primary: "#33391F", accent: "#8C9A4B", page: "#F4F4EA", cards: "#FDFDF7", text: "#1B1D12" },
  { id: "charcoal", primary: "#26282B", accent: "#8A8F98", page: "#F3F4F5", cards: "#FFFFFF", text: "#14161A" },
];
const HEAD_FONTS = [
  { id: "default", label: "Template default", css: "'Source Serif 4', Georgia, serif", note: "Editorial serif · shipped default" },
  { id: "playfair", label: "Playfair Display", css: "'Playfair Display', Georgia, serif", note: "High-contrast, editorial" },
  { id: "cormorant", label: "Cormorant Garamond", css: "'Cormorant Garamond', Georgia, serif", note: "Delicate, luxury" },
  { id: "eb", label: "EB Garamond", css: "'EB Garamond', Georgia, serif", note: "Classic old-style" },
  { id: "baskerville", label: "Libre Baskerville", css: "'Libre Baskerville', Georgia, serif", note: "Sturdy, readable" },
];
const BODY_FONTS = [
  { id: "default", label: "Template default", css: "'Inter', system-ui, sans-serif", note: "Neutral UI sans" },
  { id: "eb", label: "EB Garamond", css: "'EB Garamond', Georgia, serif", note: "Classic old-style" },
  { id: "baskerville", label: "Libre Baskerville", css: "'Libre Baskerville', Georgia, serif", note: "Sturdy, readable" },
  { id: "cormorant", label: "Cormorant Garamond", css: "'Cormorant Garamond', Georgia, serif", note: "Delicate, luxury" },
];

const TEMPLATES = [
  { id: "corp", name: "Corporate negotiated rate", tag: "CORPORATE", palette: "forest", head: "playfair",
    desc: "Annual business-travel rate, negotiated cancellation, rate parity, room-night commitment.",
    kicker: "GROUP PROPOSAL", title: "A season of rooms, held for {{account.name}}.",
    note: "Dear {{contact.first_name}}, thank you for the opportunity to formalise a seasonal allotment between {{account.name}} and {{hotel.name}}. The terms below set out a guaranteed block of rooms, held for you across {{deal.dates}}, that your team can sell with complete confidence.\n\nAn allotment removes the friction of case-by-case requests: your contracted inventory is ours to protect and yours to fill, released back to us only on the agreed schedule. We have kept the structure deliberately simple, guaranteed availability, transparent release terms, and rate parity you can rely on.",
    figures: [["Room nights", "9,000"], ["Properties", "5 hotels"], ["Allotment", "60 rooms"], ["Release", "21 days"]],
    rooms: [["Double room · BB", "€104", "3,200 RN"], ["Superior double · BB", "€118", "2,400 RN"], ["Junior suite · HB", "€162", "1,100 RN"]],
    incl: ["Guaranteed allotment across five properties", "Rate parity with all public channels", "21-day automatic release schedule", "Named account manager and quarterly review", "Consolidated monthly invoicing", "Complimentary upgrade allowance, subject to availability"],
    terms: "Commission 12% settled monthly, net 30. Cancellation 14 days before arrival with a 20% penalty on the cancelled portion. Allotment released automatically 21 days before arrival. Rates net of VAT and city tax.",
    total: "€148,000" },
  { id: "mice", name: "MICE event, conference & gala", tag: "MICE", palette: "ink", head: "cormorant",
    desc: "Full event proposal: room block, meeting space, F&B and AV for a congress with gala dinner.",
    kicker: "EVENT PROPOSAL", title: "A congress in Barcelona, run for {{account.name}}.",
    note: "Dear {{contact.first_name}}, thank you for inviting us to quote your medical congress. Below is a complete plan for {{deal.pax}}: the room block, the plenary and breakout spaces, catering across the three days, and the gala dinner on the closing night.\n\nEverything in this proposal is held provisionally under your name. A single signature converts the hold into a confirmed block, and your dedicated event manager takes it from there.",
    figures: [["Attendees", "320"], ["Rooms per night", "180"], ["Meeting rooms", "1 + 4"], ["Event days", "4"]],
    rooms: [["Double room · HB", "€138", "540 RN"], ["Single occupancy · HB", "€118", "260 RN"], ["Speaker suite · HB", "€210", "24 RN"]],
    incl: ["Plenary auditorium for 320 with stage and lectern", "Four breakout rooms with AV and technician", "Two coffee breaks per day, lunch on all three days", "Gala dinner with wine pairing on the closing night", "Dedicated on-site event manager", "Signage, registration desk and Wi-Fi for all attendees"],
    terms: "30% deposit on signature, 40% at 60 days, balance at 14 days. Cancellation staged from 90 days. Meeting space held exclusively for the contracted dates. Final rooming list required 21 days before arrival.",
    total: "€42,000" },
  { id: "leisure", name: "Leisure group series", tag: "LEISURE", palette: "rust", head: "eb",
    desc: "Seasonal tour series for an agency, held rooms, one rate, transfers and a single invoice.",
    kicker: "SERIES PROPOSAL", title: "A season of departures, priced once for {{account.name}}.",
    note: "Dear {{contact.first_name}}, here is the series proposal for {{account.name}} across {{deal.dates}}. One rate, one release schedule and one invoice for the whole season, so your product team can publish with confidence and your operations team never has to chase a confirmation.\n\nWe have built the series around your published departure days and left room to grow the block mid-season if it sells ahead of forecast.",
    figures: [["Departures", "26"], ["Rooms held", "34"], ["Room nights", "1,400"], ["Board", "Half board"]],
    rooms: [["Double room · HB", "€96", "980 RN"], ["Family room · HB", "€142", "420 RN"]],
    incl: ["Weekly guaranteed departures from May to October", "Airport transfers on arrival and departure", "Half board with one drink per meal", "Free tour-leader place per 20 paying guests", "Single seasonal invoice", "Mid-season block increase on request"],
    terms: "Commission 10% settled monthly, net 45. Rooming lists 21 days before each departure. Cancellation of an individual departure at 30 days without penalty.",
    total: "€62,000" },
  { id: "incentive", name: "Incentive trip, reward the best", tag: "MICE", palette: "plum", head: "baskerville",
    desc: "Premium reward trip for top performers, suite stay, signature experiences, exclusive venue.",
    kicker: "INCENTIVE PROPOSAL", title: "Four days worth winning, arranged for {{account.name}}.",
    note: "Dear {{contact.first_name}}, an incentive trip is judged on the two hours your guests remember, not on the rate card. Below is a four-day programme built around a private beach venue, a chef's table for the leadership group, and a closing dinner your winners will talk about next year.\n\nEverything is exclusive-use where it matters and flexible where it does not.",
    figures: [["Guests", "60"], ["Suites", "48"], ["Nights", "4"], ["Exclusive venues", "2"]],
    rooms: [["Junior suite · AI", "€268", "144 RN"], ["Sea-view suite · AI", "€312", "48 RN"]],
    incl: ["Exclusive use of the beach venue for the closing night", "Chef's table for the leadership group", "All-inclusive board with premium bar", "Welcome gift in room, personalised per guest", "Private transfers in executive vehicles", "Dedicated concierge for the full programme"],
    terms: "50% deposit on signature, balance at 30 days. Exclusive-use venues confirmed on deposit. Cancellation 30 days before arrival with a 25% penalty.",
    total: "€64,000" },
];
const COVER_NOTES = [
  ["Warm first enquiry", "Inviting opener for a brand-new prospect.", "Dear {{contact.first_name}}, thank you for getting in touch. It is a pleasure to hear from {{account.name}}. Here is what we can hold for you, and what it would cost."],
  ["Returning client", "Welcomes a repeat account back warmly.", "Dear {{contact.first_name}}, it is good to be working with {{account.name}} again. Building on last season, here is the structure we propose for {{deal.dates}}."],
  ["Formal corporate", "Precise, procurement-ready tone for corporate buyers.", "Dear {{contact.first_name}}, further to your request for proposal, please find below the negotiated terms for {{account.name}} covering {{deal.dates}}, quoted under reference {{quote.number}}."],
];
const INCLUSION_SETS = [
  ["Standard group", "Six core inclusions for a straightforward group stay.", ["Guaranteed room block", "Breakfast included", "Group check-in desk", "One free place per 20 paying guests", "Luggage handling", "Wi-Fi throughout"]],
  ["MICE & events", "Conference-ready inclusions, space, AV, catering, event manager.", ["Plenary room with AV", "Two breakout rooms", "Two coffee breaks per day", "Working lunch", "On-site event manager", "Registration desk and signage"]],
  ["Leisure half-board", "Leisure-group inclusions with half-board dining.", ["Half board with one drink per meal", "Airport transfers", "Welcome cocktail", "Tour-leader free place", "Pool and spa access", "Late check-out for the group"]],
];
const TERM_BLOCKS = [
  ["Flexible group, cancellation & payment", "Deposit plus staged cancellation with a flexible release window.", "20% deposit on signature. Staged cancellation from 60 days. Rooming list 14 days before arrival. Flexible release of unsold rooms up to 21 days out."],
  ["Non-refundable group", "Best-price, paid-on-signature, non-refundable terms.", "Full payment on signature. Non-refundable and non-transferable. Name changes accepted up to 7 days before arrival at no charge."],
  ["Contracted allotment release", "Allotment terms with a 21-day automatic release schedule.", "Allotment released automatically 21 days before arrival. Rate parity guaranteed across all channels. Commission settled monthly, net 30."],
];
const ROOM_CARDS = [
  ["Guaranteed seasonal allotment", "Double room · BB", "€104", "3,200 RN"],
  ["Congress single occupancy", "Single occupancy · HB", "€118", "260 RN"],
  ["Incentive sea-view suite", "Sea-view suite · AI", "€312", "48 RN"],
];
const ALL_SECTIONS = [
  ["cover", "Cover"], ["note", "Cover note"], ["figures", "Key figures"], ["rooms", "Accommodation"],
  ["incl", "Inclusions"], ["invest", "Investment"], ["terms", "Terms"], ["accept", "Acceptance"],
];

// ── merge-field aware text ──
function MergeText({ text, filled, design, onDark }) {
  const parts = String(text).split(/(\{\{[a-z_.]+\}\})/g);
  return (
    <>
      {parts.map((p, i) => {
        const m = p.match(/^\{\{([a-z_.]+)\}\}$/);
        if (!m) return <React.Fragment key={i}>{p}</React.Fragment>;
        if (filled) return <b key={i} style={{ fontWeight: 600 }}>{MERGE_VALUES[m[1]] || p}</b>;
        return <span key={i} style={{
          background: onDark ? "rgba(255,255,255,0.16)" : design.accent + "22",
          color: onDark ? "#fff" : design.primary,
          borderRadius: 3, padding: "0 3px", fontWeight: 600, whiteSpace: "nowrap",
        }}>{p}</span>;
      })}
    </>
  );
}

// ── right-panel controls ──
function PanelBlock({ icon, title, note, children }) {
  return (
    <div style={{ padding: "15px 15px 17px", borderBottom: `1px solid ${RULE}` }}>
      <div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: note ? 5 : 10 }}>
        <Icon name={icon} size={12} color={FUCHSIA} />
        <span style={{ fontSize: 13, fontWeight: 600, letterSpacing: "-0.01em" }}>{title}</span>
      </div>
      {note && <p style={{ fontSize: 11, color: DIM, lineHeight: 1.45, margin: "0 0 11px" }}>{note}</p>}
      {children}
    </div>
  );
}
function PanelLabel({ children }) {
  return <Mono size={8.5} color={FAINT} style={{ letterSpacing: "0.14em", fontWeight: 700, display: "block", marginBottom: 5, textDecoration: "underline", textDecorationColor: RULE, textUnderlineOffset: 3 }}>{children}</Mono>;
}
function Seg({ options, value, onChange }) {
  return (
    <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
      {options.map(o => {
        const v = Array.isArray(o) ? o[0] : o, l = Array.isArray(o) ? o[1] : o;
        const on = value === v;
        return (
          <button key={v} onClick={() => onChange(v)} style={{
            padding: "4px 9px", borderRadius: 6, cursor: "pointer", fontFamily: SANS, fontSize: 10.5, fontWeight: 600,
            border: `1px solid ${on ? FUCHSIA : RULE}`, background: on ? FUCHSIA_PALE + "55" : SURFACE, color: on ? FUCHSIA_DARK : DIM,
          }}>{l}</button>
        );
      })}
    </div>
  );
}
function PanelSlider({ value, min, max, step = 1, onChange }) {
  return (
    <input type="range" min={min} max={max} step={step} value={value} onChange={e => onChange(Number(e.target.value))}
      style={{ width: "100%", accentColor: FUCHSIA, height: 16 }} />
  );
}
function ColourRow({ label, value }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 0", borderTop: `1px solid ${RULE_SOFT}` }}>
      <span style={{ width: 14, height: 14, borderRadius: 3, background: value, border: `1px solid ${RULE}` }} />
      <span style={{ fontSize: 11.5, flex: 1 }}>{label}</span>
      <Mono size={9.5} color={FAINT}>{value.toUpperCase()}</Mono>
    </div>
  );
}
function FontList({ fonts, value, onChange, sample }) {
  return (
    <div style={{ border: `1px solid ${RULE}`, borderRadius: 7, overflow: "hidden" }}>
      {fonts.map(f => {
        const on = value === f.id;
        return (
          <button key={f.id} onClick={() => onChange(f.id)} style={{
            display: "block", width: "100%", textAlign: "left", padding: "7px 10px", cursor: "pointer",
            background: on ? FUCHSIA_PALE + "45" : SURFACE, border: "none", borderBottom: `1px solid ${RULE_SOFT}`,
          }}>
            <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <span style={{ fontFamily: f.css, fontSize: 13.5, color: INK, flex: 1 }}>{f.id === "default" ? f.label : sample}</span>
              {on && <Icon name="check" size={11} color={FUCHSIA_DARK} strokeWidth={3} />}
            </span>
            <Mono size={8.5} color={FAINT}>{f.id === "default" ? "" : f.label + " · "}{f.note}</Mono>
          </button>
        );
      })}
    </div>
  );
}

// ── the document ──
function ProposalDoc({ doc, design, filled, sections, onMove, onRemove, onAddAt, editing }) {
  const head = HEAD_FONTS.find(f => f.id === design.head).css;
  const body = BODY_FONTS.find(f => f.id === design.body).css;
  const s = design.typeScale / 100;
  const radius = design.corners === "Sharp" ? 0 : design.corners === "Round" ? 14 : 6;
  const pad = design.spacing === "Compact" ? 26 : design.spacing === "Spacious" ? 54 : 38;
  const heroH = design.heroHeight === "Compact" ? 210 : design.heroHeight === "Tall" ? 400 : 300;
  const t = TEMPLATES.find(x => x.id === doc.template);
  const band = i => design.bands === "Alternating" && i % 2 === 1 ? design.page : design.cards;
  const inclCols = design.inclusions === "Auto" ? 2 : design.inclusions === "Horizontal rows" ? 1 : design.inclusions === "2 columns" ? 2 : 3;

  const SectionShell = ({ id, i, children, tone }) => (
    <div style={{ position: "relative", background: tone, padding: `${pad * 0.7}px ${pad}px` }}>
      {editing && (
        <div style={{ position: "absolute", top: 6, right: 8, display: "flex", gap: 3, opacity: 0.55 }}>
          <button onClick={() => onMove(i, -1)} style={docBtn}><Icon name="arrow-u" size={10} color={DIM} /></button>
          <button onClick={() => onMove(i, 1)} style={docBtn}><Icon name="arrow-d" size={10} color={DIM} /></button>
          {id !== "cover" && <button onClick={() => onRemove(id)} style={docBtn}><Icon name="x" size={10} color={DIM} /></button>}
        </div>
      )}
      {children}
    </div>
  );
  const docBtn = { width: 18, height: 18, borderRadius: 4, border: `1px solid ${RULE}`, background: SURFACE, cursor: "pointer", display: "grid", placeItems: "center", padding: 0 };
  const H = ({ children, size = 22 }) => (
    <h4 style={{ fontFamily: head, fontSize: size * s, fontWeight: design.headWeight, color: design.primary, letterSpacing: `${design.tracking / 100}em`, lineHeight: 1.16, margin: "0 0 12px" }}>{children}</h4>
  );
  const Kicker = ({ children }) => <Mono size={9} color={design.accent} style={{ letterSpacing: "0.2em", fontWeight: 700, display: "block", marginBottom: 9 }}>{children}</Mono>;

  return (
    <div style={{ maxWidth: design.pageWidth, margin: "0 auto", background: design.cards, color: design.text, fontFamily: body, borderRadius: radius, overflow: "hidden", boxShadow: "0 30px 70px -45px rgba(0,0,0,0.5)", border: `1px solid ${RULE}` }}>
      {sections.map((id, i) => {
        if (id === "cover") return (
          <SectionShell key={id} id={id} i={i} tone={design.primary}>
            <div style={{
              minHeight: heroH, display: "flex", flexDirection: "column",
              justifyContent: design.heroAlign === "Centred" ? "center" : "flex-end",
              alignItems: design.heroAlign === "Centred" ? "center" : "flex-start",
              textAlign: design.heroAlign === "Centred" ? "center" : "left",
              position: "relative", margin: `-${pad * 0.7}px -${pad}px`, padding: `${pad}px`,
              background: design.heroPhoto
                ? `linear-gradient(${design.primary}${Math.round(design.heroTint * 2.55).toString(16).padStart(2, "0")}, ${design.primary}${Math.round(design.heroTint * 2.55).toString(16).padStart(2, "0")}), radial-gradient(120% 120% at 20% 0%, #6f7f6a, #2b3a33)`
                : `linear-gradient(135deg, ${design.primary} 0%, ${design.primary} 45%, ${design.primary}dd 100%)`,
            }}>
              <div style={{ position: "absolute", top: pad * 0.8, left: pad, right: pad, display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 16 }}>
                <span style={{ width: 30, height: 30, borderRadius: 8, background: "#fff", display: "grid", placeItems: "center" }}>
                  <Icon name="building" size={15} color={design.primary} />
                </span>
                <div style={{ textAlign: "right" }}>
                  <Mono size={9} color={design.accent} style={{ letterSpacing: "0.2em", fontWeight: 700 }}>{t.kicker}</Mono>
                  {[["Ref", doc.ref], ["Issued", doc.issued], ["Valid until", doc.validUntil]].map(([k, v]) => (
                    <div key={k}><Mono size={9.5} color="#ffffffcc">{k} · {v}</Mono></div>
                  ))}
                </div>
              </div>
              <div style={{ maxWidth: "22ch" }}>
                <Mono size={9} color={design.accent} style={{ letterSpacing: "0.2em", fontWeight: 700 }}>
                  <MergeText text="{{hotel.name}}" filled={filled} design={design} onDark /> · MÁLAGA
                </Mono>
                <h3 style={{ fontFamily: head, fontSize: 34 * s, fontWeight: design.headWeight, color: "#fff", letterSpacing: `${design.tracking / 100 - 0.02}em`, lineHeight: 1.1, margin: "12px 0 12px" }}>
                  <MergeText text={doc.title} filled={filled} design={design} onDark />
                </h3>
                <p style={{ fontSize: 13 * s, color: "#ffffffcc", margin: 0 }}>
                  Prepared for <b style={{ color: "#fff" }}><MergeText text="{{account.name}}" filled={filled} design={design} onDark /></b>
                </p>
                {editing && (
                  <button onClick={() => onAddAt("photo")} style={{
                    marginTop: 16, display: "inline-flex", alignItems: "center", gap: 7, height: 30, padding: "0 12px",
                    borderRadius: 8, border: "1px solid rgba(255,255,255,0.35)", background: "rgba(255,255,255,0.1)",
                    color: "#fff", fontFamily: SANS, fontSize: 11.5, fontWeight: 600, cursor: "pointer",
                  }}><Icon name="file" size={11} color="#fff" />{design.heroPhoto ? "Replace the hero photo" : "Add a hero photo"}</button>
                )}
              </div>
            </div>
          </SectionShell>
        );
        if (id === "note") return (
          <SectionShell key={id} id={id} i={i} tone={band(i)}>
            <p style={{ fontSize: 13.5 * s, fontWeight: 600, lineHeight: design.lineHeight, margin: "0 0 10px" }}>
              <MergeText text="Dear {{contact.first_name}}," filled={filled} design={design} />
            </p>
            {doc.note.split("\n\n").map((para, k) => (
              <p key={k} style={{ fontSize: 13.5 * s, color: design.text, opacity: 0.88, lineHeight: design.lineHeight, margin: "0 0 12px", textWrap: "pretty" }}>
                <MergeText text={para} filled={filled} design={design} />
              </p>
            ))}
            <p style={{ fontSize: 13.5 * s, fontWeight: 700, margin: "18px 0 0" }}>
              <MergeText text="{{rep.name}}" filled={filled} design={design} />
            </p>
            <Mono size={10} color={DIM}>Director of Sales</Mono>
          </SectionShell>
        );
        if (id === "figures") return (
          <SectionShell key={id} id={id} i={i} tone={band(i)}>
            <Kicker>AT A GLANCE</Kicker>
            <div style={{ display: "grid", gridTemplateColumns: `repeat(${design.figures}, 1fr)`, gap: 10 }}>
              {t.figures.map(([k, v]) => (
                <div key={k} style={{ border: `1px solid ${design.primary}22`, borderRadius: radius, padding: "11px 12px", background: design.page }}>
                  <Mono size={8.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 700 }}>{k.toUpperCase()}</Mono>
                  <div style={{ fontFamily: head, fontSize: 22 * s, fontWeight: design.headWeight, color: design.primary, marginTop: 3 }}>{v}</div>
                </div>
              ))}
            </div>
          </SectionShell>
        );
        if (id === "rooms") return (
          <SectionShell key={id} id={id} i={i} tone={band(i)}>
            <Kicker>ACCOMMODATION</Kicker>
            <H>Rooms held for your group</H>
            <div style={{ display: design.accommodation === "2-up grid" ? "grid" : "flex", gridTemplateColumns: "1fr 1fr", flexDirection: "column", gap: 9 }}>
              {doc.rooms.map(([n, p, rn], k) => (
                <div key={n + k} style={{
                  display: design.accommodation === "Photo above" ? "block" : "flex",
                  alignItems: "center", gap: 12, border: `1px solid ${design.primary}1f`, borderRadius: radius,
                  padding: design.accommodation === "Photo above" ? 0 : "10px 12px", background: design.page, overflow: "hidden",
                }}>
                  <div style={{
                    width: design.accommodation === "Photo above" ? "100%" : 62,
                    height: design.accommodation === "Photo above" ? 78 : 46, flexShrink: 0, borderRadius: design.accommodation === "Photo above" ? 0 : radius,
                    background: `linear-gradient(135deg, ${design.primary}22, ${design.accent}33)`,
                  }} />
                  <div style={{ flex: 1, padding: design.accommodation === "Photo above" ? "10px 12px 0" : 0 }}>
                    <div style={{ fontSize: 12.5 * s, fontWeight: 600 }}>{n}</div>
                    <Mono size={9.5} color={DIM}>{rn} · per room per night</Mono>
                  </div>
                  <div style={{ fontFamily: head, fontSize: 18 * s, fontWeight: design.headWeight, color: design.primary, padding: design.accommodation === "Photo above" ? "0 12px 10px" : 0 }}>{p}</div>
                </div>
              ))}
            </div>
          </SectionShell>
        );
        if (id === "incl") return (
          <SectionShell key={id} id={id} i={i} tone={band(i)}>
            <Kicker>WHAT IS INCLUDED</Kicker>
            <div style={{ display: "grid", gridTemplateColumns: `repeat(${inclCols}, 1fr)`, gap: "7px 16px" }}>
              {doc.incl.map((x, k) => (
                <div key={x + k} style={{ display: "flex", gap: 8, alignItems: "flex-start" }}>
                  <Icon name="check" size={11} color={design.accent} strokeWidth={3} style={{ marginTop: 4 }} />
                  <span style={{ fontSize: 12.5 * s, lineHeight: design.lineHeight }}>{x}</span>
                </div>
              ))}
            </div>
          </SectionShell>
        );
        if (id === "invest") return (
          <SectionShell key={id} id={id} i={i} tone={design.primary}>
            <div style={{ display: "flex", alignItems: "flex-end", gap: 16, flexWrap: "wrap" }}>
              <div style={{ flex: 1, minWidth: 180 }}>
                <Mono size={9} color={design.accent} style={{ letterSpacing: "0.2em", fontWeight: 700 }}>TOTAL INVESTMENT</Mono>
                <div style={{ fontFamily: head, fontSize: 34 * s, fontWeight: design.headWeight, color: "#fff", marginTop: 6 }}>
                  <MergeText text={doc.total} filled={filled} design={design} onDark />
                </div>
              </div>
              <div style={{ textAlign: "right" }}>
                {t.figures.slice(0, 2).map(([k, v]) => (
                  <div key={k}><Mono size={10} color="#ffffffbb">{k.toUpperCase()} · {v}</Mono></div>
                ))}
                <Mono size={10} color="#ffffffbb">NET OF VAT AND CITY TAX</Mono>
              </div>
            </div>
          </SectionShell>
        );
        if (id === "terms") return (
          <SectionShell key={id} id={id} i={i} tone={band(i)}>
            <Kicker>TERMS &amp; CONDITIONS</Kicker>
            <p style={{ fontSize: 12 * s, lineHeight: design.lineHeight, opacity: 0.85, margin: 0, textWrap: "pretty" }}>{doc.terms}</p>
          </SectionShell>
        );
        if (id === "accept") return (
          <SectionShell key={id} id={id} i={i} tone={band(i)}>
            <Kicker>ACCEPTANCE</Kicker>
            <H size={19}>Accept and sign</H>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
              <div>
                <Mono size={8.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 700 }}>LEGAL NAME</Mono>
                <div style={{ height: 30, borderBottom: `1px solid ${design.primary}44`, marginTop: 6, fontSize: 12.5, display: "flex", alignItems: "flex-end", paddingBottom: 4 }}>
                  {filled ? MERGE_VALUES["contact.name"] : ""}
                </div>
              </div>
              <div>
                <Mono size={8.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 700 }}>SIGNATURE</Mono>
                <div style={{ height: 30, borderBottom: `1px solid ${design.primary}44`, marginTop: 6 }} />
              </div>
            </div>
            <Mono size={9.5} color={DIM} style={{ display: "block", marginTop: 12, lineHeight: 1.6 }}>
              ACCEPTANCE RECORDS SIGNATURE · TIMESTAMP · IP · USER AGENT · CONTENT HASH · LANGUAGE
            </Mono>
          </SectionShell>
        );
        return null;
      })}
    </div>
  );
}

// ── content library modal ──
function LibraryModal({ tab, setTab, onApplyTemplate, onInsert, saved, onSave, onClose }) {
  const [name, setName] = useState("");
  const TABS = ["Templates", "Cover notes", "Inclusion sets", "Terms", "Room cards"];
  const savedList = saved[tab] || [];
  const rows = tab === "Templates" ? TEMPLATES.map(t => [t.name, t.desc, t.tag, () => onApplyTemplate(t.id)])
    : tab === "Cover notes" ? COVER_NOTES.map(([n, d, v]) => [n, d, null, () => onInsert("note", v)])
    : tab === "Inclusion sets" ? INCLUSION_SETS.map(([n, d, v]) => [n, d, null, () => onInsert("incl", v)])
    : tab === "Terms" ? TERM_BLOCKS.map(([n, d, v]) => [n, d, null, () => onInsert("terms", v)])
    : ROOM_CARDS.map(([n, r, p, rn]) => [n, `${r} · ${p} · ${rn}`, null, () => onInsert("room", [r, p, rn])]);
  const saveLabel = { "Templates": "SAVE THIS PROPOSAL AS A REUSABLE TEMPLATE", "Cover notes": "SAVE THE CURRENT COVER NOTE TO REUSE LATER", "Inclusion sets": "SAVE THE CURRENT INCLUSIONS SET TO REUSE LATER", "Terms": "SAVE THE CURRENT TERMS TO REUSE LATER", "Room cards": "SAVE THE CURRENT ROOM CARD TO REUSE LATER" }[tab];

  return (
    <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(20,16,20,0.55)", zIndex: 50, display: "grid", placeItems: "center", padding: 20 }}>
      <div onClick={e => e.stopPropagation()} style={{ width: 640, maxWidth: "100%", maxHeight: "92%", overflow: "auto", background: SURFACE, borderRadius: 14, border: `1px solid ${RULE}`, boxShadow: "0 50px 100px -40px rgba(0,0,0,0.6)", padding: "20px 22px 18px" }}>
        <div style={{ display: "flex", alignItems: "flex-start", gap: 10 }}>
          <div style={{ flex: 1 }}>
            <h3 style={{ fontFamily: SERIF, fontSize: 20, fontWeight: 500, margin: 0, letterSpacing: "-0.015em", display: "flex", alignItems: "center", gap: 8 }}>
              <Icon name="layers" size={15} color={FUCHSIA} /> Content library
            </h3>
            <p style={{ fontSize: 12.5, color: DIM, lineHeight: 1.5, margin: "6px 0 0", maxWidth: "62ch" }}>
              Save reusable blocks and templates, then drop them into any proposal. Merge fields inside them auto-fill per deal.
            </p>
          </div>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", padding: 3 }}><Icon name="x" size={14} color={FAINT} /></button>
        </div>
        <div style={{ display: "flex", gap: 6, margin: "16px 0 14px", flexWrap: "wrap" }}>
          {TABS.map(t => (
            <button key={t} onClick={() => setTab(t)} style={{
              height: 28, padding: "0 12px", borderRadius: 999, cursor: "pointer", fontFamily: SANS, fontSize: 11.5, fontWeight: 600,
              border: `1px solid ${tab === t ? FUCHSIA : RULE}`, background: tab === t ? FUCHSIA_PALE + "60" : SURFACE, color: tab === t ? FUCHSIA_DARK : DIM,
            }}>{t}</button>
          ))}
        </div>
        <div style={{ border: `1px solid ${RULE}`, borderRadius: 9, padding: "12px 13px", marginBottom: 14 }}>
          <Mono size={9} color={FAINT} style={{ letterSpacing: "0.14em", fontWeight: 700 }}>{saveLabel}</Mono>
          <div style={{ display: "flex", gap: 8, marginTop: 9 }}>
            <input value={name} onChange={e => setName(e.target.value)} placeholder={`Name this ${tab.toLowerCase().replace(/s$/, "")}...`}
              style={{ flex: 1, height: 32, borderRadius: 7, border: `1px solid ${RULE}`, padding: "0 10px", fontFamily: SANS, fontSize: 12, background: PAPER }} />
            <button onClick={() => { if (name.trim()) { onSave(tab, name.trim()); setName(""); } }} style={{
              display: "inline-flex", alignItems: "center", gap: 6, height: 32, padding: "0 14px", borderRadius: 7,
              border: "none", background: INK, color: "#fff", fontFamily: SANS, fontSize: 12, fontWeight: 600, cursor: "pointer",
            }}><Icon name="file" size={11} color="#fff" />Save</button>
          </div>
        </div>
        <Mono size={9} color={FUCHSIA} style={{ letterSpacing: "0.14em", fontWeight: 700, display: "flex", alignItems: "center", gap: 6, marginBottom: 8 }}>
          <Icon name="spark" size={10} color={FUCHSIA} /> GUESTMAKER LIBRARY
        </Mono>
        <div style={{ display: "grid", gap: 7 }}>
          {rows.map(([n, d, tag, act]) => (
            <div key={n} style={{ display: "flex", alignItems: "center", gap: 10, background: PAPER, border: `1px solid ${RULE}`, borderRadius: 8, padding: "10px 12px" }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
                  <span style={{ fontSize: 12.5, fontWeight: 600 }}>{n}</span>
                  {tag && <span style={{ background: SURFACE, border: `1px solid ${RULE}`, borderRadius: 4, padding: "1px 5px", fontFamily: MONO, fontSize: 8.5, fontWeight: 700, color: DIM }}>{tag}</span>}
                </div>
                <Mono size={9.5} color={DIM} style={{ display: "block", marginTop: 2 }}>{d}</Mono>
              </div>
              {tab === "Templates" && <ChromeButton icon="eye" onClick={act}>Preview</ChromeButton>}
              <ChromeButton icon={tab === "Templates" ? "check" : "plus"} onClick={act}>{tab === "Templates" ? "Apply" : "Insert"}</ChromeButton>
            </div>
          ))}
        </div>
        <Mono size={9} color={FAINT} style={{ letterSpacing: "0.14em", fontWeight: 700, display: "block", margin: "16px 0 8px" }}>YOUR LIBRARY</Mono>
        {savedList.length ? (
          <div style={{ display: "grid", gap: 7 }}>
            {savedList.map(n => (
              <div key={n} style={{ display: "flex", alignItems: "center", gap: 10, border: `1px solid ${FUCHSIA_PALE}`, background: FUCHSIA_PALE + "25", borderRadius: 8, padding: "9px 12px" }}>
                <Icon name="file" size={12} color={FUCHSIA_DARK} />
                <span style={{ fontSize: 12.5, fontWeight: 600, flex: 1 }}>{n}</span>
                <Mono size={9.5} color={FUCHSIA_DARK}>SAVED JUST NOW · GROUP-WIDE</Mono>
              </div>
            ))}
          </div>
        ) : (
          <div style={{ textAlign: "center", padding: "14px 0" }}>
            <Mono size={11} color={FAINT}>No saved {tab.toLowerCase()} yet. Save the current one above.</Mono>
          </div>
        )}
        <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 16 }}>
          <button onClick={onClose} style={{ height: 32, padding: "0 16px", borderRadius: 7, border: "none", background: INK, color: "#fff", fontFamily: SANS, fontSize: 12, fontWeight: 600, cursor: "pointer" }}>Done</button>
        </div>
      </div>
    </div>
  );
}

// ── the studio ──
function ProposalStudio() {
  const t0 = TEMPLATES[0];
  const [doc, setDoc] = useState({
    template: t0.id, ref: "B2B-40351DCF", issued: "28 Jun 2026", validUntil: "12 Jul 2026",
    title: t0.title, note: t0.note, rooms: t0.rooms, incl: t0.incl, terms: t0.terms, total: t0.total,
  });
  const [design, setDesign] = useState({
    ...PALETTES[0], palette: "forest", head: "playfair", body: "default", typeScale: 100, headWeight: 600,
    lineHeight: 1.6, tracking: 0, heroPhoto: false, heroTint: 72, heroHeight: "Standard", heroAlign: "Left",
    inclusions: "Auto", accommodation: "Photo beside", figures: 4, bands: "Off", corners: "Soft",
    spacing: "Comfortable", pageWidth: 640,
  });
  const [sections, setSections] = useState(["cover", "note", "figures", "rooms", "incl", "invest", "terms", "accept"]);
  const [filled, setFilled] = useState(false);
  const [panel, setPanel] = useState("design");
  const [panelOpen, setPanelOpen] = useState(true);
  const [library, setLibrary] = useState(null);
  const [fields, setFields] = useState(false);
  const [addOpen, setAddOpen] = useState(false);
  const [saved, setSaved] = useState({});
  const [status, setStatus] = useState("Draft");
  const [toast, setToast] = useState(null);
  const [defaultSet, setDefaultSet] = useState(false);
  useEffect(() => { if (!toast) return; const x = setTimeout(() => setToast(null), 2400); return () => clearTimeout(x); }, [toast]);
  const set = (k, v) => setDesign(d => ({ ...d, [k]: v }));

  function applyPalette(p) { setDesign(d => ({ ...d, ...p, palette: p.id })); }
  function applyTemplate(id) {
    const t = TEMPLATES.find(x => x.id === id);
    setDoc(d => ({ ...d, template: id, title: t.title, note: t.note, rooms: t.rooms, incl: t.incl, terms: t.terms, total: t.total }));
    const p = PALETTES.find(x => x.id === t.palette);
    setDesign(d => ({ ...d, ...p, palette: p.id, head: t.head }));
    setLibrary(null); setToast(`Template applied · ${t.name}`);
  }
  function insertBlock(kind, value) {
    if (kind === "note") setDoc(d => ({ ...d, note: value + "\n\n" + d.note }));
    if (kind === "incl") setDoc(d => ({ ...d, incl: value }));
    if (kind === "terms") setDoc(d => ({ ...d, terms: value }));
    if (kind === "room") setDoc(d => ({ ...d, rooms: [...d.rooms, value] }));
    setLibrary(null); setToast("Block inserted · merge fields inside it stay dynamic");
  }
  function moveSection(i, dir) {
    setSections(s => { const j = i + dir; if (j < 0 || j >= s.length) return s; const c = [...s]; [c[i], c[j]] = [c[j], c[i]]; return c; });
  }
  const missing = ALL_SECTIONS.filter(([id]) => !sections.includes(id));

  const toolBtn = (label, icon, onClick, extra) => (
    <button onClick={onClick} style={{
      display: "inline-flex", alignItems: "center", gap: 6, height: 28, padding: "0 10px", borderRadius: 7,
      border: `1px solid ${extra ? FUCHSIA : "transparent"}`, background: extra ? FUCHSIA_PALE + "55" : "transparent",
      color: extra ? FUCHSIA_DARK : INK, fontFamily: SANS, fontSize: 11.5, fontWeight: 600, cursor: "pointer", whiteSpace: "nowrap",
    }}><Icon name={icon} size={12} color={extra ? FUCHSIA_DARK : DIM} />{label}</button>
  );

  return (
    <div style={{ position: "relative", background: "#f2f0ec", fontFamily: SANS }}>
      {/* admin bar */}
      <div style={{ display: "flex", alignItems: "center", gap: 9, background: "#131316", color: "#EDEBE8", padding: "7px 14px" }}>
        <Dot color={FUCHSIA} size={6} />
        <Mono size={9} color="#EDEBE8" style={{ letterSpacing: "0.16em", fontWeight: 700 }}>ADMIN</Mono>
        <span style={{ width: 1, height: 12, background: "#3a3a3f" }} />
        <span style={{ fontSize: 11.5, fontWeight: 600 }}>Marea Hotels &amp; Apartments</span>
        <Icon name="chevron-d" size={10} color="#8a8a90" />
        <div style={{ flex: 1 }} />
        <Mono size={9.5} color="#8a8a90">← BACK TO ADMIN</Mono>
      </div>
      {/* toolbar */}
      <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "9px 14px", background: SURFACE, borderBottom: `1px solid ${RULE}`, flexWrap: "wrap" }}>
        <Icon name="chevron-l" size={12} color={FAINT} />
        <Mono size={10} color={DIM}>Pipeline</Mono>
        <span style={{ fontSize: 12, fontWeight: 700 }}>Deal</span>
        <Mono size={10} color={DIM}>· Proposal</Mono>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 5, marginLeft: 4 }}>
          <Icon name="check" size={11} color={GREEN} strokeWidth={3} /><Mono size={9.5} color={DIM}>All changes saved</Mono>
        </span>
        <span style={{ width: 1, height: 16, background: RULE, margin: "0 4px" }} />
        {toolBtn("Insert field", "spark", () => { setFields(f => !f); setAddOpen(false); }, fields)}
        {toolBtn("Library", "layers", () => setLibrary("Templates"))}
        {toolBtn("Save as template", "file", () => setLibrary("Templates"))}
        <div style={{ flex: 1 }} />
        {toolBtn(filled ? "Editing fields" : "Preview filled", "eye", () => { setFilled(f => !f); setToast(filled ? "Back to the editable document" : "Merge fields resolved with this deal's data"); }, filled)}
        {toolBtn("", "settings", () => setPanel("design"))}
        {toolBtn("Publish", "globe", () => { setStatus("Published"); setToast("Published · hosted on proposals.mareahotels.com"); })}
        <button onClick={() => { setStatus("Sent"); setPanel("track"); setToast("Sent to Lennart Brauwe · EN + DE variants generated"); }} style={{
          display: "inline-flex", alignItems: "center", gap: 7, height: 30, padding: "0 13px", borderRadius: 8,
          border: "none", background: FUCHSIA_DEEP, color: "#fff", fontFamily: SANS, fontSize: 12, fontWeight: 600, cursor: "pointer", boxShadow: `0 8px 18px -10px ${FUCHSIA_DEEP}`,
        }}><Icon name="send" size={12} color="#fff" />Send proposal</button>
        <button onClick={() => setPanelOpen(o => !o)} style={{ background: "none", border: "none", cursor: "pointer", padding: 3 }}>
          <Icon name="board" size={14} color={panelOpen ? FUCHSIA : FAINT} />
        </button>
      </div>

      <div style={{ display: "flex", alignItems: "stretch", minHeight: 620 }}>
        <style>{`
          .ps-canvas{scrollbar-width:thin;scrollbar-color:${FUCHSIA}66 transparent}
          .ps-canvas::-webkit-scrollbar{width:9px}
          .ps-canvas::-webkit-scrollbar-track{background:transparent}
          .ps-canvas::-webkit-scrollbar-thumb{background:${FUCHSIA}55;border-radius:5px;border:2px solid transparent;background-clip:padding-box}
          .ps-canvas::-webkit-scrollbar-thumb:hover{background:${FUCHSIA}aa;background-clip:padding-box}
        `}</style>
        {/* canvas */}
        <div className="ps-canvas" style={{ flex: 1, minWidth: 0, padding: "22px 22px 30px", position: "relative", height: 660, overflowY: "auto", overflowX: "hidden" }}>
          {fields && (
            <div style={{
              position: "absolute", top: 4, left: 200, zIndex: 30, width: 300, maxHeight: 420, overflowY: "auto",
              background: SURFACE, border: `1px solid ${RULE}`, borderRadius: 10, boxShadow: "0 30px 60px -30px rgba(0,0,0,0.45)", padding: "11px 12px",
            }}>
              <Mono size={9.5} color={DIM} style={{ lineHeight: 1.5, display: "block", marginBottom: 9 }}>
                Auto-fills per deal when you preview, publish or send.
              </Mono>
              {MERGE_GROUPS.map(([g, items]) => (
                <div key={g} style={{ marginBottom: 10 }}>
                  <Mono size={8.5} color={FAINT} style={{ letterSpacing: "0.16em", fontWeight: 700 }}>{g}</Mono>
                  <div style={{ display: "grid", gap: 3, marginTop: 5 }}>
                    {items.map(([label, token]) => (
                      <button key={token} onClick={() => { setDoc(d => ({ ...d, note: d.note + ` {{${token}}}` })); setToast(`Inserted {{${token}}} into the cover note`); }}
                        style={{ display: "flex", alignItems: "center", gap: 8, background: "none", border: "none", cursor: "pointer", padding: "4px 2px", textAlign: "left" }}>
                        <span style={{ fontSize: 11.5, flex: 1 }}>{label}</span>
                        <span style={{ background: PAPER, border: `1px solid ${RULE}`, borderRadius: 4, padding: "1px 5px", fontFamily: MONO, fontSize: 9, color: DIM }}>{`{{${token}}}`}</span>
                      </button>
                    ))}
                  </div>
                </div>
              ))}
            </div>
          )}
          <div style={{ display: "flex", justifyContent: "center", marginBottom: 12 }}>
            <button onClick={() => setAddOpen(o => !o)} style={{
              display: "inline-flex", alignItems: "center", gap: 6, height: 26, padding: "0 12px", borderRadius: 999,
              border: `1px dashed ${RULE}`, background: "transparent", color: DIM, fontFamily: SANS, fontSize: 11, fontWeight: 600, cursor: "pointer",
            }}><Icon name="plus" size={10} color={FAINT} />Add a section</button>
          </div>
          {addOpen && (
            <div style={{ display: "flex", gap: 6, justifyContent: "center", flexWrap: "wrap", marginBottom: 12 }}>
              {missing.length ? missing.map(([id, l]) => (
                <button key={id} onClick={() => { setSections(s => [...s, id]); setAddOpen(false); setToast(`${l} section added`); }} style={{
                  height: 26, padding: "0 11px", borderRadius: 999, cursor: "pointer", fontFamily: SANS, fontSize: 11, fontWeight: 600,
                  border: `1px solid ${FUCHSIA_PALE}`, background: FUCHSIA_PALE + "50", color: FUCHSIA_DARK,
                }}>{l}</button>
              )) : <Mono size={10} color={FAINT}>Every section is already in the document.</Mono>}
            </div>
          )}
          <ProposalDoc doc={doc} design={design} filled={filled} sections={sections}
            editing={!filled} onMove={moveSection} onRemove={id => setSections(s => s.filter(x => x !== id))}
            onAddAt={() => { set("heroPhoto", !design.heroPhoto); setToast(design.heroPhoto ? "Hero photo removed · gradient fallback" : "Hero photo set · tinted with your primary colour"); }} />
          <div style={{ display: "flex", justifyContent: "center", gap: 8, marginTop: 14, flexWrap: "wrap" }}>
            <Pill tone={status === "Draft" ? "tbc" : "now"}>{status}</Pill>
            <Mono size={9.5} color={FAINT}>{sections.length} SECTIONS · {doc.ref} · {filled ? "SHOWING THIS DEAL'S DATA" : "SHOWING MERGE FIELDS"}</Mono>
          </div>
        </div>

        {/* design panel */}
        {panelOpen && (
          <div style={{ width: 306, flexShrink: 0, borderLeft: `1px solid ${RULE}`, background: SURFACE, height: 660, overflowY: "auto" }}>
            <div style={{ display: "flex", gap: 4, padding: "10px 12px", borderBottom: `1px solid ${RULE}`, position: "sticky", top: 0, background: SURFACE, zIndex: 5 }}>
              {[["design", "Design", "spark"], ["track", "Track", "chart"], ["history", "History", "clock"]].map(([k, l, ic]) => (
                <button key={k} onClick={() => setPanel(k)} style={{
                  flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5, height: 27, borderRadius: 999,
                  cursor: "pointer", fontFamily: SANS, fontSize: 11, fontWeight: 600,
                  border: `1px solid ${panel === k ? FUCHSIA_PALE : "transparent"}`, background: panel === k ? FUCHSIA_PALE + "55" : "transparent",
                  color: panel === k ? FUCHSIA_DARK : DIM,
                }}><Icon name={ic} size={11} color={panel === k ? FUCHSIA_DARK : FAINT} />{l}</button>
              ))}
            </div>

            {panel === "design" && (
              <>
                <PanelBlock icon="spark" title="Colours" note="Drives the hero, the totals bar, the accept panel and every accent on the page.">
                  <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 10 }}>
                    {PALETTES.map(p => (
                      <button key={p.id} onClick={() => applyPalette(p)} title={p.id} style={{
                        width: 26, height: 26, borderRadius: "50%", cursor: "pointer", padding: 0, overflow: "hidden",
                        border: `1px solid ${RULE}`, background: `linear-gradient(135deg, ${p.primary} 50%, ${p.accent} 50%)`,
                        boxShadow: design.palette === p.id ? `0 0 0 2px ${SURFACE}, 0 0 0 3.5px ${FUCHSIA}` : "none",
                      }} />
                    ))}
                  </div>
                  <ColourRow label="Primary" value={design.primary} />
                  <ColourRow label="Accent" value={design.accent} />
                  <ColourRow label="Page" value={design.page} />
                  <ColourRow label="Cards" value={design.cards} />
                  <ColourRow label="Text" value={design.text} />
                </PanelBlock>

                <PanelBlock icon="pen" title="Typography" note="Google fonts, applied to the whole document the moment you pick one.">
                  <PanelLabel>HEADLINES</PanelLabel>
                  <FontList fonts={HEAD_FONTS} value={design.head} onChange={v => set("head", v)} sample="An offsite, arranged" />
                  <div style={{ height: 12 }} />
                  <PanelLabel>BODY TEXT</PanelLabel>
                  <FontList fonts={BODY_FONTS} value={design.body} onChange={v => set("body", v)} sample="Rooms held for your group" />
                  <div style={{ height: 12 }} />
                  <PanelLabel>TYPE SIZE · {design.typeScale}%</PanelLabel>
                  <PanelSlider value={design.typeScale} min={85} max={120} onChange={v => set("typeScale", v)} />
                  <PanelLabel>HEADING WEIGHT</PanelLabel>
                  <Seg options={[[400, "Regular"], [500, "Medium"], [600, "Semibold"], [700, "Bold"]]} value={design.headWeight} onChange={v => set("headWeight", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>LINE SPACING · {design.lineHeight.toFixed(2)}</PanelLabel>
                  <PanelSlider value={design.lineHeight} min={1.3} max={2} step={0.05} onChange={v => set("lineHeight", v)} />
                  <PanelLabel>HEADING TRACKING · {design.tracking}</PanelLabel>
                  <PanelSlider value={design.tracking} min={-3} max={3} step={0.5} onChange={v => set("tracking", v)} />
                </PanelBlock>

                <PanelBlock icon="file" title="Hero" note="The first thing your client sees. A photo of the property, under a tint of your primary colour.">
                  <button onClick={() => set("heroPhoto", !design.heroPhoto)} style={{
                    display: "flex", alignItems: "center", justifyContent: "center", gap: 7, width: "100%", height: 30, borderRadius: 7,
                    border: `1px solid ${design.heroPhoto ? FUCHSIA : RULE}`, background: design.heroPhoto ? FUCHSIA_PALE + "45" : SURFACE,
                    color: design.heroPhoto ? FUCHSIA_DARK : INK, fontFamily: SANS, fontSize: 11.5, fontWeight: 600, cursor: "pointer",
                  }}><Icon name="file" size={11} color={design.heroPhoto ? FUCHSIA_DARK : DIM} />{design.heroPhoto ? "Hero photo applied" : "Choose a hero photo"}</button>
                  <Mono size={9} color={FAINT} style={{ display: "block", margin: "7px 0 11px", lineHeight: 1.5 }}>
                    No photo? The hero falls back to a gradient built from your primary colour.
                  </Mono>
                  <PanelLabel>PHOTO TINT · {design.heroTint}%</PanelLabel>
                  <PanelSlider value={design.heroTint} min={20} max={95} onChange={v => set("heroTint", v)} />
                  <PanelLabel>HEIGHT</PanelLabel>
                  <Seg options={["Compact", "Standard", "Tall"]} value={design.heroHeight} onChange={v => set("heroHeight", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>ALIGNMENT</PanelLabel>
                  <Seg options={["Left", "Centred"]} value={design.heroAlign} onChange={v => set("heroAlign", v)} />
                </PanelBlock>

                <PanelBlock icon="board" title="Layout" note="Shape and rhythm. These are the controls that fix a cramped imported template.">
                  <PanelLabel>INCLUSIONS</PanelLabel>
                  <Seg options={["Auto", "Horizontal rows", "2 columns", "3 columns"]} value={design.inclusions} onChange={v => set("inclusions", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>ACCOMMODATION</PanelLabel>
                  <Seg options={["Photo beside", "2-up grid", "Photo above"]} value={design.accommodation} onChange={v => set("accommodation", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>KEY FIGURES PER ROW</PanelLabel>
                  <Seg options={[[2, "2"], [3, "3"], [4, "4"]]} value={design.figures} onChange={v => set("figures", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>SECTION BANDS</PanelLabel>
                  <Seg options={["Off", "Alternating"]} value={design.bands} onChange={v => set("bands", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>CORNERS</PanelLabel>
                  <Seg options={["Sharp", "Soft", "Round"]} value={design.corners} onChange={v => set("corners", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>SPACING</PanelLabel>
                  <Seg options={["Compact", "Comfortable", "Spacious"]} value={design.spacing} onChange={v => set("spacing", v)} />
                  <div style={{ height: 10 }} />
                  <PanelLabel>PAGE WIDTH · {Math.round(design.pageWidth / 16)}REM</PanelLabel>
                  <PanelSlider value={design.pageWidth} min={520} max={760} step={8} onChange={v => set("pageWidth", v)} />
                </PanelBlock>

                <PanelBlock icon="building" title="Your house style" note="Set this design once and every new proposal your team starts will already look like it.">
                  <button onClick={() => { setDefaultSet(true); setToast("House style saved · new proposals start from this design"); }} style={{
                    display: "flex", alignItems: "center", justifyContent: "center", gap: 7, width: "100%", height: 30, borderRadius: 7,
                    border: `1px solid ${defaultSet ? GREEN : RULE}`, background: defaultSet ? "#F1F8F3" : SURFACE,
                    color: defaultSet ? "#256B45" : INK, fontFamily: SANS, fontSize: 11.5, fontWeight: 600, cursor: "pointer",
                  }}><Icon name={defaultSet ? "check" : "building"} size={11} color={defaultSet ? "#256B45" : DIM} />{defaultSet ? "This is our default design" : "Make this our default design"}</button>
                  <button onClick={() => { setDesign(d => ({ ...d, ...PALETTES[0], palette: "forest", head: "playfair", body: "default", typeScale: 100, headWeight: 600, lineHeight: 1.6, tracking: 0, inclusions: "Auto", accommodation: "Photo beside", figures: 4, bands: "Off", corners: "Soft", spacing: "Comfortable", pageWidth: 640 })); setToast("Design reset · the content is untouched"); }}
                    style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 7, width: "100%", height: 28, marginTop: 8, borderRadius: 7, border: `1px dashed ${RULE}`, background: "transparent", color: DIM, fontFamily: SANS, fontSize: 11, fontWeight: 600, cursor: "pointer" }}>
                    <Icon name="refresh" size={11} color={FAINT} />Reset this proposal's design
                  </button>
                  <Mono size={9} color={FAINT} style={{ display: "block", marginTop: 8, lineHeight: 1.5 }}>
                    Content is untouched, only colours, fonts and layout reset.
                  </Mono>
                </PanelBlock>
              </>
            )}

            {panel === "track" && (
              <div style={{ padding: "14px 15px" }}>
                <div style={{ display: "grid", gap: 9, marginBottom: 14 }}>
                  {[["Status", status], ["Views", status === "Sent" ? "9" : "0"], ["Languages", "EN · DE"], ["Recipient", "Lennart Brauwe"]].map(([k, v]) => (
                    <div key={k} style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <Mono size={9} color={FAINT} style={{ letterSpacing: "0.14em", fontWeight: 700, width: 84 }}>{k.toUpperCase()}</Mono>
                      <span style={{ fontSize: 12, fontWeight: 600 }}>{v}</span>
                    </div>
                  ))}
                </div>
                {status === "Sent" ? [["Opened · Hannover, DE", "12 Aug 14:02 · Chrome 141"], ["Opened · Palma, ES", "11 Aug 09:41 · Safari iOS"], ["Link sent · EN + DE", "10 Aug 17:03 · by Adriana Vestri"]].map(([a, b]) => (
                  <TimelineItem key={a} tone={FUCHSIA} icon="eye" title={a} meta={b} />
                )) : <Mono size={11} color={FAINT}>Nothing tracked yet, send the proposal and every open, per language, lands here.</Mono>}
              </div>
            )}

            {panel === "history" && (
              <div style={{ padding: "14px 15px" }}>
                {[["Design changed · palette and headline font", "just now · you", FUCHSIA],
                  ["Template applied · " + TEMPLATES.find(x => x.id === doc.template).name, "just now · you", VIOLET],
                  ["Proposal created from deal", "28 Jun 2026 · Adriana Vestri", PLUM]].map(([a, b, c]) => (
                  <TimelineItem key={a} tone={c} icon="clock" title={a} meta={b} />
                ))}
                <Mono size={9.5} color={FAINT} style={{ lineHeight: 1.6, display: "block", marginTop: 6 }}>
                  Every version is kept. Restoring one never changes what a client already signed.
                </Mono>
              </div>
            )}
          </div>
        )}
      </div>

      {library && (
        <LibraryModal tab={library} setTab={setLibrary} onClose={() => setLibrary(null)}
          onApplyTemplate={applyTemplate} onInsert={insertBlock} saved={saved}
          onSave={(tab, name) => { setSaved(s => ({ ...s, [tab]: [...(s[tab] || []), name] })); setToast(`Saved to your library · ${name}`); }} />
      )}
      <Toast msg={toast} tone={FUCHSIA} />
    </div>
  );
}

Object.assign(window, { ProposalStudio, TEMPLATES, MERGE_VALUES });
