<!-- SOLID CITY · part 2043 -->
n (it) { return clone(it.d); }), n = ds.length, nm = ts[0].cr.name;
  select(null); setGhost(ds, n > 1 ? 'Copy of \u201c' + nm + '\u201d' : 'Copy of ' + C.shapeName(ds[0].t));
  say('Copied ' + n + ' piece' + (n > 1 ? 's' : '') + ' — the ghost is the copy; walk and PLACE it.');
}
function del() {
  if (!F.sel) return say('Select a piece to delete it.', true);
  var cr = F.sel.cr;
  if (!cr.local) { if (cr.mine || isAdmin()) return removeWorld(cr); return say('That build belongs to ' + (cr.owner || 'someone else') + '.', true); }
  pushUndo(); var ts = targets(), n = ts.length; ts.forEach(removeItem);
  if (!cr.items.length) dropCreation(cr);
  F.sel = null; changed(); say('Deleted ' + n + ' piece' + (n > 1 ? 's' : '') + '. ↶ brings ' + (n > 1 ? 'them' : 'it') + ' back.');
}

/* ---------------- undo (snapshots of the local bench) ---------------- */
function snapBench() { return JSON.stringify(F.bench.map(function (c) { return { lid: c.lid, name: c.name, pieces: c.items.map(function (it) { return it.d; }) }; })); }
function pushUndo() { F.undo.push({ bench: snapBench(), cur: F.cur && F.cur.lid }); if (F.undo.length > 60) F.undo.shift(); }
function loadBench(arr, curLid) {
  F.bench.slice().forEach(function (c) { c.items.slice().forEach(removeItem); }); F.bench.length = 0; F.cur = null; F.sel = null;
  (arr || []).forEach(function (c) {
    if (!c || !Array.isArray(c.pieces)) return;
    var cr = { lid: String(c.lid || ('c' + Math.random())).slice(0, 40), name: String(c.name || 'Creation').slice(0, L.NAME), local: true, items: [] };
    c.pieces.slice(0, L.MAX_PIECES).forEach(function (p) { var d = C.sanitizePiece(p); if (d) addItem(cr, d); });
    if (cr.items.length) { F.bench.push(cr); if (cr.lid === curLid) F.cur = cr; }
  });
}
function undo() {
  var u = F.undo.pop(); if (!u) return say('Nothing to undo.');
  loadBench(JSON.parse(u.bench), u.cur); changed(); say('Undone.');
}

/* ---------------- library (localStorage) + THE CREATOR import ---------------- */
function readLib() { try { var a = JSON.parse(localStorage.getItem(LS_LIB) || '[]'); return Array.isArray(a) ? a : []; } catch (e) { return []; } }
function writeLib(a) { try { localStorage.setItem(LS_LIB, JSON.stringify(a.slice(0, 40))); return true; } catch (e) { say('Library is full on this device — remove something first.', true); return false; } }
function libSave() {
  var items = F.sel ? targets() : F.cur ? F.cur.items : null;
  if (!items || !items.length) return say('Build or select something first.', true);
  var name = items.length > 1 || F.scope === 'whole' ? items[0].cr.name : C.shapeName(items[0].d.t);
  var lib = readLib(); lib.unshift({ name: String(name).slice(0, L.NAME), n: items.length, at: Date.now(), pieces: normalize(items.map(function (it) { return clone(it.d); })) });
  if (writeLib(lib)) { say('Saved \u201c' + name + '\u201d to your Library (' + items.length + ' piece' + (items.length > 1 ? 's' : '') + ').'); renderSheet(); }
}
function libStamp(i) {
  var e = readLib()[i]; if (!e) return;
  var ds = (e.pieces || []).map(C.sanitizePiece).filter(Boolean).slice(0, L.MAX_PIECES); if (!ds.length) return say('That library item is empty.', true);
  closeSheet(); select(null); setGhost(ds, e.name); say('\u201c' + e.name + '\u201d is the ghost — walk to a spot and PLACE.');
}
function libRemove(i) { var lib = readLib(), e = lib.splice(i, 1)[0]; if (e && confirm('Remove \u201c' + e.name + '\u201d from your Library?')) { writeLib(lib); renderSheet(); } }
function importCreator() {
  var added = 0, lib = readLib();
  try {
    var sc = JSON.parse(localStorage.getItem(LS_CRE_SCENE) || 'null');
    if (sc && Array.isArray(sc.blocks)) { var ds = sc.blocks.filter(function (b) { return b && b.vis !== false; }).map(C.sanitizePiece).filter(Boolean).slice(0, L.MAX_PIECES); if (ds.length) { lib.unshift({ name: 'THE CREATOR scene', n: ds.length, at: Date.now(), pieces: normalize(ds) }); added++; } }
    var cl = JSON.parse(localStorage.getItem(LS_CRE_LIB) || '[]');
    if (Array.isArray(cl)) cl.slice(0, 30).forEach(function (r) { var d = C.sanitizePiece(r); if (d) { lib.unshift({ name: String(r.name || C.shapeName(d.t)).slice(0, L.NAME), n: 1, at: Date.now(), pieces: normalize([d]) }); added++; } });
  } catch (e) {}
  if (!added) return say('No CREATOR saves on this device. Build at /playa (same browser), SAVE there, then import here.', true);
  if (writeLib(lib)) { say('Imported ' + added + ' item' + (added > 1 ? 's' : '') + ' from THE CREATOR into your Library.'); renderSheet(); }
}

/* ---------------- world: load / publish / pick up / review ---------------- */
function getMe() {
  if (F.me) return Promise.resolve(F.me);
  return fetch('/api/city/me', { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (j) { F.me = j; return j; }).catch(function () { return { loggedIn: false }; });
}
function isAdmin() { return !!(F.me && F.me.admin); }
function api(meth