// thin wrapper over the design system's BDSChibi top-down sprites const HAIR = { player: "#5d5b86", brad: "#7a4626", stacey: "#9a5a32", kevin: "#33324a", janet: "#241a30", derek: "#6f6a52", }; const COLOR = { player: "var(--bds-white)", brad: "var(--bds-brad)", stacey: "var(--bds-stacey)", kevin: "var(--bds-kevin)", janet: "var(--bds-janet)", derek: "var(--bds-derek)", }; export function charColor(id) { return COLOR[id] || "var(--bds-grey)"; } // a positioned character on the office floor: sprite + name tag + optional desk export function makeChar(parent, id, x, y, opts = {}) { const root = document.createElement("div"); root.style.cssText = `position:absolute;left:${x}px;top:${y}px;width:0;height:0;z-index:5;`; parent.appendChild(root); // contact shadow under the feet — grounds the sprite on the floor const feet = document.createElement("div"); feet.style.cssText = `position:absolute;left:-9px;top:0px;width:18px; height:5px;background:rgba(58,61,73,.30);border-radius:50%;z-index:1;`; root.appendChild(feet); const obj = { id, x, y, root, sprite: null, pose: null, facing: "down", dim: false, setPose(pose, facing = this.facing) { if (pose === this.pose && facing === this.facing && this.sprite) return; this.pose = pose; this.facing = facing; if (this.sprite) this.sprite.remove(); this.sprite = window.BDSChibi.topSprite({ who: id, pose, still: pose === "still", facing, dim: this.dim, }); this.sprite.style.left = "-14px"; this.sprite.style.top = "-30px"; root.insertBefore(this.sprite, root.firstChild); }, setPos(nx, ny) { this.x = nx; this.y = ny; root.style.left = `${nx}px`; root.style.top = `${ny}px`; }, setDim(v) { if (v !== this.dim) { this.dim = v; const p = this.pose; this.pose = null; this.setPose(p); } }, remove() { root.remove(); }, }; if (opts.desk) root.appendChild(window.BDSChibi.desk({ dark: opts.darkDesk })); if (opts.tag !== false) root.appendChild(window.BDSChibi.tag(id.toUpperCase(), COLOR[id], { top: 6 })); obj.setPose(opts.pose || "idle", opts.facing || "down"); return obj; } // ---- full designed chibi characters (Character Sprite Sheet v1.0) ---- // per-NPC default expression when they pitch a crisis const CRISIS_EXPRESSION = { brad: { armPose: "out", mouth: "worried", sweat: true }, stacey: { armPose: "wring", mouth: "worried" }, kevin: { armPose: "point", mouth: "smirk" }, janet: { armPose: "open", mouth: "open" }, derek: { mouth: "flat" }, player: { mouth: "neutral" }, }; // outcome animation trigger → chibi expression const OUTCOME_EXPRESSION = { npc_happy: { eyes: "happy", mouth: "smile", armPose: "open" }, npc_celebrating: { eyes: "happy", mouth: "open", armPose: "open", anim: "a-bounce" }, npc_angry: { mouth: "frown", armPose: "hips", anim: "a-shiver" }, npc_confused: { mouth: "wavy", tilt: 8 }, npc_devastated: { eyes: "closed", mouth: "frown", tilt: 5 }, npc_crying: { tears: true, mouth: "frown" }, npc_smug: { mouth: "smirk", armPose: "behindhead" }, npc_suspicious: { mouth: "flat", armPose: "crossed", tilt: -5 }, npc_hiding: { mouth: "worried", sweat: true }, npc_grateful: { eyes: "happy", mouth: "smile", blush: true }, heart_float: { eyes: "heart", mouth: "smile", blush: true }, disaster_flash: { eyes: "wide", mouth: "wavy", sweat: true }, revenue_rain: { eyes: "happy", mouth: "open", armPose: "open" }, }; /* a full chibi in a fixed-size box, scaled to fit (the chibi element itself is ~130-150px tall; transform scale keeps the layout box stable) */ export function chibiInBox(id, opts = {}, boxW = 72, boxH = 66) { const box = document.createElement("div"); box.style.cssText = `position:relative;width:${boxW}px;height:${boxH}px; flex:none;overflow:visible;`; const el = window.BDSChibi.chibi(id, opts.state || "idle", opts); // scale lives on a wrapper: the chibi's own idle/talk animations write to // its transform and would clobber an inline scale set on the element const realH = parseInt(el.style.height, 10) || 150; const scale = opts.fit || boxH / realH; const wrap = document.createElement("div"); wrap.style.cssText = `position:absolute;left:50%;bottom:0; transform-origin:bottom center; transform:translateX(-50%) scale(${scale})` + (opts.tilt ? ` rotate(${opts.tilt}deg)` : "") + ";"; wrap.appendChild(el); box.appendChild(wrap); return box; } export function crisisPortrait(id) { return chibiInBox(id, { ...CRISIS_EXPRESSION[id] }); } export function outcomePortrait(id, animation) { const expr = OUTCOME_EXPRESSION[animation] || CRISIS_EXPRESSION[id] || {}; return chibiInBox(id, { ...expr }); }