| |
| |
| |
| |
| |
| |
| |
| |
| (function () { |
| const DARK = "var(--bds-void)"; |
|
|
| |
| const css = ` |
| .chibi { position: relative; transform-origin: bottom center; z-index: 2; } |
| .chibi * { position: absolute; box-sizing: border-box; } |
| .chibi .part { border: 3px solid var(--bds-void); } |
| .chibi .head { border-radius: 44% 44% 46% 46%; z-index: 4; } |
| .chibi .hairpc { border: 3px solid var(--bds-void); z-index: 6; } |
| .chibi .collar { border-radius: 50%; z-index: 5; } |
| .chibi .torso { border-radius: 42% 42% 26% 26%; z-index: 3; } |
| .chibi .arm { border-radius: 45%; z-index: 2; transform-origin: top center; } |
| .chibi .hand { border-radius: 50%; z-index: 3; } |
| .chibi .leg { border-radius: 22% 22% 30% 30%; z-index: 3; } |
| .chibi .eye { background: var(--bds-void); border-radius: 50%; z-index: 7; } |
| .chibi .brow { z-index: 8; border-radius: 2px; } |
| .chibi .lash { background: var(--bds-void); z-index: 8; } |
| .chibi .glasses{ border: 3px solid var(--bds-void); background: transparent; border-radius: 36%; z-index: 8; } |
| .chibi .bridge { background: var(--bds-void); z-index: 8; } |
| .chibi .mouth { z-index: 7; } |
| .chibi .blush { z-index: 7; border-radius: 50%; opacity: .8; } |
| .chibi .sweat { background: var(--bds-stacey); border-radius: 50% 50% 50% 50% / 65% 65% 40% 40%; z-index: 9; box-shadow: 0 0 6px var(--bds-stacey); } |
| .chibi .pxglyph { z-index: 10; } |
| |
| .bds-top-sprite { position: absolute; width: 28px; height: 34px; z-index: 5; transform-origin: 50% 90%; } |
| .bds-top-sprite > i { position: absolute; display: block; } |
| |
| @media (prefers-reduced-motion: no-preference) { |
| .a-idle { animation: bds-c-idle 2.6s ease-in-out infinite; } |
| .a-bob { animation: bds-c-bob var(--spd,.5s) ease-in-out infinite; } |
| .a-walk .leg.l { animation: bds-c-lega var(--spd,.5s) ease-in-out infinite; } |
| .a-walk .leg.r { animation: bds-c-legb var(--spd,.5s) ease-in-out infinite; } |
| .a-walk .arm.l { animation: bds-c-arma var(--spd,.5s) ease-in-out infinite; } |
| .a-walk .arm.r { animation: bds-c-armb var(--spd,.5s) ease-in-out infinite; } |
| .a-crisis { animation: bds-c-jolt .45s ease-out infinite alternate; } |
| .a-rise { animation: bds-c-rise 3.6s ease-in-out infinite; } |
| .a-talkarm .arm.gesture { animation: bds-c-wave .7s ease-in-out infinite alternate; } |
| .a-mouth .mouth { animation: bds-c-flap .42s steps(2) infinite; } |
| .a-mouth-slow .mouth { animation: bds-c-flap 1.4s steps(2) infinite; } |
| .a-wring .arm.l { animation: bds-c-wra .55s ease-in-out infinite alternate; } |
| .a-wring .arm.r { animation: bds-c-wrb .55s ease-in-out infinite alternate; } |
| .a-type .arm.r { animation: bds-c-tap .3s steps(2) infinite; } |
| .a-nod .head, .a-nod .hairpc { animation: bds-c-nod 3s ease-in-out infinite; } |
| .a-shiver { animation: bds-c-shiver .22s steps(2) infinite; } |
| .a-bounce { animation: bds-c-bounce .6s var(--ease-pop, ease-out) infinite alternate; } |
| .a-peek { animation: bds-c-peek 3.2s steps(1,end) infinite; } |
| .a-tear .tearpx { animation: bds-c-tear 1.1s steps(3) infinite; } |
| .a-zzz .zpx { animation: bds-c-zzz 2.4s steps(4) infinite; } |
| .a-top-idle { animation: bds-top-idle 1.6s steps(2) infinite; } |
| .a-top-walk { animation: bds-top-walk var(--spd,.5s) ease-in-out infinite; } |
| /* articulated floor walk: legs step in opposition, arms counter-swing */ |
| .a-top-walk .fl-leg-l { animation: bds-fl-stepA var(--spd,.5s) ease-in-out infinite; } |
| .a-top-walk .fl-leg-r { animation: bds-fl-stepB var(--spd,.5s) ease-in-out infinite; } |
| .a-top-walk .fl-arm-l { animation: bds-fl-swingA var(--spd,.5s) ease-in-out infinite; } |
| .a-top-walk .fl-arm-r { animation: bds-fl-swingB var(--spd,.5s) ease-in-out infinite; } |
| .a-top-shake { animation: bds-c-shiver .2s steps(2) infinite; } |
| } |
| @keyframes bds-c-idle { 0%,100%{ transform: translateY(0) rotate(0); } 50%{ transform: translateY(-1px) rotate(-1.5deg); } } |
| @keyframes bds-c-bob { 50% { transform: translateY(-3px); } } |
| @keyframes bds-c-lega { 50% { transform: translateY(-5px) rotate(8deg); } } |
| @keyframes bds-c-legb { 50% { transform: translateY(0) rotate(-8deg); } } |
| @keyframes bds-c-arma { 50% { transform: rotate(18deg); } } |
| @keyframes bds-c-armb { 50% { transform: rotate(-18deg); } } |
| @keyframes bds-c-jolt { from { transform: translateY(2px) rotate(1.5deg);} to { transform: translateY(-4px) rotate(-1.5deg);} } |
| @keyframes bds-c-rise { 0%,28%{ transform: translateY(12px);} 72%,100%{ transform: translateY(0);} } |
| @keyframes bds-c-wave { from{ transform: rotate(-12deg);} to{ transform: rotate(-58deg);} } |
| @keyframes bds-c-flap { 50% { transform: scaleY(.4); } } |
| @keyframes bds-c-wra { from{ transform: rotate(14deg);} to{ transform: rotate(30deg) translateY(-2px);} } |
| @keyframes bds-c-wrb { from{ transform: rotate(-14deg);} to{ transform: rotate(-30deg) translateY(-2px);} } |
| @keyframes bds-c-tap { 50% { transform: translateY(-3px); } } |
| @keyframes bds-c-nod { 0%,40%,100%{ transform: translateY(0);} 50%,58%{ transform: translateY(2px);} } |
| @keyframes bds-c-shiver { 50% { transform: translateX(2px); } } |
| @keyframes bds-c-bounce { to { transform: translateY(-5px); } } |
| @keyframes bds-c-peek { 0%,60%,100% { transform: translateX(0); } 70%,90% { transform: translateX(7px); } } |
| @keyframes bds-c-tear { 0%{ transform: translateY(0); opacity:1;} 100%{ transform: translateY(14px); opacity:0;} } |
| @keyframes bds-c-zzz { 0%{ transform: translate(0,0); opacity:0;} 25%{ opacity:1;} 100%{ transform: translate(8px,-16px); opacity:0;} } |
| @keyframes bds-top-idle { 50% { transform: translateY(-2px); } } |
| @keyframes bds-top-walk { 0%,100%{transform:translateY(0);} 25%,75%{transform:translateY(-2px);} 50%{transform:translateY(0);} } |
| @keyframes bds-fl-stepA { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-3.5px);} } |
| @keyframes bds-fl-stepB { 0%,100%{transform:translateY(-3.5px);} 50%{transform:translateY(0);} } |
| @keyframes bds-fl-swingA { 0%,100%{transform:translateY(-1.5px);} 50%{transform:translateY(1px);} } |
| @keyframes bds-fl-swingB { 0%,100%{transform:translateY(1px);} 50%{transform:translateY(-1.5px);} } |
| `; |
| const styleEl = document.createElement("style"); |
| styleEl.textContent = css; |
| document.head.appendChild(styleEl); |
|
|
| |
| const CHARS = { |
| player: { key:"PLAYER", color:"--bds-white", u:132, wf:1.00, bw:14, bh:22, hd:10, walk:".5s", |
| face:{ hair:"#5d5b86", style:"short", eyes:"dot", brows:null, mouth:"neutral", glasses:false, woman:false } }, |
| brad: { key:"BRAD", color:"--bds-brad", u:134, wf:1.16, bw:18, bh:22, hd:11, walk:".34s", |
| face:{ hair:"#7a4626", style:"messy", eyes:"big", brows:"worried", mouth:"worried", glasses:false, woman:false, sweat:true } }, |
| stacey: { key:"STACEY", color:"--bds-stacey", u:118, wf:0.90, bw:12, bh:20, hd:9, walk:".42s", |
| face:{ hair:"#9a5a32", style:"long", eyes:"round", brows:null, mouth:"smile", glasses:false, woman:true } }, |
| kevin: { key:"KEVIN", color:"--bds-kevin", u:132, wf:1.00, bw:14, bh:22, hd:12, walk:".5s", roundHead:true, |
| face:{ hair:"#33324a", style:"neat", eyes:"round", brows:"stern", mouth:"smirk", glasses:true, woman:false } }, |
| janet: { key:"JANET", color:"--bds-janet", u:138, wf:1.00, bw:14, bh:23, hd:10, walk:".46s", |
| face:{ hair:"#241a30", style:"long", eyes:"round", brows:null, mouth:"smile", glasses:false, woman:true, streak:"var(--bds-neon-magenta)" } }, |
| derek: { key:"DEREK", color:"--bds-derek", u:148, wf:1.18, bw:18, bh:24, hd:11, walk:"1.1s", |
| face:{ hair:"#6f6a52", style:"bald", eyes:"sleepy", brows:"flat", mouth:"flat", glasses:false, woman:false } }, |
| |
| board: { key:"BOARD", color:"--bds-grey", u:138, wf:1.06, bw:16, bh:23, hd:10, walk:".6s", suit:"#3c3a52", |
| face:{ hair:"#4a485e", style:"neat", eyes:"dot", brows:"flat", mouth:"flat", glasses:false, woman:false } }, |
| |
| parent: { key:"PARENT", color:"--bds-kevin", u:158, wf:1.04, bw:16, bh:26, hd:11, walk:".7s", suit:"#6b632e", |
| face:{ hair:"#c9c4d8", style:"bald", eyes:"dot", brows:"flat", mouth:"smile", glasses:true, woman:false } }, |
| }; |
|
|
| |
| const GLYPHS = { |
| heart: ["Β·##Β·##Β·", "#######", "#######", "Β·#####Β·", "Β·Β·###Β·Β·", "Β·Β·Β·#Β·Β·Β·"], |
| star: ["Β·Β·Β·#Β·Β·Β·", "Β·Β·###Β·Β·", "#######", "Β·#####Β·", "Β·Β·#Β·#Β·Β·", "Β·#Β·Β·Β·#Β·"], |
| drop: ["Β·Β·#Β·Β·", "Β·###Β·", "#####", "#####", "Β·###Β·"], |
| z: ["####", "Β·Β·#Β·", "Β·#Β·Β·", "####"], |
| money: ["Β·###Β·", "#Β·#Β·Β·", "Β·###Β·", "Β·Β·#Β·#", "Β·###Β·"], |
| excl: ["##", "##", "##", "Β·Β·", "##"], |
| flame: ["Β·Β·#Β·Β·", "Β·##Β·Β·", "Β·###Β·", "#####", "Β·###Β·"], |
| }; |
|
|
| function pixelGlyph(rows, px, color, glow) { |
| if (typeof rows === "string") rows = GLYPHS[rows]; |
| px = px || 3; |
| const shadows = []; |
| rows.forEach((row, y) => { |
| [...row].forEach((ch, x) => { |
| if (ch === "#") shadows.push(`${x * px}px ${y * px}px 0 0 ${color}`); |
| }); |
| }); |
| const d = document.createElement("i"); |
| d.className = "pxglyph"; |
| d.style.cssText = `display:block;position:relative;width:${px}px;height:${px}px;` + |
| `margin-right:${(rows[0].length - 1) * px}px;margin-bottom:${(rows.length - 1) * px}px;` + |
| `background:transparent;box-shadow:${shadows.join(",")};` + |
| (glow ? `filter:drop-shadow(0 0 4px ${color});` : ""); |
| return d; |
| } |
| function glyphHTML(name, px, color, extra) { |
| const rows = GLYPHS[name]; |
| const shadows = []; |
| rows.forEach((row, y) => [...row].forEach((ch, x) => { |
| if (ch === "#") shadows.push(`${x * px}px ${y * px}px 0 0 ${color}`); |
| })); |
| return `<i class="pxglyph" style="display:block;width:${px}px;height:${px}px;box-shadow:${shadows.join(",")};${extra || ""}"></i>`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function chibi(who, state, opts) { |
| opts = opts || {}; |
| const c = typeof who === "string" ? CHARS[who] : who; |
| const u = c.u, wf = c.wf; |
| const col = opts.suit || c.suit ? `${opts.suit || c.suit}` : `var(${c.color})`; |
| const skin = "var(--bds-white)"; |
| const pants = "#241a44"; |
| const F = Object.assign({}, c.face, opts.face || {}); |
| const headD = Math.round((c.roundHead ? 0.50 : 0.46) * u); |
| const torsoW = Math.round(0.40 * u * wf); |
| const torsoH = Math.round(0.30 * u); |
| const armW = Math.round(0.13 * u), armH = Math.round(0.26 * u); |
| const legW = Math.round(0.16 * u), legH = Math.round(0.17 * u), legGap = Math.round(0.05 * u); |
| const handD = Math.round(0.13 * u); |
|
|
| const torsoBottomY = Math.round(legH * 0.45); |
| const torsoTopY = torsoBottomY + torsoH; |
| const headBottomY = torsoTopY - Math.round(headD * 0.22); |
| const headTopY = headBottomY + headD; |
| const H = headTopY + Math.round(0.06 * u); |
| const W = Math.round(Math.max(headD, torsoW + armW * 1.4) + Math.round(0.18 * u)); |
| const cx = W / 2; |
| const headLeft = cx - headD / 2; |
|
|
| const isBack = state === "up"; |
| const side = (state === "left" || state === "right") ? state : null; |
|
|
| const px = (fx) => headLeft + headD * fx; |
| const py = (fy) => headBottomY + headD * (1 - fy); |
|
|
| const el = document.createElement("div"); |
| el.className = "chibi"; |
| el.style.width = W + "px"; el.style.height = H + "px"; |
| if (opts.scale) { el.style.transform = `scale(${opts.scale})`; } |
| if (opts.tilt) { el.style.transform = (el.style.transform || "") + ` rotate(${opts.tilt}deg)`; } |
| let html = ""; |
|
|
| |
| if (F.style === "long") { |
| const hw = Math.round(headD * 0.26), hh = (headTopY - torsoBottomY) - Math.round(0.02 * u); |
| const hbot = torsoBottomY + Math.round(0.02 * u); |
| const z = isBack ? 7 : 3; |
| html += `<div class="hairpc" style="width:${hw}px;height:${hh}px;left:${headLeft - Math.round(0.01*u)}px;bottom:${hbot}px;background:${F.hair};border-radius:60% 40% 45% 55%;z-index:${z};"></div>`; |
| html += `<div class="hairpc" style="width:${hw}px;height:${hh}px;left:${headLeft + headD - hw + Math.round(0.01*u)}px;bottom:${hbot}px;background:${F.hair};border-radius:40% 60% 55% 45%;z-index:${z};"></div>`; |
| if (F.streak) html += `<div class="hairpc" style="width:${Math.round(hw*0.4)}px;height:${Math.round(hh*0.8)}px;left:${headLeft + headD - hw + Math.round(0.01*u)}px;bottom:${hbot}px;background:${F.streak};border:none;border-radius:40% 60% 55% 45%;z-index:${z+1};"></div>`; |
| } |
|
|
| |
| html += `<div class="part leg l" style="width:${legW}px;height:${legH}px;left:${cx - legGap/2 - legW}px;bottom:0;background:${pants};"></div>`; |
| html += `<div class="part leg r" style="width:${legW}px;height:${legH}px;left:${cx + legGap/2}px;bottom:0;background:${pants};"></div>`; |
|
|
| |
| const armBottom = torsoBottomY + Math.round(0.03 * u); |
| const pose = opts.armPose; |
| function armPair(rotL, rotR, raiseL, raiseR) { |
| const lx = cx - torsoW/2 - armW + Math.round(0.03*u); |
| const rx = cx + torsoW/2 - Math.round(0.03*u); |
| |
| |
| const hand = `<div class="part hand" style="width:${handD}px;height:${handD}px;left:50%;margin-left:${-Math.round(handD/2) - 3}px;bottom:${-Math.round(handD * 0.4)}px;background:${skin};"></div>`; |
| html += `<div class="part arm l" style="width:${armW}px;height:${armH}px;left:${lx}px;bottom:${armBottom + (raiseL||0)}px;background:${col};transform:rotate(${rotL}deg);">${hand}</div>`; |
| html += `<div class="part arm r gesture" style="width:${armW}px;height:${armH}px;left:${rx}px;bottom:${armBottom + (raiseR||0)}px;background:${col};transform:rotate(${rotR}deg);">${hand}</div>`; |
| } |
| if (side) { |
| const fx = side === "right" ? 1 : -1; |
| html += `<div class="part arm gesture" style="width:${armW}px;height:${armH}px;left:${cx - armW/2 + fx*Math.round(0.10*u)}px;bottom:${armBottom}px;background:${col};transform:rotate(${fx*22}deg);"></div>`; |
| } else if (pose === "out") armPair(8, -70, 0, Math.round(0.06*u)); |
| else if (pose === "open") armPair(46, -46, Math.round(0.04*u), Math.round(0.04*u)); |
| else if (pose === "palms") armPair(58, -58, Math.round(0.07*u), Math.round(0.07*u)); |
| else if (pose === "point") armPair(6, -88, 0, Math.round(0.10*u)); |
| else if (pose === "hips") armPair(-34, 34, 0, 0); |
| else if (pose === "behindhead") armPair(4, -160, 0, Math.round(0.16*u)); |
| else if (pose === "wring") armPair(22, -22, 0, 0); |
| else if (pose === "crossed") { |
| html += `<div class="part arm" style="width:${torsoW*0.92}px;height:${Math.round(armH*0.5)}px;left:${cx - torsoW*0.46}px;bottom:${armBottom + Math.round(0.05*u)}px;background:${col};border-radius:30%;z-index:4;"></div>`; |
| } |
| else armPair(0, 0, 0, 0); |
|
|
| |
| html += `<div class="part torso" style="width:${torsoW}px;height:${torsoH}px;left:${cx - torsoW/2}px;bottom:${torsoBottomY}px;background:${col};"></div>`; |
| if (c.suit || opts.suit) { |
| html += `<div style="width:${Math.round(0.04*u)}px;height:${Math.round(torsoH*0.6)}px;left:${cx - Math.round(0.02*u)}px;bottom:${torsoTopY - Math.round(torsoH*0.62)}px;background:${DARK};z-index:5;"></div>`; |
| } |
| if (!isBack && !side) { |
| const nw = Math.round(headD*0.34), nh = Math.round(0.05*u); |
| html += `<div class="collar" style="width:${nw}px;height:${nh}px;left:${cx - nw/2}px;bottom:${torsoTopY - nh - 1}px;background:rgba(0,0,0,.28);border-radius:0 0 60% 60%;"></div>`; |
| } |
|
|
| |
| html += `<div class="part head" style="width:${headD}px;height:${headD}px;left:${headLeft}px;bottom:${headBottomY}px;background:${skin};${isBack ? "filter:brightness(.92);" : ""}"></div>`; |
|
|
| |
| const cap = (w, h, by, radius) => |
| `<div class="hairpc" style="width:${w}px;height:${h}px;left:${cx - w/2}px;bottom:${by}px;background:${F.hair};border-radius:${radius};"></div>`; |
| if (F.style === "bald") html += cap(Math.round(headD*0.66), Math.round(headD*0.20), headTopY - Math.round(headD*0.22), "50% 50% 14% 14%"); |
| else if (F.style === "messy")html += cap(Math.round(headD*0.94), Math.round(headD*0.46), headTopY - Math.round(headD*0.42), "60% 50% 35% 30% / 70% 65% 30% 30%"); |
| else if (F.style === "neat") html += cap(Math.round(headD*0.92), Math.round(headD*0.40), headTopY - Math.round(headD*0.36), "55% 55% 20% 40%"); |
| else if (F.style === "long") html += cap(Math.round(headD*0.98), Math.round(headD*0.46), headTopY - Math.round(headD*0.40), "55% 55% 40% 40%"); |
| else html += cap(Math.round(headD*0.9), Math.round(headD*0.40), headTopY - Math.round(headD*0.34), "55% 55% 30% 30%"); |
|
|
| |
| if (!isBack && !opts.hideFace) { |
| const eyeBase = Math.max(4, Math.round(0.085 * u)); |
| let exL = 0.34, exR = 0.66; |
| if (side === "right") { exL = 0.50; exR = 0.74; } |
| if (side === "left") { exL = 0.26; exR = 0.50; } |
| const eyeY = 0.50; |
| const eyeStyle = opts.eyes || F.eyes; |
|
|
| const drawEye = (fx) => { |
| if (eyeStyle === "closed" || eyeStyle === "sleepy") { |
| const w = Math.round(eyeBase*1.4), h = Math.max(2, Math.round(eyeBase*0.42)); |
| return `<div class="eye" style="width:${w}px;height:${h}px;border-radius:40%;left:${px(fx)-w/2}px;bottom:${py(eyeY)-h/2}px;"></div>`; |
| } |
| if (eyeStyle === "happy") { |
| const w = Math.round(eyeBase*1.5), h = Math.round(eyeBase*0.9); |
| return `<div class="eye" style="width:${w}px;height:${h}px;background:transparent;border:3px solid ${DARK};border-bottom:none;border-radius:80% 80% 0 0;left:${px(fx)-w/2}px;bottom:${py(eyeY)-h/3}px;"></div>`; |
| } |
| if (eyeStyle === "heart") { |
| return `<div class="eye" style="background:transparent;left:${px(fx)-9}px;bottom:${py(eyeY)-6}px;">${glyphHTML("heart", 3, "var(--bds-neon-magenta)", "filter:drop-shadow(0 0 4px var(--bds-neon-magenta));")}</div>`; |
| } |
| if (eyeStyle === "wide") { |
| const d = Math.round(eyeBase*1.5); |
| return `<div class="eye" style="width:${d}px;height:${d}px;background:${skin};border:3px solid ${DARK};left:${px(fx)-d/2}px;bottom:${py(eyeY)-d/2}px;"></div>`; |
| } |
| const d = eyeStyle === "big" ? Math.round(eyeBase*1.25) : eyeBase; |
| return `<div class="eye" style="width:${d}px;height:${d}px;left:${px(fx)-d/2}px;bottom:${py(eyeY)-d/2}px;"></div>`; |
| }; |
| html += drawEye(exL) + drawEye(exR); |
|
|
| if (F.woman && eyeStyle !== "heart") { |
| const lw = Math.max(3, Math.round(eyeBase*0.7)), lh = Math.max(2, Math.round(0.022*u)); |
| html += `<div class="lash" style="width:${lw}px;height:${lh}px;left:${px(exL)-eyeBase*0.7}px;bottom:${py(eyeY)+eyeBase*0.45}px;transform:rotate(-24deg);border-radius:2px;"></div>`; |
| html += `<div class="lash" style="width:${lw}px;height:${lh}px;left:${px(exR)+eyeBase*0.0}px;bottom:${py(eyeY)+eyeBase*0.45}px;transform:rotate(24deg);border-radius:2px;"></div>`; |
| } |
|
|
| const brows = opts.brows !== undefined ? opts.brows : F.brows; |
| if (brows) { |
| const bw = Math.round(headD*0.16), bh = Math.max(2, Math.round(0.028*u)); |
| const browY = 0.34; |
| let rotL = 0, rotR = 0; |
| if (brows === "worried") { rotL = 16; rotR = -16; } |
| else if (brows === "stern") { rotL = -15; rotR = 15; } |
| html += `<div class="brow" style="width:${bw}px;height:${bh}px;background:${F.hair};left:${px(exL)-bw/2}px;bottom:${py(browY)}px;transform:rotate(${rotL}deg);"></div>`; |
| html += `<div class="brow" style="width:${bw}px;height:${bh}px;background:${F.hair};left:${px(exR)-bw/2}px;bottom:${py(browY)}px;transform:rotate(${rotR}deg);"></div>`; |
| } |
|
|
| if (F.glasses) { |
| const gd = Math.round(eyeBase*2.0); |
| html += `<div class="glasses" style="width:${gd}px;height:${Math.round(gd*0.86)}px;left:${px(exL)-gd/2}px;bottom:${py(eyeY)-gd*0.43}px;"></div>`; |
| html += `<div class="glasses" style="width:${gd}px;height:${Math.round(gd*0.86)}px;left:${px(exR)-gd/2}px;bottom:${py(eyeY)-gd*0.43}px;"></div>`; |
| html += `<div class="bridge" style="width:${px(exR)-px(exL)-gd}px;height:3px;left:${px(exL)+gd/2}px;bottom:${py(eyeY)}px;"></div>`; |
| } |
|
|
| |
| const mcx = px(0.5); |
| const mouth = opts.mouth || (state === "talk" ? "open" : F.mouth); |
| const my = 0.72; |
| if (mouth === "open") { |
| const mw = Math.round(headD*0.22), mh = Math.round(headD*0.16); |
| html += `<div class="mouth" style="width:${mw}px;height:${mh}px;left:${mcx-mw/2}px;bottom:${py(0.74)-mh/2}px;background:${DARK};border-radius:45%;transform-origin:center;"></div>`; |
| } else if (mouth === "smile") { |
| const mw = Math.round(headD*0.24), mh = Math.round(headD*0.12); |
| html += `<div class="mouth" style="width:${mw}px;height:${mh}px;left:${mcx-mw/2}px;bottom:${py(my)-mh}px;border:3px solid ${DARK};border-top:none;background:transparent;border-radius:0 0 80% 80%;"></div>`; |
| } else if (mouth === "frown") { |
| const mw = Math.round(headD*0.24), mh = Math.round(headD*0.12); |
| html += `<div class="mouth" style="width:${mw}px;height:${mh}px;left:${mcx-mw/2}px;bottom:${py(my)-mh/2}px;border:3px solid ${DARK};border-bottom:none;background:transparent;border-radius:80% 80% 0 0;"></div>`; |
| } else if (mouth === "worried") { |
| const mw = Math.round(headD*0.16), mh = Math.round(headD*0.13); |
| html += `<div class="mouth" style="width:${mw}px;height:${mh}px;left:${mcx-mw/2}px;bottom:${py(my)-mh/2}px;background:${DARK};border-radius:48%;"></div>`; |
| } else if (mouth === "smirk") { |
| const mw = Math.round(headD*0.22), mh = Math.max(3, Math.round(0.03*u)); |
| html += `<div class="mouth" style="width:${mw}px;height:${mh}px;left:${mcx-mw/2+Math.round(headD*0.04)}px;bottom:${py(my)}px;background:${DARK};border-radius:3px;transform:rotate(-12deg);"></div>`; |
| } else if (mouth === "wavy") { |
| const mw = Math.round(headD*0.07); |
| for (let i = 0; i < 3; i++) { |
| html += `<div class="mouth" style="width:${mw}px;height:3px;left:${mcx-mw*1.5+i*mw}px;bottom:${py(my)+(i%2?2:0)}px;background:${DARK};"></div>`; |
| } |
| } else if (mouth === "flat") { |
| const mw = Math.round(headD*0.22), mh = Math.max(2, Math.round(0.025*u)); |
| html += `<div class="mouth" style="width:${mw}px;height:${mh}px;left:${mcx-mw/2}px;bottom:${py(my)}px;background:${DARK};border-radius:2px;"></div>`; |
| } else { |
| const mw = Math.round(headD*0.12), mh = Math.max(2, Math.round(0.025*u)); |
| html += `<div class="mouth" style="width:${mw}px;height:${mh}px;left:${mcx-mw/2}px;bottom:${py(my)}px;background:${DARK};border-radius:2px;"></div>`; |
| } |
|
|
| if (opts.blush) { |
| const bd = Math.round(headD*0.14); |
| html += `<div class="blush" style="width:${bd}px;height:${Math.round(bd*0.55)}px;left:${px(0.22)}px;bottom:${py(0.66)}px;background:var(--bds-brad);"></div>`; |
| html += `<div class="blush" style="width:${bd}px;height:${Math.round(bd*0.55)}px;left:${px(0.70)}px;bottom:${py(0.66)}px;background:var(--bds-brad);"></div>`; |
| } |
| if (opts.sweat || (F.sweat && (state === "crisis"))) { |
| const sw = Math.round(0.05*u), sh = Math.round(0.07*u); |
| html += `<div class="sweat" style="width:${sw}px;height:${sh}px;left:${px(0.84)}px;bottom:${py(0.30)}px;"></div>`; |
| } |
| if (opts.tears) { |
| html += `<div class="tearpx" style="position:absolute;left:${px(0.30)}px;bottom:${py(0.62)}px;z-index:9;">${glyphHTML("drop", 2, "var(--bds-derek)", "filter:drop-shadow(0 0 3px var(--bds-derek));")}</div>`; |
| html += `<div class="tearpx" style="position:absolute;left:${px(0.66)}px;bottom:${py(0.64)}px;z-index:9;">${glyphHTML("drop", 2, "var(--bds-derek)", "filter:drop-shadow(0 0 3px var(--bds-derek));")}</div>`; |
| } |
| } |
|
|
| |
| if (opts.heartAbove) { |
| html += `<div style="position:absolute;left:${cx + Math.round(headD*0.18)}px;bottom:${headTopY + 6}px;z-index:10;">${glyphHTML("heart", 3, "var(--bds-neon-magenta)", "filter:drop-shadow(0 0 5px var(--bds-neon-magenta));")}</div>`; |
| } |
| if (opts.zzz) { |
| for (let i = 0; i < 3; i++) { |
| html += `<div class="zpx" style="position:absolute;left:${cx + Math.round(headD*0.34) + i*10}px;bottom:${headTopY - 6 + i*9}px;z-index:10;animation-delay:${i*0.8}s;">${glyphHTML("z", 2, "var(--bds-grey)", "")}</div>`; |
| } |
| } |
| if (opts.sweatDropAbove) { |
| html += `<div style="position:absolute;left:${px(0.86)}px;bottom:${headTopY - 4}px;z-index:10;">${glyphHTML("drop", 3, "var(--bds-stacey)", "filter:drop-shadow(0 0 4px var(--bds-stacey));")}</div>`; |
| } |
| if (state === "crisis" && !opts.noBubble) { |
| html += `<div style="position:absolute;left:${cx + Math.round(headD*0.20)}px;bottom:${headTopY - 2}px;z-index:10;width:28px;height:28px;border-radius:8px;background:var(--bds-white);border:3px solid ${DARK};display:flex;align-items:center;justify-content:center;font-family:var(--font-display);font-size:13px;color:var(--bds-danger);box-shadow:0 0 12px rgba(255,90,60,.6);">!</div>`; |
| } |
|
|
| el.innerHTML = html; |
|
|
| |
| if (opts.anim) opts.anim.split(" ").forEach(cl => cl && el.classList.add(cl)); |
| else if (state === "idle") { |
| if (c.key === "DEREK") { } |
| else el.classList.add("a-idle"); |
| } else if (["down","up","left","right"].includes(state)) { |
| el.classList.add("a-walk","a-bob"); |
| el.style.setProperty("--spd", c.walk); |
| } else if (state === "crisis") { |
| el.classList.add(c.key === "DEREK" ? "a-rise" : "a-crisis"); |
| } else if (state === "talk") { |
| el.classList.add("a-mouth"); |
| } |
| return el; |
| } |
|
|
| |
| |
| |
| |
| |
| function personHTML(o, ink) { |
| const skin = o.skin || "#f0c8a4", hair = o.hair || "#3a2a18", |
| shirt = o.shirt || "var(--bds-white)", pants = o.pants || "#2c2b3a", |
| style = o.style || "short", back = o.back; |
| const R = (x, y, w, h, bg, ex) => `<i style="left:${x}px;top:${y}px;width:${w}px;height:${h}px;background:${bg};${ex || ""}"></i>`; |
| const RC = (cls, x, y, w, h, bg, ex) => `<i class="${cls}" style="left:${x}px;top:${y}px;width:${w}px;height:${h}px;background:${bg};${ex || ""}"></i>`; |
| const O = `box-shadow:0 0 0 2px ${ink};`; |
| let h = ""; |
| |
| h += RC("fl-leg-l", 7, 25, 5, 8, pants, O) + RC("fl-leg-r", 14, 25, 5, 8, pants, O); |
| h += RC("fl-leg-l", 6, 31, 7, 3, "#26242f") + RC("fl-leg-r", 13, 31, 7, 3, "#26242f"); |
| |
| h += RC("fl-arm-l", 0, 14, 4, 10, shirt, O) + RC("fl-arm-r", 22, 14, 4, 10, shirt, O); |
| h += RC("fl-arm-l", 0, 22, 4, 3, skin) + RC("fl-arm-r", 22, 22, 4, 3, skin); |
| |
| h += R(4, 13, 18, 14, shirt, O + "border-radius:5px 5px 2px 2px;"); |
| if (o.tie && !back) h += R(12, 14, 3, 10, o.tie); |
| h += R(11, 11, 4, 4, skin); |
| |
| h += R(5, 0, 16, 14, skin, O + "border-radius:46% 46% 44% 44%;z-index:2;"); |
| |
| if (style === "bald") h += R(7, -1, 12, 5, hair, O + "border-radius:50% 50% 12% 12%;z-index:3;"); |
| else if (style === "long") { |
| h += R(3, 1, 4, 15, hair, O + "border-radius:50% 20% 30% 60%;z-index:1;") + R(19, 1, 4, 15, hair, O + "border-radius:20% 50% 60% 30%;z-index:1;"); |
| h += R(4, -2, 18, 8, hair, O + "border-radius:55% 55% 30% 30%;z-index:3;"); |
| if (o.streak) h += R(18, 0, 4, 12, o.streak, "z-index:4;border-radius:30% 50% 60% 30%;"); |
| } else if (style === "messy") h += R(3, -3, 20, 9, hair, O + "border-radius:60% 50% 40% 40% / 70% 65% 30% 30%;z-index:3;"); |
| else h += R(4, -2, 18, 8, hair, O + "border-radius:55% 55% 25% 35%;z-index:3;"); |
| if (back) { |
| |
| h += R(5, 2, 16, 10, hair, "z-index:3;border-radius:40% 40% 46% 46%;"); |
| } else { |
| |
| if (o.eyes === "sleepy") { |
| h += R(9, 7, 3, 1, ink, "z-index:4;border-radius:1px;") + R(14, 7, 3, 1, ink, "z-index:4;border-radius:1px;"); |
| } else { |
| const eh = o.eyes === "big" ? 4 : 3; |
| h += R(9, 5, 2, eh, ink, "z-index:4;border-radius:1px;") + R(15, 5, 2, eh, ink, "z-index:4;border-radius:1px;"); |
| } |
| if (o.glasses) { |
| h += R(7, 4, 5, 5, "transparent", "z-index:5;border:2px solid " + ink + ";border-radius:2px;") + R(14, 4, 5, 5, "transparent", "z-index:5;border:2px solid " + ink + ";border-radius:2px;") + R(12, 6, 2, 2, ink, "z-index:5;"); |
| } |
| |
| const mth = o.mouth; |
| if (mth === "smile") h += R(10, 10, 6, 3, "transparent", "z-index:4;border:2px solid rgba(120,40,30,.6);border-top:none;border-radius:0 0 70% 70%;"); |
| else if (mth === "smirk") h += R(11, 10, 5, 2, "rgba(120,40,30,.6)", "z-index:4;border-radius:2px;transform:rotate(-13deg);"); |
| else if (mth === "worried") h += R(11, 9, 3, 3, "rgba(120,40,30,.55)", "z-index:4;border-radius:50%;"); |
| else h += R(11, 10, 4, 2, "rgba(120,40,30,.5)", "z-index:4;border-radius:2px;"); |
| if (o.sweat) h += R(18, 3, 3, 4, "#7fc6e8", "z-index:6;border-radius:60% 60% 50% 50%;box-shadow:0 0 4px #7fc6e8;"); |
| } |
| return h; |
| } |
|
|
| |
| |
| |
| |
| function topSprite(opts) { |
| opts = opts || {}; |
| |
| |
| const c = opts.who && CHARS[opts.who] ? CHARS[opts.who] : null; |
| const f = c ? c.face : {}; |
| const el = document.createElement("div"); |
| el.className = "bds-top-sprite"; |
| el.style.width = "26px"; el.style.height = "34px"; |
| const anims = { idle: "a-top-idle", walk: "a-top-walk", shake: "a-top-shake" }; |
| const pose = opts.pose; |
| if (!opts.still && anims[pose]) el.classList.add(anims[pose]); |
| else if (!opts.still && (pose == null || pose === "idle")) el.classList.add("a-top-idle"); |
| |
| el.style.setProperty("--spd", opts.walk || (c && c.walk) || ".5s"); |
| let tf = ""; |
| if (pose === "lean") tf = "rotate(-8deg)"; |
| else if (pose === "slump") tf = "rotate(6deg) translateY(3px)"; |
| if (opts.scale) tf += ` scale(${opts.scale})`; |
| if (tf) { el.style.transform = tf; el.style.transformOrigin = "50% 100%"; } |
| if (opts.dim) el.style.filter = "brightness(.6) saturate(.7)"; |
| el.innerHTML = personHTML({ |
| skin: opts.skin, |
| hair: opts.hair || f.hair, |
| shirt: opts.color || (c ? `var(${c.color})` : "var(--bds-white)"), |
| pants: opts.pants, back: opts.facing === "up", |
| style: opts.style || f.style, |
| glasses: opts.glasses != null ? opts.glasses : f.glasses, |
| streak: opts.streak || f.streak, |
| eyes: opts.eyes || f.eyes, |
| mouth: opts.mouth || f.mouth, |
| sweat: opts.sweat, |
| tie: opts.tie, |
| }, "var(--bds-void)"); |
| return el; |
| } |
|
|
| |
| function desk(opts) { |
| opts = opts || {}; |
| const el = document.createElement("div"); |
| el.style.cssText = "position:absolute;width:46px;height:22px;left:-23px;top:14px;background:#5a4326;border:2px solid #3a2b16;z-index:4;box-shadow:inset 0 3px 0 #6e542f, inset 0 -3px 0 #3f2f19;"; |
| const mon = document.createElement("div"); |
| mon.style.cssText = `position:absolute;left:50%;top:-9px;transform:translateX(-50%);width:18px;height:12px;background:${opts.dark ? "#0a0c14" : "#10131f"};border:2px solid #2a2f44;${opts.dark ? "" : "box-shadow:0 0 6px rgba(80,140,255,.35);"}`; |
| el.appendChild(mon); |
| return el; |
| } |
|
|
| |
| function tag(text, color, opts) { |
| opts = opts || {}; |
| const el = document.createElement("div"); |
| el.style.cssText = `position:absolute;left:50%;top:${opts.top != null ? opts.top : 34}px;transform:translateX(-50%);font-family:var(--font-display);font-size:6px;letter-spacing:1px;color:${color};text-shadow:${opts.glow === false ? "1px 1px 0 var(--bds-void)" : `1px 1px 0 var(--bds-void), 0 0 6px ${color}`};white-space:nowrap;z-index:5;`; |
| el.textContent = text; |
| return el; |
| } |
|
|
| |
| function station(x, y) { |
| const el = document.createElement("div"); |
| el.style.cssText = `position:absolute;left:${x}px;top:${y}px;width:0;height:0;z-index:5;`; |
| for (let i = 2; i < arguments.length; i++) if (arguments[i]) el.appendChild(arguments[i]); |
| return el; |
| } |
|
|
| window.BDSChibi = { CHARS, chibi, topSprite, personHTML, desk, tag, station, pixelGlyph, glyphHTML, GLYPHS }; |
| })(); |
|
|