diff --git a/content/kuroneko.tree b/content/kuroneko.tree new file mode 100644 index 0000000..52a9f58 --- /dev/null +++ b/content/kuroneko.tree @@ -0,0 +1,7 @@ +%% title = "back porch" + scripts = ["components/chat.js"] + styles = ["components/chat.css"] + +% template = true + cast = "chat js" +- {% include_static chat/kuroneko.json %} diff --git a/static/character/coco/eyes_closed.svg b/static/character/coco/eyes_closed.svg new file mode 100644 index 0000000..67e67ab --- /dev/null +++ b/static/character/coco/eyes_closed.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/static/character/coco/neutral.svg b/static/character/coco/neutral.svg new file mode 100644 index 0000000..e8f1ee3 --- /dev/null +++ b/static/character/coco/neutral.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/static/chat/kuroneko.json b/static/chat/kuroneko.json new file mode 100644 index 0000000..b804f78 --- /dev/null +++ b/static/chat/kuroneko.json @@ -0,0 +1,322 @@ +{ + "nodes": { + "init": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "…", + "then": "initQuestion" + }, + "initQuestion": { + "kind": "ask", + "questions": [{ "content": "Kitty?", "then": "kitty" }] + }, + "kitty": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "I'm no ordinary cat.", + "then": "introductions" + }, + "introductions": { + "kind": "ask", + "questions": [ + { + "content": "…woah! You speak!", + "then": "introductions.youSpeak" + }, + { "content": "Certainly.", "then": "introductions.certainly" } + ] + }, + "introductions.youSpeak": { + "kind": "say", + "character": "coco", + "content": "Yeah. No clue what's so surprising about that. I mean, I've spoken for as long as I remember! But you're not the first person I've met that was surprised that a tiny thing like me could speak.", + "then": "introductions.youSpeak2" + }, + "introductions.youSpeak2": { + "kind": "ask", + "questions": [ + { + "content": "Who was the other?", + "then": "introductions.youSpeak.theOther" + }, + { + "content": "I mean, obviously.", + "then": "introductions.youSpeak.anyways" + } + ] + }, + "introductions.youSpeak.theOther": { + "kind": "say", + "character": "coco", + "content": "My owner. I've never seen someone this freaked out in my life!", + "then": "introductions.youSpeak.theOther2" + }, + "introductions.youSpeak.theOther2": { + "kind": "ask", + "questions": [ + { + "content": "I'm not surprised at all.", + "then": "introductions.youSpeak.anyways" + }, + { + "content": "What about me?", + "then": "introductions.youSpeak.theOther.whatAboutMe" + }, + { + "content": "Who's your owner?", + "then": "introductions.youSpeak.theOther.owner" + } + ] + }, + "introductions.youSpeak.theOther.whatAboutMe": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "You're a brave soul. Us cats can feel that. Nothing about your reaction came off as being scared.", + "then": "introductions.youSpeak.theOther.whatAboutMe2" + }, + "introductions.youSpeak.theOther.whatAboutMe2": { + "kind": "ask", + "questions": [ + { + "content": "Glad to hear that.", + "then": "introductions.youSpeak.anyways" + } + ] + }, + "introductions.youSpeak.theOther.owner": { + "kind": "say", + "character": "coco", + "content": "I'd ask her to introduce herself, but she hasn't been around lately. I believe she's out on a trip or something. No clue what the trip's about or when she'll be back.", + "then": "introductions.youSpeak.theOther.owner2" + }, + "introductions.youSpeak.theOther.owner2": { + "kind": "ask", + "questions": [ + { + "content": "\"She?\" You mean, your owner isn't liquidex?", + "then": "introductions.youSpeak.theOther.owner3" + }, + { + "content": "I see.", + "then": "introductions.youSpeak.anyways" + } + ] + }, + "introductions.youSpeak.theOther.owner3": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "No.", + "then": "introductions.youSpeak.theOther.owner4" + }, + "introductions.youSpeak.theOther.owner4": { + "kind": "ask", + "questions": [ + { + "content": "I see.", + "then": "introductions.youSpeak.anyways" + } + ] + }, + "introductions.youSpeak.anyways": { + "kind": "say", + "character": "coco", + "content": "Anyways. Is there anything in particular you're looking for?", + "then": "introductions.whatAreYouLookingFor" + }, + "introductions.certainly": { + "kind": "say", + "character": "coco", + "content": "Interesting to see you keeping your composure after meeting a speaking cat.", + "then": "introductions.certainly2" + }, + "introductions.certainly2": { + "kind": "ask", + "questions": [ + { + "content": "I see no reason to be upset. I've seen weirder things.", + "then": "introductions.certainly3" + } + ] + }, + "introductions.certainly3": { + "kind": "say", + "character": "coco", + "content": "Well anyways, while my owner is out, why don't we have a little chat?", + "then": "introductions.whatAreYouLookingFor" + }, + "introductions.whatAreYouLookingFor": { + "kind": "ask", + "questions": [ + { + "content": "Tell me more about yourself.", + "then": "aboutYourself" + }, + { + "content": "Tell me more about this place.", + "then": "aboutPlace" + } + ] + }, + "aboutYourself": { + "kind": "say", + "character": "coco", + "content": "I'm Coco, a black cat that lives in the treehouse. I like scritches and treats. One day I climbed into the treehouse and got lost. Fortunately my fine owner lady found me and took good care of me! Nowadays I live a happy life with the treefolk.", + "then": "aboutYourself2" + }, + "aboutYourself2": { + "kind": "ask", + "questions": [ + { + "content": "(scritch Coco)", + "then": "aboutYourself2.scritch" + }, + { + "content": "Are you friends with the treefolk?", + "then": "aboutYourself2.friends" + }, + { + "content": "You said earlier you're no ordinary cat. What did you mean?", + "then": "aboutYourself2.extraordinary" + }, + { + "content": "That's all I wanted to know about you, thanks.", + "then": "introductions.whatAreYouLookingFor" + } + ] + }, + "aboutYourself2.scritch": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "*purrs*", + "then": "aboutYourself2" + }, + "aboutYourself2.friends": { + "kind": "say", + "character": "coco", + "content": "I'm just here for the warmth, food, and scritches. The people here could just as easily be someone else. This is just a nice place to live, so I'm not complaining.", + "then": "aboutYourself2" + }, + "aboutYourself2.extraordinary": { + "kind": "say", + "character": "coco", + "content": "I speak. Is there anything more to say?", + "then": "aboutYourself2.extraordinary2" + }, + "aboutYourself2.extraordinary2": { + "kind": "ask", + "questions": [ + { + "content": "Well, that's the obvious part. Is there anything else?", + "then": "aboutYourself2.extraordinary3" + } + ] + }, + "aboutYourself2.extraordinary3": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "…Maybe.", + "then": "aboutYourself2" + }, + "aboutPlace": { + "kind": "say", + "character": "coco", + "content": "This is the back porch of the house. What do you think?", + "then": "aboutPlace.definition" + }, + "aboutPlace.definition": { + "kind": "ask", + "questions": [ + { + "content": "It's empty.", + "then": "aboutPlace.definition.empty" + }, + { + "content": "It's wild.", + "then": "aboutPlace.definition.wild" + }, + { + "content": "It's odd.", + "then": "aboutPlace.definition.odd" + }, + { + "content": "It's even.", + "then": "aboutPlace.definition.even" + }, + { + "content": "What even are these answers?", + "then": "aboutPlace.definition.what" + } + ] + }, + "aboutPlace.definition.empty": { + "kind": "set", + "fact": "kuroneko/empty", + "then": "aboutPlace.definition.thankYou" + }, + "aboutPlace.definition.wild": { + "kind": "set", + "fact": "kuroneko/wild", + "then": "aboutPlace.definition.thankYou" + }, + "aboutPlace.definition.odd": { + "kind": "set", + "fact": "kuroneko/odd", + "then": "aboutPlace.definition.thankYou" + }, + "aboutPlace.definition.even": { + "kind": "set", + "fact": "kuroneko/even", + "then": "aboutPlace.definition.thankYou" + }, + "aboutPlace.definition.what": { + "kind": "set", + "fact": "kuroneko/what", + "then": "aboutPlace.definition.thankYou2" + }, + "aboutPlace.definition.thankYou": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "Yeah, I think I agree.", + "then": "aboutPlace.thatsIt" + }, + "aboutPlace.definition.thankYou2": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "I just asked you what you think about how the back porch looks. That's all.", + "then": "aboutPlace.theWhat" + }, + "aboutPlace.thatsIt": { + "kind": "ask", + "questions": [ + { + "content": "That's it? You're not gonna tell me anything else?", + "then": "aboutPlace.end" + } + ] + }, + "aboutPlace.theWhat": { + "kind": "ask", + "questions": [ + { + "content": "Seriously though, where in the world did that come from.", + "then": "aboutPlace.end" + } + ] + }, + "aboutPlace.end": { + "kind": "say", + "character": "coco", + "expression": "eyes_closed", + "content": "…", + "then": "introductions.whatAreYouLookingFor" + } + } +} diff --git a/static/css/components/chat.css b/static/css/components/chat.css index 2aaaf61..bd2f13e 100644 --- a/static/css/components/chat.css +++ b/static/css/components/chat.css @@ -14,7 +14,9 @@ th-chat { } th-chat-said { - display: block; + display: flex; + flex-direction: row; + align-items: center; border: 1px solid var(--border-1); padding: 12px 16px; @@ -22,6 +24,15 @@ th-chat-said { border-radius: 8px; max-width: 60%; + + &>.picture { + padding-right: 16px; + align-self: baseline; + } + + &>.text-container { + display: inline-block; + } } th-chat-asked { diff --git a/static/js/components/chat.js b/static/js/components/chat.js index 2e1f5ce..5bcdaed 100644 --- a/static/js/components/chat.js +++ b/static/js/components/chat.js @@ -1,10 +1,17 @@ import { addSpell, spell } from "treehouse/spells.js"; import { Branch } from "treehouse/tree.js"; +const characters = { + coco: { + name: "Coco", + }, +} + const persistenceKey = "treehouse.chats"; let persistentState = JSON.parse(localStorage.getItem(persistenceKey)) || {}; persistentState.log ??= {}; +persistentState.facts ??= {}; savePersistentState(); function savePersistentState() { @@ -38,14 +45,25 @@ class Chat extends HTMLElement { customElements.define("th-chat", Chat); class Said extends HTMLElement { - constructor({ content }) { + constructor({ content, character, expression }) { super(); this.content = content; + this.character = character; + this.expression = expression ?? "neutral"; } connectedCallback() { - this.innerHTML = this.content; - this.dispatchEvent(new Event(".animationsComplete")); + this.picture = new Image(64, 64); + this.picture.src = `${TREEHOUSE_SITE}/static/character/${this.character}/${this.expression}.svg`; + this.picture.classList.add("picture"); + this.appendChild(this.picture); + + this.textContainer = document.createElement("span"); + this.textContainer.innerHTML = this.content; + this.textContainer.classList.add("text-container"); + this.appendChild(this.textContainer); + + this.dispatchEvent(new Event(".textFullyVisible")); } } @@ -100,8 +118,8 @@ class ChatState { } say(_, node) { - let said = new Said({ content: node.content }); - said.addEventListener(".animationsComplete", _ => this.exec(node.then)); + let said = new Said({ content: node.content, character: node.character, expression: node.expression }); + said.addEventListener(".textFullyVisible", _ => this.exec(node.then)); this.container.appendChild(said); } @@ -127,6 +145,11 @@ class ChatState { return questions; } + set(_, node) { + persistentState.facts[node.fact] = true; + this.exec(node.then); + } + end() {} interact(interaction) {