brad-did-something / static /js /sprites.js
qFelix's picture
Brad Did Something - Gradio/FastAPI Space on HF
78c0f6e
Raw
History Blame Contribute Delete
4.75 kB
// 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 });
}