// Materials Tab — gathered & carved campaign-level material stockpile
// + Forge Tab — assemble crafter, assistant, items, materials, wishes

const MAT_KIND_META = {
  gathered: { label: 'Gathered', icon: '🌿', color: '#1e6b3c', desc: 'Raw resources — ore, herbs, hide, bone' },
  carved:   { label: 'Carved',   icon: '⚒', color: '#a14a1a', desc: 'Refined components — planks, ingots, dust' },
};

// ── Materials Tab ────────────────────────────────────────────────────────────
const MaterialsTab = ({ campaign, isDM }) => {
  const [mats, setMats] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [editing, setEditing] = React.useState(null);
  const [adding, setAdding] = React.useState(null); // 'gathered' | 'carved'
  const [carving, setCarving] = React.useState(false);
  const [filter, setFilter] = React.useState('all'); // all | gathered | carved
  const [q, setQ] = React.useState('');

  const refresh = React.useCallback(async () => {
    setLoading(true);
    try { setMats((await api.campaigns.materials.list(campaign.id)) || []); }
    catch (err) { console.error('[MaterialsTab] load failed:', err); }
    finally { setLoading(false); }
  }, [campaign.id]);

  React.useEffect(() => { refresh(); }, [refresh]);

  const visible = mats.filter(m => {
    if (filter !== 'all' && m.kind !== filter) return false;
    if (q && !(`${m.name} ${m.source||''} ${(m.properties||[]).map(p=>p.key+' '+p.value).join(' ')}`).toLowerCase().includes(q.toLowerCase())) return false;
    return true;
  });

  // Editor save: PATCH if editing existing, POST if new (no API id yet).
  const handleSave = async (updated) => {
    const payload = {
      kind: updated.kind, name: updated.name, quantity: Number(updated.quantity) || 1,
      unit: updated.unit || '', source: updated.source || '', notes: updated.notes || '',
      properties: (updated.properties || []).filter(p => p && p.key),
      isPublic: updated.isPublic !== false,
    };
    try {
      if (editing) await api.campaigns.materials.update(campaign.id, editing.id, payload);
      else         await api.campaigns.materials.create(campaign.id, payload);
      await refresh();
    } catch (err) {
      console.error('[MaterialsTab] save failed:', err);
      window.dialog.alert('Failed to save material: ' + (err.message || 'unknown error'));
    }
    setEditing(null); setAdding(null);
  };

  const handleDelete = async () => {
    if (!editing) return;
    if (!(await window.dialog.confirm({ title:'Delete material?', message:'Delete material?', danger:true, confirmLabel:'Delete' }))) return;
    try {
      await api.campaigns.materials.delete(campaign.id, editing.id);
      await refresh();
    } catch (err) {
      console.error('[MaterialsTab] delete failed:', err);
      window.dialog.alert('Failed to delete material: ' + (err.message || 'unknown error'));
    }
    setEditing(null);
  };

  // Carve modal returns suggested materials; POST each, then refresh.
  const handleCarveCommit = async (newMats) => {
    try {
      for (const m of newMats) {
        await api.campaigns.materials.create(campaign.id, {
          kind: m.kind, name: m.name, quantity: Number(m.quantity) || 1,
          unit: m.unit || '', source: m.source || '', notes: m.notes || '',
          properties: (m.properties || []).filter(p => p && p.key),
        });
      }
      await refresh();
    } catch (err) {
      console.error('[MaterialsTab] carve commit failed:', err);
      window.dialog.alert('Failed to commit materials: ' + (err.message || 'unknown error'));
    }
    setCarving(false);
  };

  return (
    <div style={matStyles.tab}>
      <div style={matStyles.toolbar}>
        <div style={{display:'flex',gap:6}}>
          {['all','gathered','carved'].map(f => (
            <button key={f}
              style={{...matStyles.filterChip, ...(filter===f?matStyles.filterChipActive:{})}}
              onClick={()=>setFilter(f)}>
              {f==='all' ? 'All' : MAT_KIND_META[f].icon + ' ' + MAT_KIND_META[f].label}
            </button>
          ))}
        </div>
        <input style={matStyles.search} placeholder="Search by name, source, property…" value={q} onChange={e=>setQ(e.target.value)}/>
        {isDM && (
          <div style={{display:'flex',gap:6,marginLeft:'auto'}}>
            <button style={{...matStyles.btnGhost,color:'#a14a1a',borderColor:'rgba(161,74,26,0.4)'}} onClick={()=>setCarving(true)}>🪓 Carve Monster</button>
            <button style={matStyles.btnGhost} onClick={()=>setAdding('gathered')}>+ Gathered</button>
            <button style={matStyles.btnGhost} onClick={()=>setAdding('carved')}>+ Carved</button>
          </div>
        )}
      </div>

      <div style={matStyles.grid}>
        {loading && mats.length === 0 && (
          <div style={{gridColumn:'1/-1',padding:40,textAlign:'center',color:'#6b6966',fontSize:13}}>Loading materials…</div>
        )}
        {visible.map(m => (
          <MaterialCard key={m.id} mat={m} isDM={isDM} onEdit={()=>setEditing(m)}/>
        ))}
        {!loading && !visible.length && (
          <div style={{gridColumn:'1/-1',padding:40,textAlign:'center',color:'#4a4a52'}}>
            No materials match.
          </div>
        )}
      </div>

      {editing && (
        <MaterialEditor
          mat={editing}
          onClose={()=>setEditing(null)}
          onSave={handleSave}
          onDelete={handleDelete}
        />
      )}
      {adding && (
        <MaterialEditor
          mat={{ kind:adding, name:'', quantity:1, unit:'', source:'', notes:'', properties:[] }}
          onClose={()=>setAdding(null)}
          onSave={handleSave}
        />
      )}
      {carving && (
        <CarveMonsterModal
          campaign={campaign}
          onClose={()=>setCarving(false)}
          onCommit={handleCarveCommit}
        />
      )}
    </div>
  );
};

const MaterialCard = ({ mat, isDM, onEdit }) => {
  const meta = MAT_KIND_META[mat.kind] || MAT_KIND_META.gathered;
  return (
    <div style={{...matStyles.card, borderLeftColor: meta.color}} onClick={isDM ? onEdit : undefined}>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'flex-start',gap:8}}>
        <div style={{flex:1,minWidth:0}}>
          <div style={matStyles.cardName}>{mat.name || '(unnamed)'}</div>
          <div style={matStyles.cardSub}>
            <span style={{color:meta.color}}>{meta.icon} {meta.label}</span>
            <span style={{color:'#4a4a52',margin:'0 6px'}}>·</span>
            <span style={{color:'#c9a227'}}>{mat.quantity} {mat.unit}</span>
          </div>
        </div>
        {isDM && <button style={matStyles.editLink} onClick={e=>{e.stopPropagation();onEdit();}}>✎</button>}
      </div>
      {mat.source && <div style={{fontSize:11,color:'#9a9793',marginTop:6,fontStyle:'italic'}}>from {mat.source}</div>}
      {mat.notes && <div style={{fontSize:12,color:'#c5c3c0',marginTop:6,lineHeight:1.5}}>{mat.notes}</div>}
      {(mat.properties||[]).length > 0 && (
        <div style={{display:'flex',flexDirection:'column',gap:4,marginTop:10,paddingTop:10,borderTop:'1px solid #2a2a32'}}>
          {(mat.properties||[]).map((p,i) => (
            <div key={i} style={{display:'flex',gap:8,fontSize:11}}>
              <span style={{color:'#c9a227',fontWeight:700,textTransform:'uppercase',letterSpacing:'0.04em',minWidth:90}}>{p.key}</span>
              <span style={{color:'#c5c3c0',flex:1}}>{p.value}</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

const MaterialEditor = ({ mat, onSave, onClose, onDelete }) => {
  const [m, setM] = React.useState({ ...mat, properties: [...(mat.properties||[])] });
  const setField = (k,v) => setM(prev => ({...prev, [k]: v}));
  const setProp = (i, k, v) => setM(prev => {
    const props = [...prev.properties];
    props[i] = { ...props[i], [k]: v };
    return { ...prev, properties: props };
  });
  const addProp = () => setM(prev => ({...prev, properties:[...prev.properties, {key:'', value:''}]}));
  const removeProp = (i) => setM(prev => ({...prev, properties: prev.properties.filter((_,j)=>j!==i)}));

  return (
    <div style={matStyles.scrim} {...scrimDismiss(onClose)}>
      <div style={matStyles.modal} onClick={e=>e.stopPropagation()}>
        <div style={matStyles.modalHeader}>
          <div style={{fontFamily:"'Cinzel',serif",fontSize:16,fontWeight:700,color:'#e8e6e3'}}>
            {MAT_KIND_META[m.kind].icon} {mat.name ? 'Edit' : 'New'} {MAT_KIND_META[m.kind].label} Material
          </div>
          <button style={matStyles.closeX} onClick={onClose}>✕</button>
        </div>
        <div style={{padding:18,display:'grid',gridTemplateColumns:'1fr 1fr',gap:14}}>
          <div>
            <div style={matStyles.label}>Name<Req/></div>
            <input style={matStyles.input} value={m.name} onChange={e=>setField('name',e.target.value)}/>
          </div>
          <div style={{display:'grid',gridTemplateColumns:'80px 1fr',gap:10}}>
            <div>
              <div style={matStyles.label}>Qty</div>
              <input type="number" style={matStyles.input} value={m.quantity} onChange={e=>setField('quantity',+e.target.value||0)}/>
            </div>
            <div>
              <div style={matStyles.label}>Unit</div>
              <input style={matStyles.input} placeholder="vials, planks, ingots…" value={m.unit} onChange={e=>setField('unit',e.target.value)}/>
            </div>
          </div>
          <div style={{gridColumn:'1/-1'}}>
            <div style={matStyles.label}>Source</div>
            <input style={matStyles.input} placeholder="Where it came from…" value={m.source} onChange={e=>setField('source',e.target.value)}/>
          </div>
          <div style={{gridColumn:'1/-1'}}>
            <div style={matStyles.label}>Notes</div>
            <textarea style={{...matStyles.input,minHeight:60,resize:'vertical'}} value={m.notes} onChange={e=>setField('notes',e.target.value)}/>
          </div>
          <div style={{gridColumn:'1/-1'}}>
            <label style={{display:'flex',alignItems:'center',gap:8,fontSize:12,color:'#9a9793',cursor:'pointer'}}>
              <input type="checkbox" checked={m.isPublic !== false} onChange={e=>setField('isPublic', e.target.checked)} />
              <span><strong style={{color:'#e8e6e3'}}>Public</strong> — visible in the cross-campaign Homebrew Repository (uncheck to keep private to this campaign).</span>
            </label>
          </div>
          <div style={{gridColumn:'1/-1'}}>
            <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:6}}>
              <div style={matStyles.label}>Properties (passed to the Forge LLM)</div>
              <button style={matStyles.btnGhost} onClick={addProp}>+ Property</button>
            </div>
            <div style={{display:'flex',flexDirection:'column',gap:6}}>
              {m.properties.map((p,i) => (
                <div key={i} style={{display:'grid',gridTemplateColumns:'120px 1fr 28px',gap:6}}>
                  <input style={matStyles.input} placeholder="key" value={p.key} onChange={e=>setProp(i,'key',e.target.value)}/>
                  <input style={matStyles.input} placeholder="value" value={p.value} onChange={e=>setProp(i,'value',e.target.value)}/>
                  <button style={{...matStyles.btnGhost,padding:'4px 8px',color:'#f87171'}} onClick={()=>removeProp(i)}>✕</button>
                </div>
              ))}
              {!m.properties.length && (
                <div style={{fontSize:11,color:'#4a4a52',fontStyle:'italic'}}>No properties yet — add things like "rarity: rare", "magical: stores 1 spell slot", "workability: requires masterwork tools"…</div>
              )}
            </div>
          </div>
        </div>
        <div style={matStyles.modalFooter}>
          {onDelete ? <button style={{...matStyles.btnGhost,color:'#f87171'}} onClick={onDelete}>Delete</button> : <span/>}
          <div style={{display:'flex',gap:8}}>
            <button style={matStyles.btnGhost} onClick={onClose}>Cancel</button>
            <button style={matStyles.btnPrimary} onClick={()=>onSave(m)}>Save</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ── Carve Monster modal — Claude turns a monster description into materials ──
const CarveMonsterModal = ({ campaign, onClose, onCommit }) => {
  const [desc, setDesc] = React.useState('');
  const [proposed, setProposed] = React.useState(null); // [{name, kind, quantity, unit, source, notes, properties:[{key,value}]}]
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  const generate = async () => {
    if (desc.trim().length < 8) return;
    setLoading(true); setError(null); setProposed(null);
    const prompt = `You are a D&D 5e bestiary expert assisting a DM in the campaign "${campaign.name}".

The party has just defeated a creature and wants to harvest it. Based on the description, propose 3 to 6 distinct materials that could plausibly be carved/extracted from the body. Each material should be specific (not "monster part"), have a believable yield, and include 1-3 useful crafting properties.

CREATURE DESCRIPTION:
"""${desc.trim()}"""

Reply ONLY with JSON in this exact shape — no fences, no commentary:
{
  "materials": [
    {
      "name": "string — specific name, e.g. 'Frost Wyrm Scales'",
      "kind": "gathered" or "carved",
      "quantity": <integer>,
      "unit": "scales | vials | pounds | bones | etc",
      "source": "the creature, in 2-5 words",
      "notes": "1-2 sentence flavour line about the material",
      "properties": [
        { "key": "RARITY",      "value": "uncommon" },
        { "key": "RESISTANCE",  "value": "cold" }
      ]
    }
  ]
}

Use "gathered" for raw bits (scales, blood, hide, organs) and "carved" for refined or processed parts (ground horn, rendered fat, polished tooth). Properties should be useful for crafting magic items in 5e.`;
    try {
      const raw = await window.claude.complete(prompt);
      const cleaned = raw.trim().replace(/^```(?:json)?\s*/,'').replace(/\s*```$/,'');
      const json = JSON.parse(cleaned);
      const list = (json.materials||[]).map(m => ({
        ...m,
        kind: m.kind === 'carved' ? 'carved' : 'gathered',
        quantity: Math.max(1, Math.round(m.quantity||1)),
        properties: (m.properties||[]).filter(p => p && p.key),
        _enabled: true,
      }));
      if (!list.length) throw new Error('No materials returned.');
      setProposed(list);
    } catch (e) {
      setError('Carve failed: '+(e.message||String(e)));
    }
    setLoading(false);
  };

  const togglePick = (i) => setProposed(prev => prev.map((m,j)=> j===i ? {...m,_enabled:!m._enabled} : m));
  const updateField = (i, k, v) => setProposed(prev => prev.map((m,j)=> j===i ? {...m,[k]:v} : m));

  const commit = () => {
    const picked = proposed.filter(m=>m._enabled);
    if (!picked.length) return;
    const newMats = picked.map(m => ({
      id: 'mat-'+Date.now()+'-'+Math.random().toString(36).slice(2,7),
      campaignId: campaign.id,
      kind: m.kind,
      name: m.name,
      quantity: m.quantity,
      unit: m.unit||'',
      source: m.source||'',
      notes: m.notes||'',
      properties: m.properties||[],
    }));
    onCommit(newMats);
  };

  return (
    <div style={matStyles.scrim} {...scrimDismiss(onClose)}>
      <div style={{...matStyles.modal, maxWidth: 720}} onClick={e=>e.stopPropagation()}>
        <div style={matStyles.modalHeader}>
          <div style={{fontFamily:"'Cinzel',serif",fontSize:16,fontWeight:700,color:'#e8e6e3'}}>🪓 Carve Monster — propose materials</div>
          <button style={matStyles.closeX} onClick={onClose}>✕</button>
        </div>
        <div style={{padding:18}}>
          <div style={matStyles.label}>Creature description</div>
          <textarea
            style={{...matStyles.input,minHeight:90,resize:'vertical',fontFamily:'inherit'}}
            placeholder="e.g. A young frost wyrm — pale-blue scales, blood that steams in cold air, two intact horns. CR 8."
            value={desc} onChange={e=>setDesc(e.target.value)} disabled={loading}/>
          {error && <div style={{color:'#f87171',fontSize:12,marginTop:8}}>{error}</div>}
          {!proposed && (
            <div style={{display:'flex',justifyContent:'flex-end',marginTop:10}}>
              <button style={{...matStyles.btnPrimary, opacity: (loading||desc.trim().length<8)?0.5:1}}
                      disabled={loading||desc.trim().length<8} onClick={generate}>
                {loading ? 'Carving…' : 'Propose materials'}
              </button>
            </div>
          )}
          {proposed && (
            <div style={{marginTop:14}}>
              <div style={{fontSize:11,color:'#9a9793',marginBottom:8}}>Toggle, edit quantities, then commit. Enabled materials will be added to the Materials tab.</div>
              <div style={{display:'flex',flexDirection:'column',gap:8,maxHeight:340,overflowY:'auto'}}>
                {proposed.map((m,i) => {
                  const meta = MAT_KIND_META[m.kind];
                  return (
                    <div key={i} style={{
                      border:'1px solid #2a2a32',
                      borderLeft: `3px solid ${meta.color}`,
                      borderRadius:6,
                      padding:10,
                      background: m._enabled ? '#1c1c22' : '#141418',
                      opacity: m._enabled ? 1 : 0.5,
                    }}>
                      <div style={{display:'flex',gap:10,alignItems:'flex-start'}}>
                        <input type="checkbox" checked={m._enabled} onChange={()=>togglePick(i)} style={{marginTop:4}}/>
                        <div style={{flex:1}}>
                          <div style={{display:'flex',gap:8,alignItems:'center',marginBottom:4}}>
                            <input style={{...matStyles.input,flex:1,padding:'5px 8px',fontWeight:700}} value={m.name} onChange={e=>updateField(i,'name',e.target.value)}/>
                            <input type="number" style={{...matStyles.input,width:60,padding:'5px 8px'}} value={m.quantity} onChange={e=>updateField(i,'quantity',+e.target.value||1)}/>
                            <input style={{...matStyles.input,width:90,padding:'5px 8px'}} value={m.unit} onChange={e=>updateField(i,'unit',e.target.value)}/>
                            <span style={{fontSize:10,color:meta.color,fontWeight:800,letterSpacing:'0.08em'}}>{meta.icon} {meta.label.toUpperCase()}</span>
                          </div>
                          {m.notes && <div style={{fontSize:12,color:'#9a9793',marginBottom:4,lineHeight:1.4}}>{m.notes}</div>}
                          {(m.properties||[]).length > 0 && (
                            <div style={{display:'flex',gap:6,flexWrap:'wrap'}}>
                              {m.properties.map((p,j) => (
                                <span key={j} style={{fontSize:10,background:'rgba(201,162,39,0.1)',color:'#c9a227',padding:'2px 6px',borderRadius:3,border:'1px solid rgba(201,162,39,0.25)'}}>
                                  <b>{p.key}</b> {p.value}
                                </span>
                              ))}
                            </div>
                          )}
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          )}
        </div>
        <div style={matStyles.modalFooter}>
          {proposed && <button style={matStyles.btnGhost} onClick={()=>{setProposed(null);}}>← Re-roll</button>}
          {!proposed && <span/>}
          <div style={{display:'flex',gap:8}}>
            <button style={matStyles.btnGhost} onClick={onClose}>Cancel</button>
            {proposed && <button style={matStyles.btnPrimary} onClick={commit} disabled={!proposed.some(m=>m._enabled)}>Commit {proposed.filter(m=>m._enabled).length} material{proposed.filter(m=>m._enabled).length===1?'':'s'}</button>}
          </div>
        </div>
      </div>
    </div>
  );
};

// ── Facility levels → crafting bonus to the d20 ──────────────────────────────
const FACILITY_BONUS = { null: 0, undefined: 0, basic: 1, improved: 3, advanced: 5 };
const facilityBonusOf = (level) => FACILITY_BONUS[level] ?? 0;
const facilityLabelOf = (level) => level ? level[0].toUpperCase() + level.slice(1) : 'None';

// ── Forge modes ──────────────────────────────────────────────────────────────
const FORGE_MODES = [
  { id:'forge',    icon:'⚒', label:'Forge',    blurb:'Fuse items and materials into one new artifact.' },
  { id:'reforge',  icon:'🔨', label:'Reforge',  blurb:'Keep the spirit and signature traits — give it a new identity.' },
  { id:'inspire',  icon:'✨', label:'Inspire',  blurb:'Same world flavor, fresh mechanics. Items act as muses, not parts.' },
  { id:'salvage',  icon:'🪓', label:'Salvage',  blurb:'Break items down into their component materials. No materials needed.' },
];

// ── Forge Tab ────────────────────────────────────────────────────────────────
const ForgeTab = ({ campaign, isDM, user }) => {
  // API-loaded slices (refreshed after each forge/salvage commit so the picker reflects state).
  // All forge data loaded via API. The "Hired Specialists" crafter category is gone
  // (no backend entity exists; deferred indefinitely — re-add when there's a real Specialist model).
  const [allItems, setAllItems]      = React.useState([]);
  const [allMats,  setAllMats]       = React.useState([]);
  const [partyInvList, setPartyInv]  = React.useState([]);
  const [forgeCities, setForgeCities] = React.useState([]);
  const [forgeShops, setForgeShops]   = React.useState([]);

  const reloadForgeData = React.useCallback(async () => {
    try {
      const [items, mats, inv, cities, shops] = await Promise.all([
        api.campaigns.homebrew.list(campaign.id),
        api.campaigns.materials.list(campaign.id),
        api.campaigns.inventory.list(campaign.id),
        api.cities.list({ campaignId: campaign.id }),
        api.shops.list(campaign.id),
      ]);
      setAllItems(items || []);
      setAllMats(mats || []);
      setPartyInv(inv || []);
      setForgeCities(cities || []);
      setForgeShops(shops || []);
    } catch (err) {
      console.error('[ForgeTab] data load failed:', err);
    }
  }, [campaign.id]);

  React.useEffect(() => { reloadForgeData(); }, [reloadForgeData]);
  // Resolve a selItems id to a normalised record (handles 'pi:' prefix for party-inventory items)
  const resolveItem = (id) => {
    if (typeof id === 'string' && id.startsWith('pi:')) {
      const pi = partyInvList.find(p => p.id === id.slice(3));
      if (!pi) return null;
      return { id, name: pi.name, type: 'Party Item', rarity: 'unique', attunement:false, tags:['party-inventory'], markdown: pi.description||'' };
    }
    return allItems.find(x => x.id === id) || null;
  };

  // Crafters & assistants — players (from CampaignDetailDto), guild members (API), shopkeepers (API).
  // The "Hired Specialists" category is gone — no backend entity exists for them.
  const candidates = React.useMemo(
    () => buildCrafterCandidates(campaign, campaignGuild, forgeShops, forgeCities),
    [campaign.id, campaignGuild, forgeShops, forgeCities]
  );

  // State
  const [mode, setMode]               = React.useState('forge'); // forge | reforge | inspire | salvage
  const [selItems, setSelItems]       = React.useState([]); // up to 4
  const [selMats,  setSelMats]        = React.useState([]); // any number
  const [crafterId, setCrafterId]     = React.useState(null);
  const [assistantId, setAssistantId] = React.useState(null);
  const [wishes, setWishes]           = React.useState('');
  const [pickerOpen, setPickerOpen]   = React.useState(null); // 'items' | 'materials' | 'crafter' | 'assistant'
  const [working, setWorking]         = React.useState(false);
  const [result, setResult]           = React.useState(null); // { ok:true, item, narrative } | { ok:false, refusal }
  const [error, setError]             = React.useState(null);
  const salvageMode = mode === 'salvage';

  // ── Workshop strip: location, facility, fee ───────────────────────────────
  // Each campaign has at most one guild. If this user is a member of it, that's
  // their guild. Loaded async; until it resolves, no facility bonus.
  const [campaignGuild, setCampaignGuild] = React.useState(null);
  React.useEffect(() => {
    let cancelled = false;
    api.guilds.get(campaign.id)
      .then(g => { if (!cancelled) setCampaignGuild(g); })
      .catch(err => { if (err.status !== 404) console.error('[ForgeTab] guild load failed:', err); });
    return () => { cancelled = true; };
  }, [campaign.id]);
  const isInGuild = (campaignGuild?.members || []).some(m => m.playerId === user.id);
  const workshopGuild = isInGuild ? campaignGuild : null;
  const [workshopCityId, setWorkshopCityId] = React.useState(null);
  React.useEffect(() => { if (workshopGuild?.baseCityId && !workshopCityId) setWorkshopCityId(workshopGuild.baseCityId); }, [workshopGuild?.baseCityId]);
  const facilities = (() => { try { return JSON.parse(workshopGuild?.facilitiesJson || '{}'); } catch { return {}; } })();
  const facilityLevel = workshopGuild && workshopGuild.baseCityId === workshopCityId ? (facilities.crafting || null) : null;
  const facilityBonus = facilityBonusOf(facilityLevel);

  // Fee proposal: editable, optionally re-quoted by the LLM
  const [proposedFee, setProposedFee] = React.useState(null);
  const [proposedDC,  setProposedDC]  = React.useState(null);
  const [feeReasoning, setFeeReasoning] = React.useState('');
  const [quoting, setQuoting]         = React.useState(false);
  const [feeOverride, setFeeOverride] = React.useState(null); // user-edited fee

  const crafter   = candidates.find(c => c.id === crafterId);
  const assistant = candidates.find(c => c.id === assistantId);

  const toggleItem = (id) => setSelItems(prev =>
    prev.includes(id) ? prev.filter(x=>x!==id) : (prev.length >= 4 ? prev : [...prev, id])
  );
  const toggleMat = (id) => setSelMats(prev =>
    prev.includes(id) ? prev.filter(x=>x!==id) : [...prev, id]
  );

  const modeMeta = FORGE_MODES.find(m => m.id === mode);
  const needsItems = true; // every mode needs at least one source item
  const canForge = selItems.length > 0
    && (salvageMode || selMats.length > 0 || selItems.length >= 1)
    && crafter
    && wishes.trim().length > 4;

  // Quote a fee + DC from the LLM based on inputs
  const requestQuote = async () => {
    if (!crafter || wishes.trim().length <= 4) return;
    setQuoting(true); setError(null);
    const itemSummary = selItems.map(id => { const it = resolveItem(id); return it ? `${it.name} (${it.rarity} ${it.type})` : ''; }).filter(Boolean).join('; ') || 'none';
    const matSummary  = selMats.map(id => { const m = allMats.find(x=>x.id===id); return m ? `${m.name} (${m.kind} ×${m.quantity}${m.unit?' '+m.unit:''})` : ''; }).filter(Boolean).join('; ') || 'none';
    const cityName = forgeCities.find(c => c.id === workshopCityId)?.name || 'unknown city';
    const prompt = `You are ${crafter.label} (${crafter.role}${crafter.race?', '+crafter.race:''}), a crafter in the D&D 5e campaign "${campaign.name}".

A party member wants you to ${mode === 'salvage' ? 'break down some items' : 'forge an item'} in your workshop in ${cityName}. Your workshop's crafting facilities are: ${facilityLabelOf(facilityLevel)} (giving you a +${facilityBonus} bonus on the crafting roll).

Source items: ${itemSummary}
Materials: ${matSummary}
Wishes: "${wishes.trim()}"

Quote a fair fee in gold pieces and propose a DC for the d20 crafting roll. The DC should reflect difficulty: simple common ≈ 10, uncommon ≈ 13, rare ≈ 16, very-rare ≈ 19, legendary ≈ 22+. Adjust upward if the wishes are vague or contradictory. The crafter will roll d20 + ${facilityBonus} (facility bonus). On success the item is made; on failure the materials are returned.

Reply ONLY with JSON, no fences:
{
  "fee": <integer gp>,
  "dc": <integer>,
  "reasoning": "1-2 sentences in your voice — what makes this hard, why the price."
}`;
    try {
      const raw = await window.claude.complete(prompt);
      const cleaned = raw.trim().replace(/^```(?:json)?\s*/,'').replace(/\s*```$/,'');
      const json = JSON.parse(cleaned);
      setProposedFee(Math.max(0, Math.round(json.fee || 0)));
      setFeeOverride(null);
      setProposedDC(Math.max(5, Math.min(30, Math.round(json.dc || 12))));
      setFeeReasoning(json.reasoning || '');
    } catch (e) {
      setError('Quote failed: '+(e.message||String(e)));
    }
    setQuoting(false);
  };

  const finalFee = feeOverride != null ? feeOverride : proposedFee;

  const beginForge = async () => {
    setWorking(true); setError(null); setResult(null);

    const itemBlocks = selItems.map((id,i) => {
      const it = resolveItem(id);
      if (!it) return '';
      return `### Item ${i+1}: ${it.name}\n- Type: ${it.type}\n- Rarity: ${it.rarity}${it.attunement?' (attunement)':''}\n- Tags: ${(it.tags||[]).join(', ')||'—'}\n\n${it.markdown}`;
    }).join('\n\n---\n\n');
    const matBlocks = salvageMode ? '(salvage mode — no materials are added; the crafter recovers components from the source items themselves)' : selMats.map(id => {
      const m = allMats.find(x=>x.id===id);
      const props = (m.properties||[]).map(p => `  - ${p.key}: ${p.value}`).join('\n');
      return `- **${m.name}** (${m.kind}, ${m.quantity} ${m.unit}, from ${m.source||'unknown'})\n${m.notes ? '  - notes: '+m.notes+'\n' : ''}${props}`;
    }).join('\n');

    const crafterDesc = describeCrafter(crafter);
    const assistantDesc = assistant ? describeCrafter(assistant) : 'None — the crafter works alone.';

    const modeInstructions = {
      forge:   'FUSE the source items and materials into a single new artifact. Combine signature traits where it makes sense; invent new behavior where the combination demands it.',
      reforge: 'REFORGE one of the source items into a new identity. Keep its spirit and at least one signature trait; let the materials and other items reshape its form, name, and the rest of its mechanics.',
      inspire: 'Use the source items as MUSES only — do not literally combine them. Produce a fresh item in the same world flavor, with new mechanics that feel like they belong on the same shelf.',
      salvage: 'BREAK the source items DOWN into their component materials. Output is a list of recovered materials (not a new item). Some traits may be lost; some essence may be preserved as a unique salvaged component.',
    }[mode];

    const prompt = `You roleplay a master crafter in the D&D 5e campaign "${campaign.name}" (setting: ${campaign.setting}). Decide whether this NPC/character can credibly perform the requested craft, and respond in character.

# CRAFT MODE: ${mode.toUpperCase()}
${modeInstructions}

# CRAFTER
${crafterDesc}

# ASSISTANT
${assistantDesc}

# PLAYER WISHES
"""
${wishes}
"""

# SOURCE ITEMS (existing homebrew items — NOT consumed except in salvage):
${itemBlocks}

# MATERIALS PROVIDED:
${matBlocks || '(none — crafter must work from items only)'}

# YOUR JOB
Decide, IN CHARACTER as the crafter, whether this is something they are confident they can do. Be honest about their limits — their proficiencies, specialties, personality, and stated max rarity. If the request offends their personality, exceeds their training, or the materials don't fit, REFUSE. Refusing costs the player nothing.

Choose the resulting item's RARITY YOURSELF based on the materials and source items provided. Tier UP the rarity only if the combined inputs genuinely justify it (rare materials, multiple high-tier items, a player wish that calls for legendary scope). When in doubt, stay at or near the rarity of the strongest source ingredient. Do not inflate.

Respond with ONLY a JSON object, no markdown fences, no commentary. Two possible shapes:

ON SUCCESS:
{
  "outcome": "success",
  "narrative": "1-3 sentences in the crafter's voice — what they say as they accept the work, how the assistant helps, the mood of the workshop.",
  "item": {
    "name": "...",
    "type": "...",
    "rarity": "common | uncommon | rare | very-rare | legendary | artifact",
    "attunement": true|false,
    "tags": ["...","..."],
    "markdown": "# Item Name\\n*Type, rarity (attunement clause if any)*\\n\\n[flavor]\\n\\n***Trait.*** [mechanical effect]\\n\\n> [in-world quote, optional]"
  }
}

Respond with ONLY a JSON object, no markdown fences, no commentary. Three possible shapes:

ON SUCCESS (forge / reforge / inspire):
{
  "outcome": "success",
  "narrative": "1-3 sentences in the crafter's voice — what they say as they accept the work, how the assistant helps, the mood of the workshop.",
  "item": {
    "name": "...",
    "type": "...",
    "rarity": "common | uncommon | rare | very-rare | legendary | artifact",
    "attunement": true|false,
    "tags": ["...","..."],
    "markdown": "# Item Name\n*Type, rarity (attunement clause if any)*\n\n[flavor]\n\n***Trait.*** [mechanical effect]\n\n> [in-world quote, optional]"
  }
}

ON SALVAGE SUCCESS (when mode is SALVAGE):
{
  "outcome": "salvaged",
  "narrative": "1-3 sentences in the crafter's voice as they break the items down. Some traits may whisper out as essence; some are simply lost to the heat.",
  "materials": [
    { "name": "...", "type": "metal | wood | bone | hide | gem | herb | essence | other", "quantity": 1, "rarity": "common | uncommon | rare | very-rare | legendary", "description": "1 sentence — what it looks like, what it remembers.", "properties": [ { "key": "density", "value": "heavy" } ] }
  ]
}
Produce 2-5 materials per source item, weighted by the items' rarities. Higher-rarity items yield 1 "essence" material that preserves a hint of the original power. Lower-rarity items yield mundane components.

ON REFUSAL:
{
  "outcome": "refused",
  "reason": "1-3 sentences in the crafter's voice explaining why they cannot or will not take this on. Reference specific limits — their training, their oath, the materials, or simply taste."
}

Match the crafter's personality precisely. A perfectionist doesn't apologize. A herbalist refuses necromantic work without lecturing. A dwarf doesn't waffle. Nothing is consumed on refusal.`;

    try {
      const raw = await window.claude.complete(prompt);
      const cleaned = raw.trim().replace(/^```(?:json)?\s*/,'').replace(/\s*```$/,'');
      const json = JSON.parse(cleaned);
      // Roll the crafting check client-side BEFORE deciding outcome shape.
      const dc = proposedDC || 13;
      const d20 = 1 + Math.floor(Math.random() * 20);
      const total = d20 + facilityBonus;
      const passed = total >= dc;
      const partial = !passed && (dc - total) <= 4; // close-but-no — partial recovery

      if (json.outcome === 'refused') {
        setResult({ ok: false, reason: json.reason });
      } else if (passed && json.outcome === 'success' && json.item) {
        setResult({ ok: true, kind: 'item', item: json.item, narrative: json.narrative, roll: { d20, total, dc, bonus: facilityBonus, passed: true } });
      } else if (passed && json.outcome === 'salvaged' && Array.isArray(json.materials)) {
        setResult({ ok: true, kind: 'salvage', materials: json.materials, narrative: json.narrative, roll: { d20, total, dc, bonus: facilityBonus, passed: true } });
      } else if (!passed) {
        // Failure / partial — items & materials are returned. Narrative comes from the LLM but we replace the shape.
        setResult({
          ok: false,
          failed: true,
          partial,
          reason: partial
            ? `${crafter.label} stops short of finishing. The work is sound but not what you asked for — they set down their tools and wave you back. "Take your materials. We'll try again with a clearer plan."`
            : `${crafter.label} sets the work aside, frustrated. "This won't hold. The materials are still good — bring them back when the design is sharper."`,
          roll: { d20, total, dc, bonus: facilityBonus, passed: false },
        });
      } else {
        throw new Error('Unexpected response shape');
      }
    } catch (e) {
      setError(e.message || String(e));
    }
    setWorking(false);
  };

  const acceptResult = async () => {
    if (!result?.ok) return;
    if (result.kind === 'salvage') {
      // Salvaged "materials" carry extra type/rarity/description fields the campaign material
      // schema doesn't yet model \u2014 flatten them into properties so nothing is lost.
      try {
        const created = (result.materials || []).map(m => {
          const extraProps = [];
          if (m.type)   extraProps.push({ key: 'type',   value: m.type });
          if (m.rarity) extraProps.push({ key: 'rarity', value: m.rarity });
          return {
            kind: 'carved',
            name: m.name || 'Salvaged component',
            quantity: Number(m.quantity) || 1,
            unit: '',
            source: `Salvaged by ${crafter?.label || 'forge'}`,
            notes: m.description || '',
            properties: [...extraProps, ...(Array.isArray(m.properties) ? m.properties : [])],
          };
        });
        for (const mat of created) {
          await api.campaigns.materials.create(campaign.id, mat);
        }
        await reloadForgeData();
        window.dialog.alert(`Recovered ${created.length} material${created.length===1?'':'s'} \u2014 added to the Materials tab.`);
      } catch (err) {
        console.error('[ForgeTab] salvage commit failed:', err);
        window.dialog.alert('Failed to save salvaged materials: ' + (err.message || 'unknown error'));
      }
      setResult(null); setSelItems([]); setSelMats([]); setWishes('');
      return;
    }

    // Forged a new item \u2014 POST to homebrew endpoint with provenance in ForgeMetaJson.
    const item = result.item;
    const forgeMeta = {
      crafter: crafter?.label, assistant: assistant?.label,
      cityId: workshopCityId, guildId: workshopGuild?.id || null,
      guildName: workshopGuild?.name || null,
      facilityLevel, facilityBonus, fee: finalFee, dc: proposedDC, roll: result.roll,
      narrative: result.narrative, wishes,
      sourceItemIds: [...selItems], sourceMaterialIds: [...selMats],
      date: new Date().toISOString().slice(0,10),
    };
    try {
      await api.campaigns.homebrew.create(campaign.id, {
        name: item.name || 'Forged Item',
        type: item.type || 'Wondrous Item',
        rarity: item.rarity || 'common',
        attunement: !!item.attunement,
        tags: item.tags || [],
        markdown: item.markdown || '',
        forgeMetaJson: JSON.stringify(forgeMeta),
      });
      await reloadForgeData();
      window.dialog.alert(`"${item.name}" added to the campaign Homebrew tab.`);
    } catch (err) {
      console.error('[ForgeTab] forge commit failed:', err);
      window.dialog.alert('Failed to save forged item: ' + (err.message || 'unknown error'));
    }
    setResult(null); setSelItems([]); setSelMats([]); setWishes('');
  };

  return (
    <div style={forgeStyles.tab}>
      <div style={forgeStyles.header}>
        <div>
          <div style={{fontFamily:"'Cinzel',serif",fontSize:18,fontWeight:700,color:'#e8e6e3'}}>⚒ The Forge</div>
          <div style={{fontSize:12,color:'#9a9793',marginTop:2}}>
            Combine items and materials with a chosen crafter. The crafter decides the resulting rarity from the inputs.
          </div>
        </div>
      </div>

      {/* Workshop strip — location, facility, fee */}
      <div style={forgeStyles.workshopStrip}>
        <div style={forgeStyles.wsCell}>
          <div style={forgeStyles.wsLabel}>WORKSHOP LOCATION</div>
          <window.CityPicker value={workshopCityId} onChange={setWorkshopCityId} campaignId={campaign.id} placeholder="Choose a city…"/>
        </div>
        <div style={forgeStyles.wsCell}>
          <div style={forgeStyles.wsLabel}>FACILITY LEVEL</div>
          <div style={forgeStyles.wsValue}>
            <span style={{color: facilityBonus>0?'#22c55e':'#7a7873'}}>{facilityLabelOf(facilityLevel)}</span>
            <span style={{color: facilityBonus>0?'#22c55e':'#7a7873', fontSize:11, marginLeft:8}}>(+{facilityBonus} to roll)</span>
          </div>
          {workshopGuild && <div style={{fontSize:10, color:'#7a7873', marginTop:3, fontStyle:'italic'}}>via {workshopGuild.name}</div>}
        </div>
        <div style={forgeStyles.wsCell}>
          <div style={forgeStyles.wsLabel}>JOB FEE</div>
          {proposedFee == null ? (
            <button style={forgeStyles.quoteBtn} disabled={!canForge || quoting} onClick={requestQuote}>
              {quoting ? 'Asking…' : 'Request quote'}
            </button>
          ) : (
            <div style={{display:'flex',alignItems:'center',gap:8}}>
              <input type="number" min="0" style={forgeStyles.feeInput} value={finalFee ?? ''} onChange={e=>setFeeOverride(Math.max(0, Math.round(+e.target.value||0)))} />
              <span style={{fontSize:11,color:'#c9a227',fontWeight:700}}>gp</span>
              <button style={{...forgeStyles.quoteBtn, padding:'4px 8px', fontSize:10}} disabled={quoting} onClick={requestQuote}>{quoting?'…':'Re-quote'}</button>
            </div>
          )}
          {proposedDC != null && <div style={{fontSize:10, color:'#7a7873', marginTop:3}}>DC {proposedDC} · roll d20 + {facilityBonus}</div>}
        </div>
        {feeReasoning && (
          <div style={{...forgeStyles.wsCell, flex:'1 1 100%', borderTop:'1px solid #2a2a32', paddingTop:10, marginTop:6}}>
            <div style={{fontSize:11, color:'#c9a227', fontStyle:'italic', lineHeight:1.5}}>"{feeReasoning}"</div>
          </div>
        )}
      </div>

      {/* Mode card-radio */}
      <div style={forgeStyles.modeGrid}>
        {FORGE_MODES.map(m => {
          const active = mode === m.id;
          return (
            <button key={m.id}
              style={{...forgeStyles.modeCard, ...(active ? forgeStyles.modeCardActive : {})}}
              onClick={()=>setMode(m.id)}>
              <div style={{display:'flex',alignItems:'center',gap:8}}>
                <div style={{...forgeStyles.modeIcon, ...(active ? forgeStyles.modeIconActive : {})}}>{m.icon}</div>
                <div style={{fontFamily:"'Cinzel',serif",fontSize:14,fontWeight:700,color: active ? '#c9a227' : '#e8e6e3'}}>
                  {m.label}
                </div>
                <div style={{marginLeft:'auto',width:14,height:14,borderRadius:'50%',border:'2px solid '+(active?'#c9a227':'#3a3a42'),background:active?'#c9a227':'transparent',flexShrink:0}}/>
              </div>
              <div style={{fontSize:11,color:'#9a9793',marginTop:8,lineHeight:1.5}}>{m.blurb}</div>
            </button>
          );
        })}
      </div>

      <div style={forgeStyles.grid}>
        {/* Items slot */}
        <ForgeSlot
          title="Source Items"
          subtitle={`${selItems.length}/4 — items you've previously made`}
          empty={!selItems.length}
          onAdd={()=>setPickerOpen('items')}
          addDisabled={selItems.length >= 4}
        >
          {selItems.map(id => {
            const it = resolveItem(id);
            if (!it) return null;
            return (
              <div key={id} style={forgeStyles.chipFilled}>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontSize:13,fontWeight:700,color:'#e8e6e3',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{it.name}</div>
                  <div style={{fontSize:11,color:'#9a9793'}}>{it.type} · {it.rarity}</div>
                </div>
                <button style={forgeStyles.removeX} onClick={()=>toggleItem(id)}>✕</button>
              </div>
            );
          })}
        </ForgeSlot>

        {/* Materials slot */}
        <ForgeSlot
          title={salvageMode ? 'Materials (disabled in Salvage)' : 'Materials'}
          subtitle={salvageMode ? 'Salvaging produces materials — none are consumed' : `${selMats.length} selected — gathered & carved`}
          empty={!selMats.length}
          onAdd={()=>setPickerOpen('materials')}
          addDisabled={salvageMode}
          disabled={salvageMode}
        >
          {selMats.map(id => {
            const m = allMats.find(x=>x.id===id);
            if (!m) return null;
            const meta = MAT_KIND_META[m.kind];
            return (
              <div key={id} style={forgeStyles.chipFilled}>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontSize:13,fontWeight:700,color:'#e8e6e3',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
                    <span style={{color:meta.color}}>{meta.icon}</span> {m.name}
                  </div>
                  <div style={{fontSize:11,color:'#9a9793'}}>{m.quantity} {m.unit} · {(m.properties||[]).length} props</div>
                </div>
                <button style={forgeStyles.removeX} onClick={()=>toggleMat(id)}>✕</button>
              </div>
            );
          })}
        </ForgeSlot>

        {/* Crafter slot */}
        <ForgeSlot
          title="Crafter"
          subtitle="Required — performs the work"
          empty={!crafter}
          single
          onAdd={()=>setPickerOpen('crafter')}
          accent
        >
          {crafter && <CrafterCard candidate={crafter} onRemove={()=>setCrafterId(null)}/>}
        </ForgeSlot>

        {/* Assistant slot */}
        <ForgeSlot
          title="Assistant"
          subtitle="Optional — second pair of hands"
          empty={!assistant}
          single
          onAdd={()=>setPickerOpen('assistant')}
        >
          {assistant && <CrafterCard candidate={assistant} onRemove={()=>setAssistantId(null)}/>}
        </ForgeSlot>

        {/* Wishes */}
        <div style={{...forgeStyles.slot, gridColumn:'1/-1'}}>
          <div style={forgeStyles.slotHead}>
            <div>
              <div style={forgeStyles.slotTitle}>Player Wishes</div>
              <div style={forgeStyles.slotSubtitle}>Tell the crafter what you want made. The more specific, the better the result.</div>
            </div>
          </div>
          <textarea
            style={forgeStyles.wishes}
            placeholder="e.g. I want a longsword that channels grief — when someone I love dies, the blade burns silver until I avenge them. Use the phoenix-ash and the vow-iron. Should feel mournful, not vengeful."
            value={wishes}
            onChange={e=>setWishes(e.target.value)}
            rows={4}
          />
        </div>
      </div>

      {/* Action bar */}
      <div style={forgeStyles.actionBar}>
        <div style={{fontSize:12,color:'#7a7873'}}>
          {!canForge ? (
            <span>
              {!crafter && '› Pick a crafter'}
              {crafter && wishes.trim().length <= 4 && '› Describe what you want made'}
              {crafter && wishes.trim().length > 4 && selItems.length === 0 && selMats.length === 0 && '› Add at least one item or material'}
            </span>
          ) : (
            <span style={{color:'#22c55e'}}>✓ Ready to forge — {crafter.label} {assistant ? `& ${assistant.label}` : ''} will weigh in.</span>
          )}
        </div>
        <button
          style={{...forgeStyles.bigBtn, ...(canForge && !working ? {} : forgeStyles.bigBtnDisabled)}}
          disabled={!canForge || working}
          onClick={beginForge}>
          {working ? '⚒ Working...' : '⚒ Begin the Craft'}
        </button>
      </div>

      {error && <div style={{padding:'10px 16px',color:'#f87171',fontSize:12}}>Error: {error}</div>}

      {/* Result */}
      {result && (
        <ForgeResult
          result={result}
          crafter={crafter}
          assistant={assistant}
          onAccept={acceptResult}
          onClose={()=>setResult(null)}
        />
      )}

      {/* Pickers */}
      {pickerOpen === 'items' && (
        <PickerModal title="Select up to 4 source items" onClose={()=>setPickerOpen(null)}>
          {(() => {
            const partyInv = partyInvList;
            const empty = allItems.length === 0 && partyInv.length === 0;
            if (empty) return <div style={{padding:30,color:'#7a7873',textAlign:'center'}}>No items yet — add to Homebrew or party inventory first.</div>;
            return (
              <>
                {allItems.length > 0 && (
                  <>
                    <div style={{padding:'10px 12px 4px',fontSize:11,fontWeight:800,color:'#c9a227',textTransform:'uppercase',letterSpacing:'0.12em'}}>✦ Homebrew Items</div>
                    {allItems.map(it => {
                      const checked = selItems.includes(it.id);
                      const disabled = !checked && selItems.length >= 4;
                      return (
                        <button key={it.id}
                          style={{...forgeStyles.pickRow, ...(checked?forgeStyles.pickRowActive:{}), ...(disabled?{opacity:0.4,cursor:'not-allowed'}:{})}}
                          disabled={disabled}
                          onClick={()=>toggleItem(it.id)}>
                          <div style={{flex:1,minWidth:0}}>
                            <div style={{fontSize:14,fontWeight:700,color:'#e8e6e3'}}>{it.name}</div>
                            <div style={{fontSize:12,color:'#9a9793'}}>{it.type} · {it.rarity}{it.attunement?' · attunement':''}</div>
                          </div>
                          <span style={{fontSize:18,color: checked?'#c9a227':'#3a3a42'}}>{checked?'✓':'+'}</span>
                        </button>
                      );
                    })}
                  </>
                )}
                {partyInv.length > 0 && (
                  <>
                    <div style={{padding:'14px 12px 4px',fontSize:11,fontWeight:800,color:'#7eb6ff',textTransform:'uppercase',letterSpacing:'0.12em'}}>⛀ Party Inventory</div>
                    {partyInv.map(pi => {
                      const id = 'pi:'+pi.id;
                      const checked = selItems.includes(id);
                      const disabled = !checked && selItems.length >= 4;
                      return (
                        <button key={id}
                          style={{...forgeStyles.pickRow, ...(checked?forgeStyles.pickRowActive:{}), ...(disabled?{opacity:0.4,cursor:'not-allowed'}:{})}}
                          disabled={disabled}
                          onClick={()=>toggleItem(id)}>
                          <div style={{flex:1,minWidth:0}}>
                            <div style={{fontSize:14,fontWeight:700,color:'#e8e6e3'}}>{pi.name}{pi.quantity>1?` ×${pi.quantity}`:''}</div>
                            <div style={{fontSize:12,color:'#9a9793',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>Party · {pi.value||'—'}</div>
                          </div>
                          <span style={{fontSize:18,color: checked?'#c9a227':'#3a3a42'}}>{checked?'✓':'+'}</span>
                        </button>
                      );
                    })}
                  </>
                )}
              </>
            );
          })()}
        </PickerModal>
      )}

      {pickerOpen === 'materials' && (
        <PickerModal title="Select materials" onClose={()=>setPickerOpen(null)}>
          {allMats.length === 0 && <div style={{padding:30,color:'#7a7873',textAlign:'center'}}>No materials yet — add some in the Materials tab first.</div>}
          {['gathered','carved'].map(kind => {
            const grp = allMats.filter(m=>m.kind===kind);
            if (!grp.length) return null;
            const meta = MAT_KIND_META[kind];
            return (
              <div key={kind}>
                <div style={{padding:'10px 12px 4px',fontSize:11,fontWeight:800,color:meta.color,textTransform:'uppercase',letterSpacing:'0.12em'}}>
                  {meta.icon} {meta.label}
                </div>
                {grp.map(m => {
                  const checked = selMats.includes(m.id);
                  return (
                    <button key={m.id}
                      style={{...forgeStyles.pickRow, ...(checked?forgeStyles.pickRowActive:{})}}
                      onClick={()=>toggleMat(m.id)}>
                      <div style={{flex:1,minWidth:0}}>
                        <div style={{fontSize:14,fontWeight:700,color:'#e8e6e3'}}>{m.name}</div>
                        <div style={{fontSize:12,color:'#9a9793'}}>{m.quantity} {m.unit} · {(m.properties||[]).length} properties</div>
                      </div>
                      <span style={{fontSize:18,color: checked?'#c9a227':'#3a3a42'}}>{checked?'✓':'+'}</span>
                    </button>
                  );
                })}
              </div>
            );
          })}
        </PickerModal>
      )}

      {(pickerOpen === 'crafter' || pickerOpen === 'assistant') && (
        <PickerModal
          title={pickerOpen === 'crafter' ? 'Select a crafter' : 'Select an assistant'}
          wide
          onClose={()=>setPickerOpen(null)}
        >
          {Object.entries(groupCandidates(candidates)).map(([groupName, list]) => (
            <div key={groupName} style={{marginBottom:18}}>
              <div style={forgeStyles.pickGroupHead}>{groupName}</div>
              <div style={forgeStyles.candGrid}>
                {list.map(c => {
                  const sel = pickerOpen === 'crafter' ? crafterId === c.id : assistantId === c.id;
                  const otherRoleSel = pickerOpen === 'crafter' ? assistantId === c.id : crafterId === c.id;
                  return (
                    <button key={c.id}
                      style={{
                        ...forgeStyles.candCard,
                        ...(sel?forgeStyles.candCardActive:{}),
                        ...(otherRoleSel?{opacity:0.4}:{}),
                      }}
                      disabled={otherRoleSel}
                      onClick={()=>{
                        if (pickerOpen === 'crafter') setCrafterId(c.id);
                        else setAssistantId(c.id);
                        setPickerOpen(null);
                      }}>
                      <div style={{display:'flex',justifyContent:'space-between',alignItems:'flex-start',gap:8}}>
                        <div style={{flex:1,minWidth:0}}>
                          <div style={forgeStyles.candName}>{c.label}</div>
                          <div style={forgeStyles.candRole}>{c.role}</div>
                        </div>
                        {sel && <div style={forgeStyles.candCheck}>✓</div>}
                      </div>
                      {c.race && <div style={forgeStyles.candRace}>{c.race}</div>}
                      {(c.proficiencies||[]).length > 0 && (
                        <div style={forgeStyles.candProfs}>
                          {(c.proficiencies||[]).slice(0,4).map((p,i) => (
                            <span key={i} style={forgeStyles.profChipSm}>{p}</span>
                          ))}
                        </div>
                      )}
                      {c.maxRarity && (
                        <div style={{fontSize:10,color:'#7a7873',marginTop:6,fontStyle:'italic'}}>
                          max rarity: <span style={{color:'#c9a227'}}>{c.maxRarity}</span>
                        </div>
                      )}
                      {otherRoleSel && (
                        <div style={{fontSize:10,color:'#f87171',marginTop:6}}>already in the other role</div>
                      )}
                    </button>
                  );
                })}
              </div>
            </div>
          ))}
        </PickerModal>
      )}
    </div>
  );
};

// ── Helper components ────────────────────────────────────────────────────────
const ForgeSlot = ({ title, subtitle, empty, onAdd, addDisabled, accent, disabled, single, children }) => (
  <div style={{...forgeStyles.slot, ...(accent?forgeStyles.slotAccent:{}), ...(disabled?forgeStyles.slotDisabled:{})}}>
    <div style={forgeStyles.slotHead}>
      <div style={{minWidth:0,flex:1}}>
        <div style={forgeStyles.slotTitle}>{title}</div>
        <div style={forgeStyles.slotSubtitle}>{subtitle}</div>
      </div>
      <button style={{...forgeStyles.btnGhost,...(addDisabled?{opacity:0.4,cursor:'not-allowed'}:{})}} onClick={onAdd} disabled={addDisabled}>
        {empty ? '+ Pick' : (single ? 'Change' : '+ Add')}
      </button>
    </div>
    <div style={forgeStyles.slotBody}>
      {empty ? <div style={forgeStyles.slotEmpty}>{disabled ? '— not used in this mode —' : 'nothing selected'}</div> : children}
    </div>
  </div>
);

const CrafterCard = ({ candidate, onRemove }) => (
  <div style={forgeStyles.crafterCard}>
    <div style={{display:'flex',justifyContent:'space-between',alignItems:'flex-start',gap:8}}>
      <div style={{flex:1,minWidth:0}}>
        <div style={{fontFamily:"'Cinzel',serif",fontSize:14,fontWeight:700,color:'#e8e6e3'}}>{candidate.label}</div>
        <div style={{fontSize:11,color:'#9a9793',marginTop:2}}>
          {candidate.role}{candidate.race?` · ${candidate.race}`:''}
        </div>
      </div>
      <button style={forgeStyles.removeX} onClick={onRemove}>✕</button>
    </div>
    {(candidate.proficiencies||[]).length > 0 && (
      <div style={{display:'flex',flexWrap:'wrap',gap:4,marginTop:8}}>
        {candidate.proficiencies.slice(0,5).map((p,i) => (
          <span key={i} style={forgeStyles.profChip}>{p}</span>
        ))}
      </div>
    )}
  </div>
);

const PickerModal = ({ title, children, onClose, wide }) => (
  <div style={forgeStyles.scrim} {...scrimDismiss(onClose)}>
    <div style={{...forgeStyles.pickerModal, ...(wide?forgeStyles.pickerModalWide:{})}} onClick={e=>e.stopPropagation()}>
      <div style={forgeStyles.modalHeader}>
        <div style={{fontFamily:"'Cinzel',serif",fontSize:15,fontWeight:700,color:'#e8e6e3'}}>{title}</div>
        <button style={forgeStyles.closeX} onClick={onClose}>✕</button>
      </div>
      <div style={{flex:1,overflow:'auto',padding:wide ? 16 : 6}}>{children}</div>
      <div style={forgeStyles.modalFooter}>
        <span/>
        <button style={forgeStyles.btnPrimary} onClick={onClose}>Done</button>
      </div>
    </div>
  </div>
);

const ForgeResult = ({ result, crafter, assistant, onAccept, onClose }) => {
  const isSalvage = result.ok && result.kind === 'salvage';
  const md = result.ok && !isSalvage ? result.item.markdown : null;
  return (
    <div style={forgeStyles.scrim} {...scrimDismiss(onClose)}>
      <div style={forgeStyles.resultModal} onClick={e=>e.stopPropagation()}>
        <div style={forgeStyles.modalHeader}>
          <div style={{fontFamily:"'Cinzel',serif",fontSize:16,fontWeight:700,color: result.ok?'#22c55e':'#f87171'}}>
            {!result.ok ? '✗ Refused' : (isSalvage ? '✓ Salvage Complete' : '✓ The Craft Is Done')}
          </div>
          <button style={forgeStyles.closeX} onClick={onClose}>✕</button>
        </div>

        <div style={{padding:'16px 20px',borderBottom:'1px solid #2a2a32',background:'#16161b'}}>
          <div style={{fontSize:12,color:'#9a9793',fontStyle:'italic',lineHeight:1.6}}>
            <span style={{color:'#c9a227',fontStyle:'normal',fontWeight:700}}>{crafter.label}</span>
            {assistant && <> with <span style={{color:'#c9a227',fontStyle:'normal',fontWeight:700}}>{assistant.label}</span></>}:
          </div>
          <div style={{fontSize:14,color:'#e8e6e3',marginTop:8,lineHeight:1.6,fontStyle:'italic'}}>
            "{result.ok ? result.narrative : result.reason}"
          </div>
        </div>

        {isSalvage ? (
          <>
            <div style={{flex:1,overflow:'auto',padding:'18px 22px'}}>
              <div style={{display:'flex',flexDirection:'column',gap:10}}>
                {(result.materials||[]).map((m,i)=>{
                  const rc = SHOP_RARITY_COLOR[m.rarity] || '#9a9793';
                  return (
                    <div key={i} style={{background:'#16161b',border:'1px solid #2a2a32',borderRadius:8,padding:'12px 14px'}}>
                      <div style={{display:'flex',alignItems:'baseline',gap:8,flexWrap:'wrap'}}>
                        <div style={{fontFamily:"'Cinzel',serif",fontSize:15,fontWeight:700,color:'#e8e6e3'}}>{m.name}</div>
                        <span style={{fontSize:11,color:rc,border:`1px solid ${rc}55`,borderRadius:4,padding:'1px 6px',textTransform:'uppercase',letterSpacing:'0.05em',fontWeight:700}}>{m.rarity||'common'}</span>
                        <span style={{fontSize:11,color:'#9a9793'}}>{m.type||'other'} · ×{m.quantity||1}</span>
                      </div>
                      {m.description && <div style={{fontSize:12,color:'#9a9793',marginTop:5,lineHeight:1.55}}>{m.description}</div>}
                      {(m.properties||[]).length>0 && (
                        <div style={{display:'flex',flexWrap:'wrap',gap:5,marginTop:7}}>
                          {m.properties.map((p,j)=>(<span key={j} style={{fontSize:11,color:'#c9a227',background:'rgba(201,162,39,0.08)',border:'1px solid rgba(201,162,39,0.2)',borderRadius:4,padding:'1px 6px'}}>{p.key}: {p.value}</span>))}
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>
            </div>
            <div style={forgeStyles.modalFooter}>
              <button style={forgeStyles.btnGhost} onClick={onClose}>Discard</button>
              <button style={forgeStyles.btnPrimary} onClick={onAccept}>Accept · Add {(result.materials||[]).length} to Materials</button>
            </div>
          </>
        ) : result.ok && md ? (
          <>
            <div style={{flex:1,overflow:'auto',padding:'18px 22px'}}>
              <div style={hbStyles.dndScroll}>
                {renderMarkdown(md)}
              </div>
            </div>
            <div style={forgeStyles.modalFooter}>
              <button style={forgeStyles.btnGhost} onClick={onClose}>Discard</button>
              <button style={forgeStyles.btnPrimary} onClick={onAccept}>Accept · Add to Homebrew</button>
            </div>
          </>
        ) : (
          <div style={{padding:'30px 24px',textAlign:'center'}}>
            <div style={{fontSize:48,opacity:0.3,marginBottom:12}}>⚒</div>
            <div style={{fontSize:13,color:'#7a7873',maxWidth:420,margin:'0 auto',lineHeight:1.6}}>
              Nothing was lost. Your items and materials are still in your stockpile.
              Try a different crafter, adjust your wishes, or lower the target rarity.
            </div>
            <button style={{...forgeStyles.btnPrimary,marginTop:18}} onClick={onClose}>Try Again</button>
          </div>
        )}
      </div>
    </div>
  );
};

// ── Crafter candidate aggregation ────────────────────────────────────────────
function buildCrafterCandidates(campaign, guild, shops = [], cities = []) {
  const out = [];

  // 1. Players in the campaign — derived from the campaign's API DTO. Characters
  // aren't pulled here (would need an extra fetch); name only for now.
  (campaign.players || []).forEach(p => {
    out.push({
      id: 'player-'+p.id,
      group: 'Player Characters',
      label: p.name,
      role: 'Player',
      proficiencies: [],
      raw: { user: p },
    });
  });

  // 2. Guild members (incl NPC specialists) — guild is API-sourced
  if (guild) {
    (guild.members || []).forEach(m => {
      // Skip if it's a player we already added
      if (m.playerId && out.some(c => c.id === 'player-'+m.playerId)) return;
      out.push({
        id: 'guild-'+m.id,
        group: 'Guild Members',
        label: m.name,
        role: `${m.role} · ${m.specialization} (${m.specLevel})`,
        proficiencies: [`${m.specialization} (${m.specLevel})`],
        raw: { guildMember: m },
      });
    });
  }

  // 3. (Hired Specialists category removed — no backend entity exists. Re-add when one does.)

  // 4. Shopkeepers in the campaign's cities — shops + cities are API-sourced.
  (shops || []).forEach(sh => {
    out.push({
      id: 'shop-'+sh.id,
      group: 'Shopkeepers',
      label: sh.keeper,
      role: `${SHOP_CATEGORY_META[sh.category]?.label || sh.category} · ${sh.name} · ${sh.cityName || (cities.find(c => c.id === sh.cityId)?.name) || sh.cityId}`,
      race: sh.keeperRace,
      personality: sh.personality,
      proficiencies: shopkeeperProficiencies(sh),
      raw: { shop: sh },
    });
  });

  return out;
}

function groupCandidates(candidates) {
  const groups = {};
  candidates.forEach(c => {
    if (!groups[c.group]) groups[c.group] = [];
    groups[c.group].push(c);
  });
  return groups;
}

function describeCrafter(c) {
  if (!c) return '(none)';
  const lines = [
    `Name: ${c.label}`,
    `Role: ${c.role}`,
  ];
  if (c.race) lines.push(`Race: ${c.race}`);
  if (c.proficiencies?.length) lines.push(`Proficiencies: ${c.proficiencies.join(', ')}`);
  if (c.specialties?.length) lines.push(`Specialties: ${c.specialties.join(', ')}`);
  if (c.maxRarity) lines.push(`Stated max rarity they will attempt: ${c.maxRarity}`);
  if (c.personality) lines.push(`Personality: ${c.personality}`);
  return lines.join('\n');
}

function buildClassProficiencies(ch) {
  // Rough mapping for prompt context. Not authoritative 5e — just enough flavor.
  const map = {
    Wizard:   ['Arcana', "Calligrapher's Supplies (scribing)", "Alchemist's Supplies"],
    Fighter:  ["Smith's Tools", 'Athletics', 'Heavy & Martial Weapons'],
    Cleric:   ['Religion', 'Medicine', 'Herbalism Kit'],
    Rogue:    ["Thieves' Tools (expertise)", "Tinker's Tools", 'Stealth'],
    Barbarian:['Survival', 'Athletics', 'Skinning & Hide-working'],
    Ranger:   ['Survival', 'Herbalism Kit', "Leatherworker's Tools"],
    Paladin:  ['Religion', 'Athletics', "Smith's Tools"],
    Druid:    ['Herbalism Kit', 'Nature', 'Brewer\'s Supplies'],
    Bard:     ['Performance', 'Persuasion', "Calligrapher's Supplies"],
    Sorcerer: ['Arcana', 'Persuasion'],
    Warlock:  ['Arcana', 'Religion'],
    Monk:     ['Athletics', 'Acrobatics'],
  };
  return map[ch.class] || ['(unknown class proficiencies)'];
}

function shopkeeperProficiencies(shop) {
  const map = {
    weapons:    ["Smith's Tools", 'Martial Weapons', 'History (Arms)'],
    armor:      ["Smith's Tools", "Leatherworker's Tools"],
    blacksmith: ["Smith's Tools (expertise)", "Mason's Tools"],
    herbal:     ['Herbalism Kit (expertise)', 'Medicine', "Alchemist's Supplies"],
    materials:  ['Mining Tools', "Jeweler's Tools", 'Insight (appraisal)'],
    arcane:     ['Arcana', "Calligrapher's Supplies"],
    magic:      ['Arcana', 'Investigation'],
    general:    ['Persuasion', 'Insight'],
    tavern:     ["Brewer's Supplies", "Cook's Utensils"],
  };
  return map[shop.category] || ['Insight', 'Persuasion'];
}

// ── Styles ───────────────────────────────────────────────────────────────────
const matStyles = {
  tab: { padding:0 },
  toolbar: {
    display:'flex', alignItems:'center', gap:10,
    padding:'10px 14px',
    background:'#16161b', border:'1px solid #2a2a32',
    borderRadius:8, marginBottom:14,
  },
  filterChip: {
    background:'#0d0d12', color:'#9a9793',
    border:'1px solid #2a2a32', borderRadius:4,
    padding:'5px 12px', fontSize:12, fontWeight:600, cursor:'pointer',
    fontFamily:"'Nunito',sans-serif",
  },
  filterChipActive: { background:'#1c1c22', color:'#e8e6e3', borderColor:'#4a4a52' },
  search: {
    flex:1, maxWidth:300,
    background:'#0d0d12', border:'1px solid #2a2a32',
    borderRadius:4, padding:'6px 10px', color:'#e8e6e3', fontSize:12,
    fontFamily:"'Nunito',sans-serif",
  },
  grid: { display:'grid', gridTemplateColumns:'repeat(auto-fill,minmax(280px,1fr))', gap:12 },
  card: {
    background:'#1c1c22', border:'1px solid #2a2a32',
    borderLeft:'3px solid #6b6966',
    borderRadius:6, padding:'12px 14px', cursor:'pointer',
    transition:'all 0.12s',
  },
  cardName: { fontFamily:"'Cinzel',serif", fontSize:14, fontWeight:700, color:'#e8e6e3' },
  cardSub: { fontSize:12, color:'#9a9793', marginTop:2 },
  editLink: { background:'none', border:'none', color:'#6b6966', cursor:'pointer', fontSize:14, padding:2 },
  scrim: {
    position:'fixed', inset:0, background:'rgba(8,8,12,0.85)',
    display:'flex', alignItems:'center', justifyContent:'center',
    zIndex:200, padding:24, backdropFilter:'blur(2px)',
  },
  modal: {
    background:'#13131a', border:'1px solid #3a3a42', borderRadius:10,
    width:'100%', maxWidth:720, maxHeight:'85vh',
    display:'flex', flexDirection:'column',
    boxShadow:'0 20px 60px rgba(0,0,0,0.7)',
    overflow:'hidden',
  },
  modalHeader: {
    display:'flex', justifyContent:'space-between', alignItems:'center',
    padding:'14px 18px', borderBottom:'1px solid #2a2a32', background:'#16161b', flexShrink:0,
  },
  modalFooter: {
    display:'flex', justifyContent:'space-between', alignItems:'center',
    padding:'12px 18px', borderTop:'1px solid #2a2a32', background:'#16161b', flexShrink:0,
  },
  closeX: { background:'none', border:'none', color:'#6b6966', cursor:'pointer', fontSize:18, padding:4, lineHeight:1 },
  label: { fontSize:10, fontWeight:800, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.12em', marginBottom:6 },
  input: {
    width:'100%', background:'#0d0d12', border:'1px solid #2a2a32', borderRadius:4,
    padding:'6px 10px', color:'#e8e6e3', fontSize:12, fontFamily:"'Nunito',sans-serif",
  },
  btnPrimary: {
    background:'var(--accent)', color:'#fff', border:'none', borderRadius:4,
    padding:'7px 14px', fontSize:12, fontWeight:700, cursor:'pointer',
    fontFamily:"'Nunito',sans-serif",
  },
  btnGhost: {
    background:'transparent', color:'#9a9793', border:'1px solid #3a3a42',
    borderRadius:4, padding:'6px 12px', fontSize:12, cursor:'pointer',
    fontFamily:"'Nunito',sans-serif",
  },
};

const forgeStyles = {
  tab: { display:'flex', flexDirection:'column', gap:14 },
  workshopStrip: { display:'flex', flexWrap:'wrap', gap:18, padding:'14px 18px', background:'#1c1c22', border:'1px solid #2a2a32', borderRadius:8 },
  wsCell: { display:'flex', flexDirection:'column', gap:4, minWidth:160 },
  wsLabel: { fontSize:10, fontWeight:800, color:'#7a7873', letterSpacing:'0.12em' },
  wsValue: { fontFamily:"'Cinzel',serif", fontSize:14, fontWeight:700, color:'#e8e6e3' },
  quoteBtn: { background:'rgba(201,162,39,0.08)', border:'1px solid rgba(201,162,39,0.3)', color:'#c9a227', borderRadius:6, padding:'6px 12px', cursor:'pointer', fontWeight:700, fontSize:12 },
  feeInput: { background:'#16161b', border:'1px solid #2a2a32', color:'#e8e6e3', borderRadius:6, padding:'6px 10px', width:90, fontFamily:"'Cinzel',serif", fontSize:14, fontWeight:700 },
  header: {
    display:'flex', justifyContent:'space-between', alignItems:'center',
    padding:'14px 18px', background:'#16161b', border:'1px solid #2a2a32', borderRadius:8,
  },
  modeGrid: {
    display:'grid', gridTemplateColumns:'repeat(4, minmax(0, 1fr))', gap:10,
  },
  modeCard: {
    textAlign:'left', cursor:'pointer',
    background:'#13131a', border:'1px solid #2a2a32', borderRadius:8,
    padding:'12px 14px',
    fontFamily:"'Nunito',sans-serif",
    transition:'all 0.12s',
  },
  modeCardActive: {
    background:'#1a1a22',
    borderColor:'rgba(201,162,39,0.55)',
    boxShadow:'0 0 0 1px rgba(201,162,39,0.25), 0 4px 18px rgba(201,162,39,0.08)',
  },
  modeIcon: {
    width:30, height:30, display:'flex', alignItems:'center', justifyContent:'center',
    fontSize:18, background:'#0d0d12',
    border:'1px solid #2a2a32', borderRadius:6, color:'#9a9793',
  },
  modeIconActive: { background:'rgba(201,162,39,0.12)', color:'#c9a227', borderColor:'rgba(201,162,39,0.4)' },
  grid: {
    display:'grid', gridTemplateColumns:'repeat(2, minmax(0, 1fr))', gap:12,
  },
  slot: {
    background:'#16161b', border:'1px solid #2a2a32', borderRadius:8,
    padding:14, display:'flex', flexDirection:'column', gap:10,
    minHeight:140,
  },
  slotAccent: { borderColor:'rgba(201,162,39,0.3)' },
  slotDisabled: { opacity:0.5, background:'#101015' },
  slotHead: { display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:10 },
  slotTitle: { fontFamily:"'Cinzel',serif", fontSize:13, fontWeight:700, color:'#c9a227', textTransform:'uppercase', letterSpacing:'0.06em' },
  slotSubtitle: { fontSize:11, color:'#7a7873', marginTop:2 },
  slotBody: { display:'flex', flexDirection:'column', gap:6 },
  slotEmpty: {
    fontSize:12, color:'#4a4a52', fontStyle:'italic',
    border:'1px dashed #2a2a32', borderRadius:4,
    padding:'14px 12px', textAlign:'center',
  },
  chipFilled: {
    display:'flex', alignItems:'center', gap:8,
    background:'#0d0d12', border:'1px solid #2a2a32', borderRadius:4,
    padding:'8px 10px',
  },
  removeX: {
    background:'none', border:'none', color:'#6b6966', cursor:'pointer',
    fontSize:14, padding:'2px 6px', lineHeight:1, borderRadius:3,
  },
  crafterCard: {
    background:'#0d0d12', border:'1px solid #2a2a32', borderRadius:5,
    padding:'10px 12px',
  },
  profChip: {
    fontSize:10, color:'#9a9793', background:'#16161b',
    border:'1px solid #2a2a32', padding:'2px 7px', borderRadius:3,
  },
  wishes: {
    width:'100%', background:'#0d0d12', border:'1px solid #2a2a32',
    borderRadius:4, padding:'10px 12px', color:'#e8e6e3',
    fontSize:13, lineHeight:1.55, resize:'vertical',
    fontFamily:"'Nunito',sans-serif",
  },
  actionBar: {
    display:'flex', justifyContent:'space-between', alignItems:'center',
    padding:'14px 18px', background:'#16161b',
    border:'1px solid rgba(201,162,39,0.2)', borderRadius:8,
  },
  bigBtn: {
    background:'var(--accent)', color:'#fff', border:'none',
    borderRadius:5, padding:'10px 22px',
    fontSize:13, fontWeight:700, fontFamily:"'Cinzel',serif",
    letterSpacing:'0.04em', cursor:'pointer',
    boxShadow:'0 4px 12px rgba(197,48,48,0.3)',
  },
  bigBtnDisabled: { opacity:0.4, cursor:'not-allowed', boxShadow:'none' },

  scrim: matStyles.scrim,
  modalHeader: matStyles.modalHeader,
  modalFooter: matStyles.modalFooter,
  closeX: matStyles.closeX,
  btnGhost: matStyles.btnGhost,
  btnPrimary: matStyles.btnPrimary,

  pickerModal: {
    background:'#13131a', border:'1px solid #3a3a42', borderRadius:10,
    width:'100%', maxWidth:560, height:'min(80vh, 600px)',
    display:'flex', flexDirection:'column',
    boxShadow:'0 20px 60px rgba(0,0,0,0.7)',
    overflow:'hidden',
  },
  pickerModalWide: { maxWidth:920 },
  pickGroupHead: {
    fontSize:11, fontWeight:800, color:'#6b6966',
    textTransform:'uppercase', letterSpacing:'0.12em',
    marginBottom:8, paddingBottom:6, borderBottom:'1px solid #1c1c22',
  },
  candGrid: {
    display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(220px, 1fr))', gap:10,
  },
  candCard: {
    textAlign:'left', cursor:'pointer',
    background:'#0d0d12', border:'1px solid #2a2a32', borderRadius:7,
    padding:'12px 14px', fontFamily:"'Nunito',sans-serif",
    transition:'all 0.12s', display:'flex', flexDirection:'column', gap:4,
  },
  candCardActive: {
    background:'#1a1a22', borderColor:'rgba(201,162,39,0.55)',
    boxShadow:'0 0 0 1px rgba(201,162,39,0.25)',
  },
  candName: { fontFamily:"'Cinzel',serif", fontSize:14, fontWeight:700, color:'#e8e6e3' },
  candRole: { fontSize:11, color:'#9a9793', marginTop:2, lineHeight:1.4 },
  candRace: { fontSize:10, color:'#7a7873', marginTop:2, fontStyle:'italic' },
  candProfs: { display:'flex', flexWrap:'wrap', gap:4, marginTop:8 },
  profChipSm: {
    fontSize:9, color:'#9a9793', background:'#16161b',
    border:'1px solid #2a2a32', padding:'2px 6px', borderRadius:3,
    textTransform:'uppercase', letterSpacing:'0.04em',
  },
  candCheck: {
    width:22, height:22, borderRadius:'50%',
    background:'rgba(201,162,39,0.15)', color:'#c9a227',
    display:'flex', alignItems:'center', justifyContent:'center',
    fontSize:13, fontWeight:700, flexShrink:0,
  },
  resultModal: {
    background:'#13131a', border:'1px solid #3a3a42', borderRadius:10,
    width:'100%', maxWidth:760, height:'min(85vh, 720px)',
    display:'flex', flexDirection:'column',
    boxShadow:'0 20px 60px rgba(0,0,0,0.7)',
    overflow:'hidden',
  },
  pickRow: {
    display:'flex', alignItems:'center', gap:10,
    width:'100%', background:'transparent',
    border:'none', borderBottom:'1px solid #1c1c22',
    padding:'10px 14px', cursor:'pointer', textAlign:'left',
    fontFamily:"'Nunito',sans-serif",
    transition:'background 0.1s',
  },
  pickRowActive: { background:'rgba(201,162,39,0.08)' },
};

Object.assign(window, { MaterialsTab, ForgeTab });
