Your browser lacks required capabilities. Please upgrade it or switch to another to continue.
Loading…
The kitchen table has the usual spread. You grab a bowl and a box of cereal, then reconsider.
<<img "kitchen">>
<<link "Sit down with the family for a few minutes">>
<<goto "BreakfastFamily">>
<</link>>
<<link "Cereal, easy and fast">>
<<goto "WalkToSchool">>
<</link>>
<<link "Skip it, you're already running late">>
<<goto "WalkToSchool">>
<</link>>
Either way, you're thinking about tonight more than breakfast. Jordan promised the next session would be worth the wait.<<set _who to either("father", "mother", "brother")>>
<<if _who is "father">>
<<img "family_father_breakfast">>
Mark's already at the table, coffee in hand, half-reading something on his phone. He glances up, asks how the week's shaping up, and actually listens to the answer for once. It's not much, but it's a nice way to start the day.
<<elseif _who is "mother">>
<<img "family_mother_breakfast">>
Your mom's got breakfast half-made before you even sit down, sliding a plate over without asking if you want one. She asks about school, about the group, about whether you're sleeping enough, the usual rundown, delivered with actual warmth.
<<else>>
<<img "family_brother_breakfast">>
Your brother's already inhaling cereal like it's a competitive sport, and he tries to rope you into an argument about something completely inconsequential before you've even sat down. It's stupid. It helps anyway.
<</if>>
You're thinking about tonight more than breakfast. Jordan promised the next session would be worth the wait.
[[Head to school->WalkToSchool]]<<set _returnPassage to $characterSheetReturn>>
<<set _features to ["face", "chest", "waist", "rear", "hair", "skin"]>>
<div class="parchment-sheet">
<h2><<print $player.character>></h2>
<p class="parchment-subtitle"><<print $player.class>></p>
<p class="parchment-stats">
<span>Race</span>Human
<span>Gender</span><<genderLabel>>
<span>Session</span><<print $currentEpisode>>
<span>HP</span><<print $partyHp.player.current>>/<<print $partyHp.player.maxHp>>
</p>
<<if $bodyFeatures.voice gt 0>>
<p class="parchment-subtitle">Voice sample: <<voiceSample $bodyFeatures.voice>></p>
<</if>>
<div class="parchment-grid">
<<for _f range _features>>
<<set _stage to $bodyFeatures[_f]>>
<<if _f is "skin" and _stage is not 0>>
<<set _stage to Math.max(2, _stage - (_stage % 2))>>
<</if>>
<<if _stage gt 0>>
<<set _img to "sheet_" + _f + "_stage" + _stage>>
<<else>>
<<set _img to "sheet_" + _f + "_stage0">>
<</if>>
<<set _label to _f.charAt(0).toUpperCase() + _f.slice(1)>>
<div>
<<img _img>>
<p><<= _label>></p>
</div>
<</for>>
</div>
<<if $sessionItemsHeld.length gt 0>>
<h3 style="text-align:center; font-variant:small-caps; letter-spacing:0.05em; opacity:0.75; margin:1em 0 0.5em;">[[Carried Items->SessionInventory]]</h3>
<<sessionItemList "compact">>
<</if>>
</div>
<div class="parchment-companion-row">
<div class="parchment-sheet parchment-sheet-companion">
<h2>Sam</h2>
<p class="parchment-subtitle">Fighter</p>
<<img "sam_portrait">>
<p class="parchment-stats">
<span>HP</span><<print $partyHp.sam.current>>/<<print $partyHp.sam.maxHp>>
</p>
</div>
<div class="parchment-sheet parchment-sheet-companion">
<h2>Max</h2>
<p class="parchment-subtitle">Ranger</p>
<<img "max_portrait">>
<p class="parchment-stats">
<span>HP</span><<print $partyHp.max.current>>/<<print $partyHp.max.maxHp>>
</p>
</div>
</div>
<p><<link "Sit down and get ready">>
<<goto _returnPassage>>
<</link>></p><p style="text-align:center;"><strong>Before you begin</strong></p>
<p>TFDungeon is an interactive fiction story written for adults. It contains an ongoing physical and psychological transformation arc, frank descriptions of a changing body, and sensual/intimate content at points in the story. Nothing here is meant for readers under 18. None of the characters are minors: the protagonist and their friends are college-age adults throughout.</p>
<p>By continuing, you're confirming that you're 18 or older and comfortable with this kind of content.</p>
[[I understand, continue->WakeUp]]
[[I do not wish to view this->ContentGateDecline]]<p style="text-align:center;"><strong>That's okay.</strong></p>
<p>TFDungeon contains 18+ content and isn't for everyone. Thanks for checking it out, and feel free to close this tab.</p>
<p><em>(If you reached this page by mistake, reload or restart the game to return to the start.)</em></p><<if $daySceneStep is 0>>
The day stretches out ahead of you: classes first, then whatever's left of the afternoon before your shift.
<</if>>
<<if $daySceneStep lt 2>>
<<dayScenePicker>>
<<romanceScenePicker "nadia" setup.nadiaScenePool $nadiaSeen>>
<<romanceScenePicker "theo" setup.theoScenePool $theoSeen>>
<<else>>
<<goto "HomeHub">>
<</if>><<img "day_art_class_scene">>
Art class, technically a still-life assignment. You end up sketching something that looks suspiciously like a fantasy pendant instead, and don't notice until the teacher's already circling the room.
<<link "Cover it up fast">>
<<goto "Day_ArtClass_Cover">>
<</link>>
<<link "Just let her see it">>
<<goto "Day_ArtClass_Show">>
<</link>><<img "day_artclass_cover">>
You flip to a blank page just in time, heart doing something ridiculous over a sketch that's genuinely not a big deal. The teacher passes without comment.
[[Continue->DayHub]]<<img "day_artclass_show">>
She pauses at your desk, studies the sketch a beat longer than the assignment warrants, and says, "that's lovely, very delicate linework," before moving on. It wasn't a criticism. That's the part that sticks with you.
[[Continue->DayHub]]<<img "day_assembly_scene">>
A whole-campus town hall about something you stop listening to within the first two minutes. Sam passes you a note that just says "how long has this been going on" with an increasingly detailed tally mark count in the corner.
<<link "Write something back">>
<<goto "Day_Assembly_Note">>
<</link>>
<<link "Actually try to pay attention">>
<<goto "Day_Assembly_Listen">>
<</link>><<img "day_assembly_listen">>
You actually tune in for once. It's exactly as forgettable as Sam's note implied, and you couldn't repeat the topic five minutes later if asked.
[[Continue->DayHub]]<<img "day_assembly_note">>
You scrawl back a number that's definitely too high to be accurate and pass it over. Sam has to bite down a laugh loud enough to get you both a look from a teacher three rows up.
[[Continue->DayHub]]<<img "day_club_scene">>
Club meeting after last period, nothing you're especially invested in, but it beats going straight home, and Jordan's there too, doodling in the margins of the sign-in sheet instead of paying attention. Someone's collecting dues for a group snack fund.
<<link "Chip in">>
<<goto "Day_Club_ChipIn">>
<</link>>
<<link "Pass this time">>
<<goto "Day_Club_Pass">>
<</link>><<img "day_club_chipin">>
You toss in a couple dollars without much thought. Jordan raises an eyebrow, impressed you actually care about the snack fund.
<<set $money to $money - 2>>
[[Continue->DayHub]]<<img "day_club_pass">>
You wave it off. Nobody holds it against you; the snack fund has survived worse.
[[Continue->DayHub]]<<img "day_detention_scene">>
Mandatory office hours, for something that felt like a lot less of an offense in the moment. Forty-five minutes of silence broken only by a clock that seems to be actively slowing down out of spite. You could actually get some homework done, or just let the time pass.
<<link "Get homework done">>
<<goto "Day_Detention_Homework">>
<</link>>
<<link "Just stare at the clock">>
<<goto "Day_Detention_Stare">>
<</link>><<img "day_detention_homework">>
You make the best of it and actually knock out most of tonight's homework, which feels like a strange kind of victory to walk out with.
[[Continue->DayHub]]<<img "day_detention_stare">>
You let the forty-five minutes pass at their own excruciating pace, thoughts drifting somewhere else entirely by the halfway mark.
[[Continue->DayHub]]<<img "day_family_scene">>
You catch a few minutes with whoever's home before you have to head out, nothing deep, just someone asking about your day, waiting on an actual answer this time.
<<link "Actually tell them about your day">>
<<goto "Day_Family_Share">>
<</link>>
<<link "Give the usual noncommittal answer">>
<<goto "Day_Family_Deflect">>
<</link>><<img "day_family_deflect">>
"Fine, same as always," you say, which isn't a lie exactly, just not the whole answer either. Nobody pushes.
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_family_share">>
You actually talk for a couple minutes, nothing important, just the shape of an ordinary day. It's a small, easy kind of good, the kind you don't notice you needed until it's happening.
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_group_project_scene">>
You end up paired with Jordan for a history project neither of you has started. He spends most of the working time sketching a rough map on the back of the worksheet instead, "for the campaign," he says, like that's an acceptable excuse to a teacher.
<<link "Actually start the project">>
<<goto "Day_GroupProject_Work">>
<</link>>
<<link "Let him keep sketching, wing it later">>
<<goto "Day_GroupProject_Sketch">>
<</link>><<img "day_groupproject_sketch">>
You let it slide and lean over to actually look at the map instead. It's good, unsettlingly good, in a way that makes you wonder how much of it he's making up as he goes.
[[Continue->DayHub]]<<img "day_groupproject_work">>
You drag Jordan back on task, mostly by starting the outline yourself and handing him a pen. He grumbles but actually contributes, and you both leave with something halfway respectable.
[[Continue->DayHub]]<<img "day_gym_scene">>
Gym is dodgeball again, which mostly means watching Sam get absolutely wrecked in the first thirty seconds and refuse to sit out anyway. You've still got a ball in hand and a clear shot at redemption for the team.
<<link "Actually try to win">>
<<goto "Day_Gym_Try">>
<</link>>
<<link "Coast through it">>
<<goto "Day_Gym_Coast">>
<</link>><<img "day_gym_coast">>
You drift to the back of the group and let the ball go by twice, content to run out the clock without incident.
[[Continue->DayHub]]<<img "day_gym_try">>
You actually commit, and it almost pays off. You take out two people before getting nailed yourself. Sam cheers from the sideline like you won something.
[[Continue->DayHub]]<<img "day_hallway_scene">>
Between classes, the hallway is its usual crush of noise and shoulders. You catch Max for about ten seconds before everyone scatters toward their next class, long enough to see something crumpled on the floor near the lockers.
<<link "Stop and check it out">>
<<goto "Day_Hallway_Check">>
<</link>>
<<link "Keep moving, you'll be late">>
<<goto "Day_Hallway_Skip">>
<</link>><<img "day_hallway_check">>
It's a crumpled five dollar bill, no wallet in sight, no obvious owner in the crowd already moving past. You pocket it, a little guiltily.
<<set $money to $money + 5>>
[[Continue->DayHub]]<<img "day_hallway_skip">>
You let the bell win and keep moving. Whatever it was, someone else will find it.
[[Continue->DayHub]]<<img "day_homework_scene">>
You spread your homework out across the kitchen table, more out of habit than any real intention of finishing it before your shift.
<<link "Actually push through it">>
<<goto "Day_Homework_Push">>
<</link>>
<<link "Give up after ten minutes">>
<<goto "Day_Homework_GiveUp">>
<</link>><<img "day_homework_giveup">>
You make it ten minutes before deciding future-you can deal with it. Future-you is going to be annoyed about this.
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_homework_push">>
You actually grind through it, surprising yourself. One less thing hanging over the rest of the week.
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_room_scene">>
You end up on your laptop between everything else, half-doing homework and half not. A tab full of D&D forum threads open next to it: someone arguing about whether clerics are underpowered, someone else posting fan art of a cleric who looks uncomfortably close to how you picture Kaelen these days.
<<link "Click through the fan art">>
<<goto "Day_Laptop_FanArt">>
<</link>>
<<link "Close the tab, get back to homework">>
<<goto "Day_Laptop_Close">>
<</link>><<img "day_laptop_close">>
You close the tab a little faster than the others and actually get some homework done, mostly to avoid thinking about why you closed it that fast.
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_laptop_fanart">>
You click through more of it than you mean to. It's a stranger's art of a character who's never existed outside your table, and it looks more like you than it has any right to.
<<if $bodyFeatures.face gte 1 and $bodyFeatures.face lte 2>>
You end up staring at one piece in particular a beat too long, comparing it against your own reflection in the dark part of the screen without quite meaning to.
<<elseif $bodyFeatures.face gte 3>>
You close the door first, then go back to it, scrolling slower now, lingering on the ones that get her face right, feeling something low and warm about how little imagination it actually takes anymore. It's not just recognition. It's want, quiet and a little embarrassing, and you let yourself sit in it instead of closing the tab.
<</if>>
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_lecture_scene">>
Class drags on. You take notes you'll never look at again and watch the clock more than the board. The teacher calls on you out of nowhere, and you clearly weren't listening.
<<link "Bluff an answer">>
<<goto "Day_Lecture_Bluff">>
<</link>>
<<link "Admit you weren't paying attention">>
<<goto "Day_Lecture_Honest">>
<</link>><<img "day_lecture_bluff">>
You piece together something vague that technically uses the right vocabulary. The teacher looks unconvinced but moves on anyway, unwilling to spend more class time on it.
[[Continue->DayHub]]<<img "day_lecture_honest">>
"Sorry, lost the thread," you admit. A couple people snicker, but the teacher just repeats the question more slowly, like that's the actual problem.
[[Continue->DayHub]]<<img "day_library_scene">>
A quiet free period in the library, ostensibly for studying. You get about ten minutes of actual work done before you're just staring at the same page, thinking about anything but the page. There's a fantasy paperback on the returns cart that catches your eye.
<<link "Grab the paperback instead">>
<<goto "Day_Library_Paperback">>
<</link>>
<<link "Force yourself to actually study">>
<<goto "Day_Library_Study">>
<</link>><<img "day_library_paperback">>
You give up on the homework and lose the rest of the period to three chapters of a fantasy novel with a cover that looks suspiciously like the Frostbound Keep. Worth it.
[[Continue->DayHub]]<<img "day_library_study">>
You force yourself through it, one paragraph at a time, and actually finish the reading for once. Small victories.
[[Continue->DayHub]]<<img "day_locker_room_scene">>
Changing for gym, the locker room loud with everyone else's conversations. You catch a fragment of two people you don't know arguing about whether a game they played this weekend was rigged, and can't help but think about your own table for a second too long.
<<link "Eavesdrop a little longer">>
<<goto "Day_LockerRoom_Eavesdrop">>
<</link>>
<<link "Tune it out and get changed">>
<<goto "Day_LockerRoom_Ignore">>
<</link>><<img "day_lockerroom_eavesdrop">>
You linger a beat too long, half-hoping for details that would make it feel less like a coincidence. It's nothing, just two people arguing about a video game, not a tabletop session, not anything close to your table. You feel a little ridiculous for hoping otherwise.
[[Continue->DayHub]]<<img "day_lockerroom_ignore">>
You let it go and finish getting changed. Not everything has to mean something.
[[Continue->DayHub]]<<img "day_lunch_scene">>
Lunch is Sam complaining about a pop quiz and Jordan sketching something in the margins of a notebook he angles just slightly out of view. Sam finally notices and tries to get a look.
<<link "Help Sam peek at the sketch">>
<<goto "Day_Lunch_Peek">>
<</link>>
<<link "Let Jordan have his privacy">>
<<goto "Day_Lunch_Leave">>
<</link>><<img "day_lunch_leave">>
You wave Sam off. Jordan shoots you a grateful look and keeps sketching, unbothered, for the rest of the period.
[[Continue->DayHub]]<<img "day_lunch_peek">>
Between the two of you, Jordan doesn't stand a chance. It's a rough map: some fortress or keep, corridors marked in a shorthand only he understands. "Campaign stuff," he mutters, closing the notebook a beat too late.
[[Continue->DayHub]]<<img "day_nadia_escalation2">>
She catches your hand before you've fully thought about reaching for hers, not surprised by it, just quicker than you expected, lacing her fingers through yours like it's a question she's already decided the answer to. Neither of you says anything about it. You keep walking like it's completely normal, hands together, and it is, and it also very much isn't, and you're both pretending equally hard that it's the first one.
<<set $nadia.attraction to $nadia.attraction + 1>>
<<if $nadia.trust gte 3 and $nadia.attraction gte 4>>
At the corner where you'd usually split off in different directions, she doesn't let go right away. She holds on a beat longer, looking at you instead of the street, like she's deciding whether to say something and then deciding not to. "See you tomorrow," she says instead, and it lands like it means more than tomorrow.
<</if>>
[[Continue->DayHub]]<<img "hallway">>
Group project assignments come out, and you end up paired with Nadia, of all people. Small enough school that it was bound to happen eventually. You claim a table in the corner and get through more actual work than either of you expected to, mostly because neither one wants to be the reason the other has to redo it later.
She laughs at something you say, a real laugh, not a polite one, and it goes on a half-second longer than the joke really earned, long enough that you both notice it and neither one comments.
<<set $nadia.trust to $nadia.trust + 1>>
<<if $nadia.trust gte 2>>
<<set $nadia.attraction to $nadia.attraction + 1>>
<</if>>
[[Continue->DayHub]]<<img "hallway">>
Someone at the next table over asks, quietly, if you've got a highlighter she can borrow. You do, and hand it over without really looking up. "Lifesaver," she says, and something about the way she says it makes you actually look up this time.
You end up talking for a few minutes, low-voiced, the way you do in a library. Turns out you're both stuck on the same professor's reading list. She introduces herself as Nadia. You don't manage much more than names and complaints about the workload before she has to go, but it's a pleasant enough few minutes that you notice you're a little disappointed when she does.
<<set $nadia.trust to $nadia.trust + 1>>
[[Continue->DayHub]]<<img "hallway">>
Nadia ends up at the edge of your usual table, dragged over by a mutual acquaintance who then immediately wanders off, leaving the two of you to make conversation or not. You make conversation.
It's easy. She's got a dry sense of humor that catches you off guard more than once, and by the time lunch is over you've half-agreed to see her around more, in the vague, noncommittal way people say that and sometimes actually mean it.
<<if $nadia.trust lt 2>>
<<set $nadia.trust to $nadia.trust + 1>>
<<else>>
<<set $nadia.attraction to $nadia.attraction + 1>>
<</if>>
[[Continue->DayHub]]<<img "day_schoolyard_scene">>
Nadia waves you over from across the quad, no particular reason for it beyond apparently wanting the company. You end up sitting in the grass for longer than either of you has time for, not talking about anything that matters, in the specific way that somehow matters anyway.
<<set $nadia.attraction to $nadia.attraction + 1>>
<<if $nadia.trust gte 2 and $nadia.attraction gte 2>>
At some point the conversation runs out and neither of you rushes to fill it. You just sit there, comfortable, her shoulder close enough to yours that it would take almost nothing to close the distance. Neither of you does. You both seem to clock that the option's there anyway.
<<if $nadia.trust gte 3 and $nadia.attraction gte 3>>
[[Let the moment stretch a little longer->Day_Nadia_Escalation2]]
<<else>>
[[Continue->DayHub]]
<</if>>
<<else>>
[[Continue->DayHub]]
<</if>><<img "day_room_scene">>
You get a few minutes alone in your room before heading out, just enough to lie on the bed and stare at the ceiling, not really thinking about anything in particular.
<<if $electronicsOwned.includes("headphones")>>
You put your headphones on more out of habit than any real need to drown anything out, and just let them sit there, silent, for a minute.
<</if>>
Your character sheet's sitting on the desk where you left it, Kaelen's stats scribbled in the margins.
<<link "Look it over">>
<<goto "Day_Room_Sheet">>
<</link>>
<<link "Leave it, just rest">>
<<goto "Day_Room_Rest">>
<</link>><<img "day_room_scene">>
<<set _maxStage to Math.max.apply(null, Object.keys($bodyFeatures).filter(function (k) { return k !== "voice"; }).map(function (k) { return $bodyFeatures[k]; }))>>
<<if _maxStage is 0>>
You open the notebook you keep meaning to actually use and write a few lines about nothing in particular: the session, mostly, and how long until the next one.
<<elseif _maxStage lte 2>>
You write more than you meant to, circling back a couple of times to reread a sentence about "feeling different lately" before deciding not to cross it out.
<<else>>
You end up writing about it directly for once, instead of around it. Not explaining it, exactly, since you don't have the words for that yet, just admitting on paper that something's been changing and that some part of you isn't in a hurry for it to stop. You close the notebook before you can second-guess that last line.
<</if>>
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_room_rest">>
You leave it where it is and just let yourself have a few quiet minutes before the rest of the day catches up with you.
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_room_sheet">>
You skim it out of habit, the way you'd check your phone. The handwriting near the bottom doesn't quite look like yours anymore, and you can't say when that changed.
<<if $bodyFeatures.face gte 1 and $bodyFeatures.face lte 2>>
You catch yourself rereading the character description at the top longer than you need to, not checking the stats, just looking at the words describing her face, and wondering, not for the first time, how much of that description you're still making up.
<<elseif $bodyFeatures.face gte 3>>
You end up just sitting with it for a while, alone, door closed, tracing a finger along the description like it's something you could touch. It's not really about the sheet anymore. There's a warmth to it you don't examine too closely: the thought of not just playing her, but becoming her, settling somewhere low and unhurried while nobody's around to see your face do whatever it's doing right now.
<</if>>
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "shower_steam">>
<<set _maxStage to Math.max.apply(null, ["skin", "chest", "waist", "rear"].map(function (f) { return $bodyFeatures[f]; }))>>
<<if _maxStage is 0>>
You duck into the shower before anything else, quick and functional, the way you always have. Nothing about it takes any longer than it needs to.
<<elseif _maxStage lte 2>>
You take a little longer than usual under the water, not for any reason you'd admit to out loud, just a hand trailing somewhere it didn't used to pause, a beat of noticing before you catch yourself and finish up like normal.
<<elseif _maxStage lte 3>>
You linger. The water's long since stopped being about getting clean and started being about something closer to just being in your own skin for a few minutes: a hand resting where it wants to, unhurried, no reason to explain it to anyone, least of all yourself.
<<else>>
You don't rush this. Whatever this is now, the weight of it, the shape of it under your own hands, you let yourself actually feel it instead of getting through it, and for a few minutes the water running over you is the least interesting part of the moment. Nobody's waiting. You take exactly as long as you want.
<</if>>
<<link "Continue">>
<<goto $dayReturnTarget>>
<</link>><<img "day_schoolyard_scene">>
A free period outside on the quad, sun actually cooperating for once. Max is holding court near the benches, mid-story about something that definitely didn't happen the way it's being told.
<<link "Call out the exaggeration">>
<<goto "Day_Schoolyard_CallOut">>
<</link>>
<<link "Let Max have the moment">>
<<goto "Day_Schoolyard_Listen">>
<</link>><<img "day_schoolyard_callout">>
"That's not how it happened," you say, and Max doesn't even try to argue, just grins wider and keeps going with the exact same story, completely unbothered.
[[Continue->DayHub]]<<img "day_schoolyard_listen">>
You let it ride. The story gets visibly more dramatic with each retelling, and by the third pass it barely resembles whatever actually happened.
[[Continue->DayHub]]<<img "day_substitute_scene">>
A teaching assistant covering for the professor today, which means the class collectively decides the lesson plan is optional. Max spends the entire period trying to convince the TA that today's assignment is "technically a tabletop game" and should be graded as such.
<<link "Back Max up">>
<<goto "Day_Substitute_Support">>
<</link>>
<<link "Stay out of it">>
<<goto "Day_Substitute_Stay">>
<</link>><<img "day_substitute_stay">>
You keep your head down and let Max fight that battle alone. It doesn't work, but the TA clearly respects the effort.
[[Continue->DayHub]]<<img "day_substitute_support">>
You back Max up with a straight face, citing made-up rules with real conviction. The TA is not convinced, but she's entertained enough to let it slide for the rest of the period.
[[Continue->DayHub]]<<img "day_theo_escalation">>
He doesn't look away this time, not right away. He just lets the silence sit a beat too long before covering it with a joke that doesn't quite land, like he heard himself get caught. "Anyway," he says, and doesn't finish the thought, and you both let it go without actually letting it go.
<<set $theo.attraction to $theo.attraction + 1>>
<<if $theo.trust gte 3 and $theo.attraction gte 3>>
Neither of you says what just happened. You're both fairly sure the other one noticed anyway.
<</if>>
[[Continue->DayHub]]<<img "day_gym_scene">>
Theo's spotting for someone else at the squat rack when he clocks you across the gym floor. "Hey, it's you," he says, like running into you here settles some kind of bet with himself. "Riley's cousin's friend. I never got your actual name at that party."
You end up trading a few minutes of gym small talk, mostly him giving unsolicited but not-wrong form advice, before he has to get back to spotting.
<<set $theo.trust to $theo.trust + 1>>
[[Continue->DayHub]]<<img "hallway">>
Theo falls into step beside you between classes, unhurried about it in a way that suggests he wasn't actually headed this direction to begin with. "Small campus," he says. "Or I'm just bad at not noticing people twice."
The conversation doesn't go anywhere in particular: classes, Riley's chaos, whether the dining hall pizza counts as food. But he's easy to talk to, unbothered in a way that makes the walk shorter than it should feel.
<<set $theo.attraction to $theo.attraction + 1>>
[[Continue->DayHub]]<<img "day_library_scene">>
You find Theo camped out at a back table, textbook open to a page he's clearly not reading, more interested in flicking a pen cap across the table and catching it. "Don't tell anyone I'm capable of sitting still this long," he says, kicking the chair across from him out with his foot. "Sit. Save me from myself."
You end up talking longer than either of you planned, the textbook staying open to the same page the whole time.
<<set $theo.trust to $theo.trust + 1>>
[[Continue->DayHub]]<<img "day_lunch_scene">>
Theo waves you over to his table without much ceremony, sliding his tray over to make room like it was always the plan. "Sit. I promise I'm more interesting than whatever you were about to eat alone."
<<set $theo.trust to $theo.trust + 1>>
<<set $theo.attraction to $theo.attraction + 1>>
<<if $theo.trust gte 2 and $theo.attraction gte 2>>
Somewhere between the small talk and the second half of his fries migrating onto your tray without either of you commenting on it, you notice he's not as subtle about looking at you as he probably thinks he is. Neither of you brings it up. It's there anyway.
[[Let the moment sit->Day_Theo_Escalation]]
<<else>>
[[Continue->DayHub]]
<</if>><<img "day_hallway_scene">>
Answering a question in class, your voice catches halfway through the sentence, cracks up and sideways, nothing like the low, steady thing it used to be. A couple of heads turn.
"You good?" someone asks, not unkindly, already moving on before you can even answer.
You clear your throat and finish the sentence in whatever register it wants to be in today. Nobody brings it up again, which is somehow worse than if they had.
<<set $bodyFeatures.voice to $bodyFeatures.voice + 1>>
<<recalcTransformation>>
[[Continue->DayHub]]<h2>Diary</h2>
<<if $currentEpisode gt 1>><<Diary_E1>><</if>>
<<if $currentEpisode gt 2>><<Diary_E2>><</if>>
<<if $currentEpisode gt 3>><<Diary_E3>><</if>>
<<if $currentEpisode gt 4>><<Diary_E4>><</if>>
<<if $currentEpisode gt 5>><<Diary_E5>><</if>>
<<if $currentEpisode gt 6>><<Diary_E6>><</if>>
<<if $currentEpisode gt 7>><<Diary_E7>><</if>>
<<if $currentEpisode gt 8>><<Diary_E8>><</if>>
<<if $currentEpisode gt 9>><<Diary_E9>><</if>>
<<if $currentEpisode gte 10>><<Diary_E10>><</if>>
<<if $currentEpisode gt 10>><<Diary_E11>><</if>>
<<if $currentEpisode gt 11>><<Diary_E12>><</if>>
<<if $currentEpisode gt 12>><<Diary_E13>><</if>>
<<if $currentEpisode gt 13>><<Diary_E14>><</if>>
<<if $currentEpisode gt 14>><<Diary_E15>><</if>>
<h3>Where things stand</h3>
<<relationshipSummary "nadia" "Nadia">>
<<relationshipSummary "theo" "Theo">>
<<relationshipSummary "ashwyn" "Ashwyn">>
<<relationshipSummary "henderson" "Henderson">>
<<familyBondSummary>>
<p><em>Transformation:</em> <<genderLabel>></p>
<p><<link "Close the diary">>
<<goto "EveningHub">>
<</link>></p><<img "bedroom">>
Monday again, and the shrine's still sitting there in the back of your mind, quiet and cold and a little too well described for something nobody's ever seen.
<p><em>This is the end of the currently available content. Thanks for playing through Episode 1.</em></p>
<p><em>More is coming.</em></p><<img "packing_up_dice">>
Chairs scrape back. Sam starts gathering up snack wrappers, and Max is already arguing with Jordan about whether the guardian would've attacked if the perception check had failed.
<<print $player.name>> is still turning the session over, half-listening, when Jordan says something offhand while packing up the dice.
"Weird timing thing," Jordan says, dropping the heavy new die set into its box. "I only picked these up last week, some estate sale box lot, of all things. And then I sat down to write the shrine and just... the whole Mother Goddess angle showed up out of nowhere. Never even considered it before that."
"Huh," <<print $player.name>> says, not thinking much of it.
Jordan laughs it off. "Anyway. Same time next week?"
<<print $player.name>> nods, grabs the backpack, and heads out, already thinking, a little more than usual, about what a cleric of the Mother Goddess even prays for.
[[The week ahead->JobChoice]]<<img "day_family_scene">>
Dinner's the usual chaos: someone's phone keeps buzzing, someone else is retelling a story everyone's already heard, and for a minute none of it has anything to do with dice, jobs, or shrines.
[[Head into the living room->EveningHub]]<<img "day_room_scene">>
<p>You check your phone. The group chat's been busy.</p>
<<set _unseen to setup.groupChatLines.filter(function (l) { return !$groupChatSeen.includes(l.text); })>>
<<if _unseen.length is 0>>
<<set $groupChatSeen to []>>
<<set _unseen to setup.groupChatLines.slice(0)>>
<</if>>
<<set _line to _unseen[random(0, _unseen.length - 1)]>>
<<set $groupChatSeen.push(_line.text)>>
<p style="border-left: 3px solid #888; padding-left: 0.75em; margin: 1em 0;">
<strong><<= _line.speaker>>:</strong> <<= _line.text>>
</p>
<p>You lock the screen without replying, for now.</p>
[[Back->EveningHub]]The evening's yours until it isn't.
<<eveningFamilyList>>
<<if $electronicsOwned.includes("smartphone")>>
<p>[[Check the group chat->EveningGroupChat]]</p>
<<if $currentEpisode gte 11>>
<p>[[Scroll social media for a while->SocialFeed]]</p>
<</if>>
<</if>>
<<if $currentEpisode gt 1>>
<p>[[Read over your diary->Diary]]</p>
<</if>>
[[Head up to bed->GoToBed]]<<img "family_brother">>
Ryan's sprawled on the couch, controller in hand, deep into something loud he's clearly already beaten twice. "You're still terrible at this," you tell him, which only makes him try harder to prove otherwise.
He doesn't look up. "Bet you can't beat my score."
You probably can't. You watch him die once, curse at the TV like it's personal, and get right back into it before wandering off anyway.
<<if $electronicsOwned.includes("charger_cable")>>
Ryan spots your extra charging cable on the counter and steals it without asking, the way he steals everything. You let it go. You have a spare.
<</if>>
[[Back->EveningHub]]<<img "family_brother">>
"Oh, by the way," Ryan says, not looking up from his screen, like it's barely worth mentioning, "some guy in my party said something dumb about you online yesterday. Told him to shut up and muted him." A beat. "Anyway, you want in on this round or not?"
He says it so fast, so sideways, that you almost miss it was even a defense at all. No lecture, no big moment, just a fact reported and immediately buried under the next round starting.
<<set $familyBond.brother.closeness to $familyBond.brother.closeness + 1>>
<<set $familyBond.brother.awareness to $familyBond.brother.awareness + 1>>
[[Back->EveningHub]]<<img "family_brother">>
Ryan's at the kitchen table with a textbook open to a page he's clearly not reading, phone propped against his water glass instead. "Don't tell Mom," he says, without looking up, already assuming you're going to.
You sit with him for a few minutes anyway, half-helping, half just keeping him company while he complains about a teacher who "has it out for him specifically." By the time you leave he's actually gotten through a page and a half.
[[Back->EveningHub]]<<img "family_brother">>
Ryan's scrolling through a streaming menu with the specific, doomed energy of someone who's been doing this for twenty minutes and has landed on nothing. "Pick something," he says, handing you the remote like it's your problem now.
You pick something bad on purpose, mostly to see his reaction. He complains through the whole first ten minutes and doesn't turn it off once.
[[Back->EveningHub]]<<img "family_brother">>
Ryan's got music playing too loud from his room, door half open like he doesn't care who hears it or is specifically hoping someone will. You lean in the doorway. He turns it down exactly one notch, which from him counts as a concession.
"This is actually good," you admit, against your better judgment.
"I *know*," he says, delighted, and immediately starts explaining the entire discography of a band you didn't ask about, in more detail than the moment strictly requires.
[[Back->EveningHub]]<<img "family_brother">>
Ryan's mid-game, controller clicking away, when his eyes flick over for half a second longer than a glance usually lasts. He doesn't say anything about it, just goes quiet for one beat too many, then covers it with a joke about your reflexes being trash lately, voice pitched exactly like nothing happened.
"You gonna sit down or just stand there judging my K/D?" he says, already sliding a second controller across the couch without waiting for an answer.
<<set $familyBond.brother.closeness to $familyBond.brother.closeness + 1>>
<<set $familyBond.brother.awareness to $familyBond.brother.awareness + 1>>
[[Back->EveningHub]]<<img "family_brother">>
"Rematch," Ryan says, the second you walk in, like the conversation's been going on the whole time you were gone. You're not even sure what game he means.
Turns out he's still mad about a loss from what he insists was "days ago" and was actually last week. You humor him. He wins, loudly, and immediately demands you admit he's better, which you refuse to do purely on principle, which just makes him louder.
[[Back->EveningHub]]<<img "family_brother">>
You find Ryan in the kitchen, standing in front of the open fridge like it owes him an explanation. "There's nothing to eat," he says, holding a container of leftovers he's clearly about to eat.
"That's food."
"It's *old* food." He eats it anyway, standing up, not bothering with a plate. Somewhere between bites he asks how your day was, actually seems to half-listen to the answer, then goes right back to complaining about the fridge.
[[Back->EveningHub]]<<img "family_brother">>
Ryan hands you a second controller without asking if you want one, which is how most things get decided in this house. "You're going to lose," he informs you, already selecting a mode you've never played.
You do lose, badly, twice, while he narrates his own gameplay like a sports commentator having the best night of his career. The third round you almost win, and the look on his face when it happens is worth every previous loss.
[[Back->EveningHub]]<<img "family_father">>
Mark's on the couch, half-watching some game with the sound too low to actually follow. "Rough day?" he asks, not really expecting an answer, which is exactly the right amount of conversation for right now.
He asks, vaguely, how "the dice thing" is going. You give him the two-sentence version. He nods like he understood all of it.
[[Back->EveningHub]]<<img "family_father">>
Mark's headed out for something he calls "just a drive," which never actually has a destination. "Come if you want," he says, already grabbing his keys, not really waiting for an answer either way.
You end up in the passenger seat for twenty minutes of mostly silence and a radio station he's had memorized for a decade. Neither of you says much. It's better than it sounds.
[[Back->EveningHub]]<<img "family_father">>
Mark's in the garage with a cabinet hinge disassembled on the workbench, more parts laid out than the job should reasonably require. "Hand me that," he says, pointing vaguely at three different tools, none of which turn out to be the right one.
You hang around and hand him things, mostly wrong, while he narrates the repair to nobody in particular. It takes twice as long as it should and he seems happier for it.
[[Back->EveningHub]]<<img "family_father">>
Mark's got the hood up on nothing in particular, tinkering more for the sake of tinkering than because anything's actually broken. He doesn't say much, just hands you a wrench, nods at a bolt, lets the quiet do most of the talking, the way it usually does out here.
Near the end, still looking at the engine and not at you, he says it almost like an afterthought: "You've seemed different lately. Good different, I think." Then he's back to the bolt, like that was the whole conversation, and maybe it was.
<<set $familyBond.father.closeness to $familyBond.father.closeness + 1>>
<<set $familyBond.father.awareness to $familyBond.father.awareness + 1>>
[[Back->EveningHub]]<<img "family_father">>
Mark's out back doing something to the grill that isn't strictly maintenance so much as an excuse to stand outside with a tool in his hand. "It's making a noise," he explains, though it sounds fine to you.
You stand out there with him anyway while he pokes at it for a while, eventually declares it fixed with no visible evidence of having changed anything, and looks genuinely satisfied about it.
[[Back->EveningHub]]<<img "family_father">>
Mark's got the local news on, mostly for background noise, occasionally reacting to a story with a level of outrage that seems disproportionate to a segment about a new stoplight. "That intersection's been a disaster for years," he says, like it's personal.
You let him get it out of his system. He winds down eventually, asks how your week's going almost as an afterthought, and actually waits for the answer this time.
[[Back->EveningHub]]<<img "family_father">>
Mark's watching one of those evening trivia shows, shouting answers at the TV a half-second too late every time. "I knew that one," he insists, every single time, whether or not he actually said it before the contestant did.
You sit with him for a round, throwing out a few guesses of your own. You get one right before he does, and he pretends not to be bothered by it for exactly as long as it takes the next question to load.
[[Back->EveningHub]]<<img "family_father">>
Grill smoke drifting between you, Mark asks about "the dice thing" the way he always does: vague, genuine, half-listening for the parts that sound like a real story. Then, flipping something that doesn't need flipping yet, he adds, almost too casually to be planned: "You doing okay? With everything?"
He doesn't specify what "everything" means. You give him a vague answer. He accepts it without pushing, the grill hissing back into the silence, and somehow the not-pushing says more than a direct question would have.
<<set $familyBond.father.closeness to $familyBond.father.closeness + 1>>
<<set $familyBond.father.awareness to $familyBond.father.awareness + 1>>
[[Back->EveningHub]]<<img "family_father">>
Mark starts telling a story you've definitely heard before, something about a job he had in his twenties, a boss who was "a real piece of work," and you let him tell it anyway, since he clearly enjoys telling it more than you'd enjoy stopping him.
Halfway through he loses the thread of his own point, laughs about it, and just lets the story end wherever it ends. You never do find out what happened with the boss.
[[Back->EveningHub]]<<img "family_mother">>
Diane's at the kitchen table with a mug of tea and a stack of bills she's pretending to enjoy sorting through. She asks how the shift went, actually listens to the answer, and doesn't say anything when you dodge the question about how much you made.
"Long as you're saving some of it," she says, which is as close to a lecture as it gets tonight.
[[Back->EveningHub]]<<img "family_mother">>
Diane catches you in the hallway and asks, out of nowhere, what you'd do about a coworker who won't stop taking credit for her ideas, a work thing she never usually mentions. You give her the most reasonable answer you can think of on the spot.
"That's what I told myself too," she says, sounding relieved to have it confirmed by someone other than herself, and that's the end of it. You're not sure if you helped or just agreed with her, but either way she seems lighter walking away.
[[Back->EveningHub]]<<img "family_mother">>
Diane's stuck on a crossword clue, pen tapping against the newspaper in the particular rhythm of someone who refuses to look up the answer. "Seven letters, 'quietly persistent,'" she reads out loud, mostly to the room.
You throw out a guess. It doesn't fit. You throw out another one. It fits perfectly, and the look of genuine betrayal on her face that you got it before she did is worth staying for the next three clues too.
[[Back->EveningHub]]<<img "family_mother">>
Diane's making a list on the back of an envelope, the household-errands kind that never seems to actually shrink. "You wouldn't be able to grab a few things this week, would you?" she asks, already half-writing your name at the top like the answer was never really in question.
You say you will. She says she knows, and keeps writing, and somehow that's the whole conversation and it's still nice.
[[Back->EveningHub]]<<img "family_mother">>
You find Diane out back in the last of the daylight, frowning at a tomato plant like it's personally disappointed her this season. "It's the squirrels," she says, with total conviction and zero evidence. "I know it's the squirrels."
You stand out there with her for a few minutes, not really doing anything, just keeping her company while she narrates the ongoing war against the local wildlife. It's a nice kind of nothing.
[[Back->EveningHub]]<<img "family_mother">>
She's folding laundry when she says it, casual enough that it takes you a second to register it as anything more than background noise. "Whatever's going on with you lately," Diane says, not looking up from the towel she's folding, "you know you can just... tell me. If you want to. I'm not going anywhere."
She doesn't wait for an answer, doesn't turn it into a moment. Just leaves the door open and lets you decide whether to walk through it tonight or not.
<<set $familyBond.mother.closeness to $familyBond.mother.closeness + 1>>
<<set $familyBond.mother.awareness to $familyBond.mother.awareness + 1>>
[[Back->EveningHub]]<<img "family_mother">>
Diane's got an old photo album open on the table, the physical kind, corners gone soft from handling. "Look how small you were," she says, in the tone of someone who says this every single time, and turns it toward you anyway.
You sit through a few pages of it: a birthday, a vacation you barely remember, Ryan as a toddler doing something that got him in trouble even then. She doesn't ask anything. She just likes having someone to look at it with.
[[Back->EveningHub]]<<img "family_mother">>
Diane's got the crossword spread out on the kitchen table, pen tapping against a clue she hasn't filled in yet, when she looks up at you a beat longer than the moment really calls for. "You cutting your hair different," she asks, "or is it just the light in here?"
You give her some half-answer. She nods like it settles something, or like she's decided not to press for whatever the real answer would be, and goes back to the crossword without another word about it.
<<set $familyBond.mother.closeness to $familyBond.mother.closeness + 1>>
<<set $familyBond.mother.awareness to $familyBond.mother.awareness + 1>>
[[Back->EveningHub]]<<img "family_mother">>
Diane's got an old recipe card out, squinting at handwriting that isn't hers, her own mother's, going by the age of the paper. "I never can remember if this is two eggs or three," she says, more to herself than to you, then asks anyway like you'd somehow know.
You guess. She goes with your guess. Neither of you will ever know if it was right, which she seems entirely at peace with.
[[Back->EveningHub]]<<img "mall_food_court">>
Nadia's parked at a table near the noodle stall, picking apart a plate of fries one at a time like she's stalling instead of eating. She spots you before you can decide whether to say hi, so that decision's made for you.
"Small mall," she says, kicking the chair across from her out with one foot. "Sit, or don't. I'm not fighting you for the table."
You sit.
<<if $nadia.trust lt 2>>
<<set $nadia.trust to $nadia.trust + 1>>
<<else>>
<<set $nadia.attraction to $nadia.attraction + 1>>
<</if>>
<<link "Continue">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
Theo's at the smoothie stand arguing good-naturedly with the kid behind the counter about whether a "protein boost" add-in does anything at all. He waves you over before you can pretend you didn't see him.
"Back me up," he says. "Placebo, right?"
You have no idea, but you back him up anyway, and he grins like that settles it.
<<if $theo.trust lt 2>>
<<set $theo.trust to $theo.trust + 1>>
<<else>>
<<set $theo.attraction to $theo.attraction + 1>>
<</if>>
<<link "Continue">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
Sam and Max are locked in a deeply serious argument over a shared tray of fries, specifically over whether Max is allowed to keep eating them while insisting he's "not really hungry, just testing something."
"It's a controlled experiment," Max says, not looking up.
"It's my fries," Sam says.
They both immediately try to pull you into ruling on it, and there is no good answer.
<<link "Rule in Sam's favor">>
<<goto "MallFoodCourt">>
<</link>>
<<link "Rule in Max's favor">>
<<goto "MallFoodCourt">>
<</link>><<set _maxStage to Math.max.apply(null, Object.keys($bodyFeatures).filter(function (k) { return k !== "voice"; }).map(function (k) { return $bodyFeatures[k]; }))>>
<<if _maxStage lte 1>>
<<img "mall_food_court">>
The mirrored pillar by the escalators catches your reflection as you pass, same as it always does. Nothing to look twice at.
<<else>>
<<img "mirror_stage3">>
The mirrored pillar by the escalators catches your reflection as you pass, and for a second you actually stop walking. It's not a bad look. That thought arrives before you can stop it, and it doesn't leave right away either.
<</if>>
<<link "Keep moving">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
Two tables over, someone says something to their friend, not loud, not really meant to carry, but it carries anyway. You only catch the tail end of it, something about "she" and a gesture that might have been about you or might not have been.
You don't turn around to check. You're not sure which answer you're hoping for.
<<link "Eat your food and don't think about it">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
The cashier hands back your change and receipt and pauses half a second longer than the transaction requires, like they're about to say something and then don't.
"Have a good one," is what actually comes out, a little too warm for a fast food counter. You take the receipt and go, turning it over in your head longer than it deserves.
<<link "Find a table">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
You spot someone from the neighborhood, one of the Hendersons maybe, or close enough that waving feels safer than not, a few tables down. They wave back, mouth something you can't hear over the food court noise, and go back to their meal. Small mall, smaller world.
<<link "Wave back and keep walking">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
Your phone buzzes on the table. Group chat, predictably.
<<set _line to setup.groupChatLines[random(0, setup.groupChatLines.length - 1)]>>
<em><<= _line.speaker>>: "<<= _line.text>>"</em>
You put the phone face-down and go back to your food, mostly successfully.
<<link "Continue">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
You nearly sit down at a table that isn't yours, tray and all, before the stranger already sitting there clears their throat. You apologize, relocate, and spend the next full minute overthinking a completely normal mistake anyone could've made.
<<link "Find your actual table">>
<<goto "MallFoodCourt">>
<</link>><<img "mall_food_court">>
Nothing happens. You just sit with your food for a minute, food court noise blurring into background static, and let yourself not think about anything in particular. It's nicer than it sounds.
<<link "Continue">>
<<goto "MallFoodCourt">>
<</link>>Friday, finally. Jordan already texted the group chat: "session tomorrow, don't be late." Sam replied with just a skull emoji, which could mean anything.
<<switch $job.type>>
<<case "cafe">>
<<img "fri_cafe">>
Last shift of the week. Your manager asks, entirely casually, if you've been sleeping okay. You look "softer" today, apparently. You genuinely don't know what that means and let it go.
<<case "fastfood">>
<<img "fri_fastfood">>
Last shift. The manager hands you your check without comment, which is as close to a compliment as he gets.
<<default>>
<<img "fri_babysitting">>
Last night of babysitting this week. The kids ask if you're coming back next week like it's already decided, which, fair.
<</switch>>
<<shiftPay>>
<<link "Head home for the weekend">>
<<set $eveningReturnTarget to "">>
<<goto "EveningDinner">>
<</link>><<img "day_room_scene">>
You get ready for bed, and the day folds itself away behind you: the shift, the shrine still humming somewhere in the back of your mind, all of it set aside until tomorrow.
<<set $eveningSeen to []>>
<<set $dayNumber to $dayNumber + 1>>
<<set $randomEventFired to false>>
<<if $eveningReturnTarget isnot "">>
<<set $daySceneStep to 0>>
<<set $dayReturnTarget to $eveningReturnTarget>>
<<set _dayMatch to $eveningReturnTarget.match(/Monday|Tuesday|Wednesday|Thursday|Friday/)>>
<<if _dayMatch>>
<<set $currentDayName to _dayMatch[0]>>
<</if>>
[[Fall asleep->MorningHub]]
<<else>>
<<link "Fall asleep">>
<<set $currentDayName to "Saturday">>
<<goto $weekendTarget>>
<</link>>
<</if>><<set $henderson.milestoneSeen to true>>
<<img "babysitting_house">>
The kids are asleep, and instead of the usual quick payment-and-goodbye, Mrs. Henderson sits down across from you at the kitchen table like she means to actually talk. "I've been thinking," she says, "that I don't really think of you as 'the babysitter' anymore. Not for a while now, honestly."
She mentions Riley, glad, she says, that the two of you hit it off at the party, that it's nice seeing you find your footing with people your own age too. Then, almost as an afterthought, she offers something warmer than the usual hand-me-down: an actual invitation, next time the family does something bigger than a shift.
"No pressure," she adds, same as always. "Just — you're welcome here. More than welcome, at this point."
<<link "Say thank you">>
<<goto $jobNoticedReturn>>
<</link>>You get home with a little time before you need to head out again.
<<homeScenePicker>>
<<link "Skip it, head straight out">>
<<goto $dayReturnTarget>>
<</link>><h2>Inventory</h2>
<p><em><<print "$" + $money>> on hand.</em></p>
<<ownedItemList "Pharmacy" setup.pharmacyItems $pharmacyBought>>
<<ownedItemList "Electronics" setup.electronicsItems $electronicsOwned>>
<<ownedItemList "Books & Comics" setup.bookstoreItems $bookstoreOwned>>
<<ownedItemList "Beauty" setup.beautyItems $beautyOwned>>
<<if State.length gt 1>>
<<set _backTarget to State.peek(1).title>>
<p><<link "Back">>
<<goto _backTarget>>
<</link>></p>
<</if>>It's been a quiet couple of days since the last session, and your bank account has been bothering you more than the shrine has. Time to actually commit to a part-time job.
<<for _j range setup.jobs>>
<<capture _j>>
<p><<link _j.label>>
<<set $job.type to _j.id>>
<<set $job.label to _j.label>>
<<set $job.basePay to _j.basePay>>
<<set $job.tips to _j.tips>>
<<set $daySceneStep to 0>>
<<set $dayReturnTarget to "E1_EndOfContentMonday">>
<<set $dayNumber to $dayNumber + 1>>
<<goto "Sunday">>
<</link>> — <<= _j.blurb>></p>
<</capture>>
<</for>><<img "cafe">>
The regular who always orders the same thing has started ordering it before you even ask. She just says your name, says "the usual," and waits, like the two of you have some kind of ritual going now. The manager notices too, mentions offhand that she'd trust you to run a shift solo if it ever came to that.
Neither of them says anything about anything else. It's just become the shape of a normal shift, the kind of familiarity that stopped being remarkable without you noticing exactly when.
<<link "Let it feel normal">>
<<set $eventChoice to "embrace">>
<<goto "JobMilestone_Result">>
<</link>>
<<link "Feel strange about how normal it's gotten">>
<<set $eventChoice to "consider">>
<<goto "JobMilestone_Result">>
<</link>><<img "fastfood">>
A coworker starts covering your side of the counter without being asked whenever you duck out for a break, no comment, just does it. It's the kind of easy trust that only builds up in increments too small to notice individually. The manager mentions, half-joking, that customers ask for you by description now instead of by name, since apparently you're memorable.
Nobody frames it as a big deal. It's just where things have settled, the way a shift finds its own rhythm eventually.
<<link "Let it feel normal">>
<<set $eventChoice to "embrace">>
<<goto "JobMilestone_Result">>
<</link>>
<<link "Feel strange about how normal it's gotten">>
<<set $eventChoice to "consider">>
<<goto "JobMilestone_Result">>
<</link>><<set $job.milestoneSeen to true>>
<<switch $job.type>>
<<case "cafe">>
<<img "cafe">>
<<if $eventChoice is "embrace">>
You let it be what it is, comfortable, familiar, no bigger a deal than any other part of the routine.
<<elseif $eventChoice is "consider">>
You catch yourself thinking about it for longer than the shift really has room for, and don't quite land on why it bothers you.
<</if>>
<<case "fastfood">>
<<img "fastfood">>
<<if $eventChoice is "embrace">>
You let it be what it is: comfortable, familiar, no bigger a deal than any other part of the routine.
<<elseif $eventChoice is "consider">>
You catch yourself thinking about it for longer than the shift really has room for, and don't quite land on why it bothers you.
<</if>>
<</switch>>
<<set $eventChoice to "">>
<<link "Get on with the day">>
<<goto $jobNoticedReturn>>
<</link>><<img "babysitting_house">>
Mrs. Henderson catches you in the hallway before you even get your coat off, gentler about it than you expected. "I hope this isn't out of line," she says, "but you've changed so much since you started watching the kids. I've got a few things I never wear anymore that would probably suit you better than they ever suited me. No pressure at all — just, the offer's there."
<<link "Politely decline">>
<<set $eventChoice to "deflect">>
<<goto "JobNoticed_Result">>
<</link>>
<<link "Say you'd actually like that">>
<<set $eventChoice to "embrace">>
<<goto "JobNoticed_Result">>
<</link>><<img "cafe">>
The manager pulls you aside before your shift starts, not unkind about it, just direct. "Hey — not a complaint, just checking in. You've changed a lot since you started here. Wanted to make sure that's, you know, going okay for you. Dress code's flexible if you want it to be."
<<link "Say things are fine as they are">>
<<set $eventChoice to "deflect">>
<<goto "JobNoticed_Result">>
<</link>>
<<link "Actually take her up on the offer">>
<<set $eventChoice to "embrace">>
<<goto "JobNoticed_Result">>
<</link>><<img "fastfood">>
Your shift manager catches you by the time clock, awkward about it in the specific way managers get when they've rehearsed a sentence twice. "So — not trying to make it weird, but you've changed a lot since you started. If you want to update your uniform order, sizing, whatever, just let me know. No paperwork, I'll just fix it."
<<link "Say the current uniform's fine">>
<<set $eventChoice to "deflect">>
<<goto "JobNoticed_Result">>
<</link>>
<<link "Ask him to actually update it">>
<<set $eventChoice to "embrace">>
<<goto "JobNoticed_Result">>
<</link>><<set $job.noticed to true>>
<<switch $job.type>>
<<case "cafe">>
<<img "cafe">>
<<if $eventChoice is "embrace">>
The manager doesn't make a thing of it beyond that, just updates whatever needs updating and gets back to opening up. It's a small, practical kindness, the kind that changes nothing and everything at once.
<<else>>
She nods, doesn't push, and the conversation ends there. It's still sitting differently with you than most conversations do.
<</if>>
<<case "fastfood">>
<<img "fastfood">>
<<if $eventChoice is "embrace">>
He actually follows through by the end of the week, no fuss about it, like updating a coworker's uniform order is the least remarkable thing he'll do all shift.
<<else>>
He shrugs, says the offer stands whenever, and goes back to the register like it was nothing.
<</if>>
<<default>>
<<img "babysitting_house">>
<<if $eventChoice is "embrace">>
She actually brings a bag over within the week, nothing dramatic, just things that fit better than what you've been making do with. The kids don't notice. She clearly already had.
<<else>>
She lets it go easily, no hurt feelings, though you get the sense the offer isn't going anywhere.
<</if>>
<</switch>>
<<set $eventChoice to "">>
<<link "Get on with the day">>
<<goto $jobNoticedReturn>>
<</link>><<img "mall_interior">>
The mall's the same as always: bright, a little too air-conditioned, and full of places to spend money you were supposed to be saving. You've got <<print "$" + $money>> to work with.
<p><strong>Clothing</strong></p>
[[Menswear->MallClothingMenGate]]
[[Women's clothing->MallClothingWomenGate]]
[[Lingerie->MallClothingLingerieGate]]
<p><strong>Other stores</strong></p>
[[Pharmacy->MallPharmacy]]
[[Electronics store->MallElectronics]]
[[Bookstore->MallBookstore]]
[[Jewelry store->MallJewelry]]
[[Beauty store->MallBeauty]]
[[Food court->MallFoodCourt]]
<<if $mallReturnOverride isnot "">>
<<link "Head home">>
<<set _target to $mallReturnOverride>>
<<set $mallReturnOverride to "">>
<<goto _target>>
<</link>>
<<else>>
<<link "Head home">>
<<set $eveningReturnTarget to $nextDay>>
<<goto "EveningDinner">>
<</link>>
<</if>><<if $beautyOwned.length gt 0 and not $beautyFirstUseSeen>>
<<goto "MallBeautyFirstUse">>
<</if>>
<<img "mall_beauty">>
Rows of skincare and makeup under warm, flattering light. You've got <<print "$" + $money>> to work with.
<<consumableList setup.beautyItems $beautyOwned "MallBeauty">>
[[Back to the mall->Mall]]<<img "mall_beauty">>
<<set $beautyFirstUseSeen to true>>
You stand at the counter a second longer than you need to, bag in hand, half-expecting the cashier to say something about it. She doesn't. Why would she? It's just a purchase, the same as anyone else's.
You still catch yourself checking the bag twice before you leave, like it might have been a mistake you could still undo.
[[Continue->MallBeauty]]<<img "mall_bookstore">>
Shelves of paperbacks, graphic novels, and a whole section devoted to tabletop games. You've got <<print "$" + $money>> to work with.
<<consumableList setup.bookstoreItems $bookstoreOwned "MallBookstore">>
[[Back to the mall->Mall]]<<img "mall_clothing_store">>
Quiet, carefully lit, folded stacks of things you don't have a lot of practice picking out. You've got <<print "$" + $money>> to work with.
<p><strong>Underwear</strong></p>
<<mallItemList "underwear" "lingerie" "MallClothingLingerie">>
<p><strong>Tops</strong></p>
<<mallItemList "top" "lingerie" "MallClothingLingerie">>
<p><strong>Legwear</strong></p>
<<mallItemList "bottom" "lingerie" "MallClothingLingerie">>
<p><strong>Shoes</strong></p>
<<mallItemList "shoes" "lingerie" "MallClothingLingerie">>
[[Back to the mall->Mall]]<<if $transformation gte 5>>
<<goto "MallClothingLingerie">>
<<else>>
<<img "mall_clothing_store">>
You catch yourself actually considering it before you've fully registered what store you're standing in front of. That thought alone is enough to make you take a step back.
<<link "Go in, see what's there">>
<<goto "MallClothingLingerie">>
<</link>>
<<link "Keep walking">>
<<goto "Mall">>
<</link>>
<</if>><<img "mall_clothing_store">>
Racks of men's clothes, most of it nicer than anything currently in your closet. You've got <<print "$" + $money>> to work with.
<p><strong>Tops</strong></p>
<<mallItemList "top" "men" "MallClothingMen">>
<p><strong>Bottoms</strong></p>
<<mallItemList "bottom" "men" "MallClothingMen">>
<p><strong>Shoes</strong></p>
<<mallItemList "shoes" "men" "MallClothingMen">>
<p><strong>Accessories</strong></p>
<<mallItemList "hands" "men" "MallClothingMen">>
[[Back to the mall->Mall]]<<if $transformation gte 16>>
<<img "mall_clothing_store">>
You stop in front of the menswear section out of habit more than anything, and the racks just don't register as an option anymore. Not a refusal, exactly, just an absence, like reaching for a language you used to speak and finding the words aren't there.
<<link "Back to the mall">>
<<goto "Mall">>
<</link>>
<<else>>
<<goto "MallClothingMen">>
<</if>><<img "mall_clothing_store">>
Racks of women's clothes, bright and orderly. You've got <<print "$" + $money>> to work with.
<p><strong>Tops</strong></p>
<<mallItemList "top" "women" "MallClothingWomen">>
<p><strong>Bottoms</strong></p>
<<mallItemList "bottom" "women" "MallClothingWomen">>
<p><strong>Shoes</strong></p>
<<mallItemList "shoes" "women" "MallClothingWomen">>
<p><strong>Accessories</strong></p>
<<mallItemList "hands" "women" "MallClothingWomen">>
<<mallItemList "head" "women" "MallClothingWomen">>
<<mallItemList "neck" "women" "MallClothingWomen">>
[[Back to the mall->Mall]]<<if $transformation gte 3>>
<<goto "MallClothingWomen">>
<<else>>
<<img "mall_clothing_store">>
You stop a few feet from the entrance, looking at the racks through the storefront glass. Nothing about it is unusual, it's just a clothing store, but you can't quite make your feet carry you through the door.
<<link "Walk in anyway">>
<<goto "MallClothingWomen">>
<</link>>
<<link "Not today">>
<<goto "Mall">>
<</link>>
<</if>><<img "mall_electronics">>
Rows of gadgets under bright display lighting. You've got <<print "$" + $money>> to work with.
<<consumableList setup.electronicsItems $electronicsOwned "MallElectronics">>
[[Back to the mall->Mall]]<<img "mall_food_court">>
Plastic trays, fluorescent lighting, and the smell of six different cuisines competing for attention. You grab a seat and just people-watch for a while: no shopping, no spending, just a break from the rest of the mall.
<<foodCourtEventPicker>>
[[Back to the mall->Mall]]<<img "mall_jewelry">>
Glass cases of chains, rings, and earrings catching the light. You've got <<print "$" + $money>> to work with.
<p><strong>Necklaces</strong></p>
<<mallItemList "neck" "any" "MallJewelry">>
<<mallItemList "neck" "women" "MallJewelry">>
<p><strong>Rings & bracelets</strong></p>
<<mallItemList "hands" "any" "MallJewelry">>
<<mallItemList "hands" "men" "MallJewelry">>
<<mallItemList "hands" "women" "MallJewelry">>
<p><strong>Earrings</strong></p>
<<mallItemList "head" "any" "MallJewelry">>
<<mallItemList "head" "women" "MallJewelry">>
[[Back to the mall->Mall]]<<img "mall_pharmacy">>
Fluorescent-lit aisles of basic health and hygiene supplies. You've got <<print "$" + $money>> to work with.
<<consumableList setup.pharmacyItems $pharmacyBought "MallPharmacy">>
[[Back to the mall->Mall]]<<img "tabletalk_map">>
Everyone settles in. Jordan, the DM, spreads out a hand-drawn map behind the screen and clears their throat.
"Okay," Jordan says. "You've been hired to look into an old shrine outside town, something about strange lights at night. You've just reached the door. It's sealed, but not locked. Kaelen, you're standing right in front of it."
Sam leans forward, rolling a stray die between his fingers. "Do we hear anything?"
<<print $player.name>> glances at the table, then at the group, and smiles. Time to find out.
[[Step into the shrine->E1_Shrine_Entrance]]Monday. Your first shift of the week.
<<switch $job.type>>
<<case "cafe">>
After class you clock in at the coffee shop, tying on the ugly green apron that never quite fits right. The espresso machine hisses, the line never really stops, and by close your hands smell like burnt coffee grounds no matter how many times you wash them.
<<img "cafe">>
<<link "Make small talk with a regular">>
<<goto "MondayEvent">>
<</link>>
<<link "Keep your head down and just work">>
<<goto "MondayEvent">>
<</link>>
<<case "fastfood">>
After class it's straight to the fast food counter, hairnet and all. The fryer alarm goes off every ninety seconds, someone's always yelling an order back, and the shift manager reminds you twice that the bathroom needs mopping.
<<img "fastfood">>
<<link "Take the drive-through headset">>
<<goto "MondayEvent">>
<</link>>
<<link "Stick to the register">>
<<goto "MondayEvent">>
<</link>>
<<default>>
After class you head a few blocks over to babysit the Hendersons' kids, who greet you by immediately demanding a very specific cartoon. Homework gets done in between snack requests and one minor couch-cushion crisis.
<<img "babysitting_house">>
<<link "Let them stay up a little late">>
<<goto "MondayEvent">>
<</link>>
<<link "Stick to the actual bedtime">>
<<goto "MondayEvent">>
<</link>>
<</switch>><<switch $job.type>>
<<case "cafe">>
<<img "mon_event_cafe">>
Near the end of the shift, a regular leaves a five in the tip jar just for remembering her order. Small stuff, but it adds up.
<<case "fastfood">>
<<img "mon_event_fastfood">>
The dinner rush is brutal, but you make it through without dropping a single tray. Small victories.
<<default>>
<<img "mon_event_babysitting">>
The younger Henderson kid falls asleep on your arm halfway through a book, and you're stuck reading in a whisper for another twenty minutes so you don't wake her up.
<</switch>>
<<shiftPay>>
<<link "Head home">>
<<set $eveningReturnTarget to "Tuesday">>
<<goto "EveningDinner">>
<</link>><<if $daySceneStep is 0 and not $randomEventFired>>
<<set $randomEventFired to true>>
<<maybeRandomEvent "MorningHub">>
<</if>>
Another morning. You've got a little time before you need to be out the door.
<<img "bedroom">>
<<link "Check yourself in the mirror">>
<<goto "MorningMirror">>
<</link>>
<<link "Take a shower">>
<<goto "MorningHubShower">>
<</link>>
<<link "Just wash up at the sink">>
<<goto "MorningHubSink">>
<</link>>
<<link "Skip it, you're fine">>
<<goto "MorningHubWardrobe">>
<</link>><<img "kitchen">>
The kitchen table has the usual spread, if you want it.
<<link "Sit down with the family for a few minutes">>
<<goto "MorningHubBreakfastFamily">>
<</link>>
<<link "Cereal, easy and fast">>
<<goto "MorningHubSchoolCheck">>
<</link>>
<<link "Skip breakfast, you're already running late">>
<<goto "MorningHubSchoolCheck">>
<</link>><<set _who to either("father", "mother", "brother")>>
<<if _who is "father">>
<<img "family_father_breakfast">>
Mark's already at the table, coffee in hand, half-reading something on his phone. He glances up, asks how the week's shaping up, and actually listens to the answer for once. It's not much, but it's a nice way to start the day.
<<elseif _who is "mother">>
<<img "family_mother_breakfast">>
Your mom's got breakfast half-made before you even sit down, sliding a plate over without asking if you want one. She asks about school, about the group, about whether you're sleeping enough, the usual rundown, delivered with actual warmth.
<<else>>
<<img "family_brother_breakfast">>
Your brother's already inhaling cereal like it's a competitive sport, and he tries to rope you into an argument about something completely inconsequential before you've even sat down. It's stupid. It helps anyway.
<</if>>
<<link "Head to school">>
<<goto "MorningHubSchoolCheck">>
<</link>><<if $outfit.underwear neq "" and $outfit.top neq "" and $outfit.bottom neq "" and $outfit.shoes neq "">>
You grab your bag and head for the door, dressed, fed or not, ready enough.
[[Head to campus->DayHub]]
<<else>>
You catch yourself halfway out the door and realize you're still missing something basic. Can't show up to campus like this.
[[Back to the closet->MorningHubWardrobe]]
<</if>><<img "shower_steam">>
The water takes a minute to warm up, same as always. You don't linger, just enough to feel human again before the day starts.
<<set _unshaved to Object.keys($shaveProgress).filter(function (k) { return !$shaveProgress[k]; })>>
<<if $pharmacyBought.includes("razor") and _unshaved.length gt 0>>
<p>The razor's sitting on the edge of the tub, exactly where you left it.</p>
[[Shave->MorningShave]]
<<elseif _unshaved.length is 0 and not $shaveProgressCelebrated>>
[[Get dressed->MorningShaveComplete]]
<<else>>
[[Get dressed->MorningHubWardrobe]]
<</if>><<img "sink_closeup">>
No time to waste on a full shower. You brush your teeth, splash some water on your face, and drag a comb through your hair without much ceremony.
[[Get dressed->MorningHubWardrobe]]You pull open the closet and take stock of what's clean.
<<wardrobeSlot "underwear" "MorningHubWardrobe">>
<<wardrobeSlot "top" "MorningHubWardrobe">>
<<wardrobeSlot "bottom" "MorningHubWardrobe">>
<<wardrobeSlot "shoes" "MorningHubWardrobe">>
<<wardrobeSlot "neck" "MorningHubWardrobe">>
<<wardrobeSlot "hands" "MorningHubWardrobe">>
<<wardrobeSlot "head" "MorningHubWardrobe">>
<<if $outfit.underwear neq "" and $outfit.top neq "" and $outfit.bottom neq "" and $outfit.shoes neq "">>
<<outfitDailyCheck>>
[[Head down for breakfast->MorningHubBreakfast]]
<<else>>
<p><em>(You'll want at least the basics on before you head out.)</em></p>
<</if>><<set _features to ["face", "chest", "waist", "rear", "hair", "skin"]>>
<<set _maxStage to Math.max.apply(null, _features.map(function (f) { return $bodyFeatures[f]; }))>>
<<if _maxStage is 0>>
<<img "mirror_stage0">>
You catch your reflection for a second. Same face as always, nothing to see here.
<<else>>
You catch your reflection and take an extra second this time.
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(140px, 1fr)); gap:0.75em; margin:1em 0;">
<<for _f range _features>>
<<if $bodyFeatures[_f] gt 0>>
<<set _img to "body_" + _f + "_stage" + $bodyFeatures[_f]>>
<<else>>
<<set _img to "body_" + _f + "_stage0">>
<</if>>
<<set _label to _f.charAt(0).toUpperCase() + _f.slice(1)>>
<div style="text-align:center;">
<<img _img>>
<p style="margin:0.25em 0 0; font-size:0.85em; text-transform:uppercase; letter-spacing:0.05em; opacity:0.7;"><<= _label>></p>
<p style="margin:0.15em 0 0; font-size:0.9em;">
<<switch _f>>
<<case "face">>
<<switch $bodyFeatures.face>>
<<case 1>>Nothing you could point to, exactly. Just a feeling that something's slightly off from what you remember, gone as soon as you look for it.
<<case 2>>The jaw looks softer than it used to, the features somehow smoothed at the edges. You tell yourself it's just the light.
<<case 3>>The changes aren't subtle anymore: softer features, longer lashes, a face that's drifted somewhere you don't have a word for yet. You don't feel alarmed, which is its own kind of alarming.
<<case 4>>Whoever's looking back is unmistakably not who you used to be, and some quiet part of you has stopped being surprised by that. You touch your own cheek just to confirm the mirror isn't lying.
<<default>>Same face as always, nothing to see here.
<</switch>>
<<case "chest">>
<<switch $bodyFeatures.chest>>
<<case 1>>Your shirt sits differently than it used to, just barely, a fullness you'd swear wasn't there before, if you were the type to swear to it.
<<case 2>>There's no explaining it away now. The fit across your chest has changed enough that you've started picking shirts with it in mind.
<<case 3 4>>It's just part of your shape now, unremarkable to look at, the kind of thing you'd only notice was ever different if you went looking for old photos.
<<default>>Nothing different here yet.
<</switch>>
<<case "waist">>
<<switch $bodyFeatures.waist>>
<<case 1>>Your waistband sits a little differently than it did. Nothing dramatic, just a shape that isn't quite the one you're used to accounting for.
<<case 2>>The curve at your waist and hips is visible now, plain as anything, in a way that changes how your old clothes hang.
<<case 3 4>>The silhouette in the mirror is fully someone else's: narrower waist, wider hips, a shape your old wardrobe was never built for.
<<default>>Nothing different here yet.
<</switch>>
<<case "rear">>
<<switch $bodyFeatures.rear>>
<<case 1>>You catch yourself turning slightly to check, like your own clothes fit a little differently than they should.
<<case 2>>There's a fullness there now that your reflection doesn't let you ignore, however you're standing.
<<case 3 4>>It's just how you're built now, fuller, rounder, the kind of change that stopped registering as a change a while back.
<<default>>Nothing different here yet.
<</switch>>
<<case "hair">>
<<switch $bodyFeatures.hair>>
<<case 1>>Your hair's a little longer and softer than you remember leaving it, the ends starting to curl somewhere they didn't used to.
<<case 2>>It's long enough now that it gets in the way. You've started reaching for a hair tie without quite deciding to.
<<case 3 4>>Long, styled without much effort, framing your face in a way that's stopped feeling like anyone else's hair but yours.
<<default>>Same as always.
<</switch>>
<<case "skin">>
<<switch $bodyFeatures.skin>>
<<case 2>>Your skin's smoother than it has any right to be this early in the morning. The razor's doing less work than it used to.
<<case 4>>It doesn't grow back the way it used to. Smooth is just the default now, no upkeep required to keep it that way.
<<default>>Nothing different here yet.
<</switch>>
<</switch>>
</p>
</div>
<</for>>
</div>
<<if $bodyFeatures.voice gt 0>>
<<switch $bodyFeatures.voice>>
<<case 1>>Your voice catches on something mid-thought, higher than you meant it, gone before you can decide if you imagined it.
<<case 2>>Your own voice sounds a half-step off from what you expect every time you hear it played back, a text-to-speech clip, a recording, anything that isn't just talking.
<<case 3>>People who don't know you well default to "she" on the phone now, without hesitating, without you saying a word to correct it.
<<default>>It's just your voice. You'd have to think hard to remember it sounding any other way.
<</switch>>
<p><em>Hear a sample:</em> <<voiceSample $bodyFeatures.voice>></p>
<</if>>
<</if>>
[[Look away->MorningHub]]The bathroom mirror is still fogged from someone else's shower.
<<img "bathroom">>
<<link "Take a quick shower">>
<<goto "MorningShower">>
<</link>>
<<link "Just the sink will do">>
<<goto "MorningSink">>
<</link>><<img "shower_steam">>
<<set _order to ["face", "chest", "legs", "intimate"]>>
<<set _next to _order.find(function (k) { return !$shaveProgress[k]; })>>
<<switch _next>>
<<case "face">>
<p>You catch your reflection in the fogged-up mirror and reach for the razor out of habit. It'd take thirty seconds either way.</p>
<<link "Shave">>
<<set $shaveProgress.face to true>>
<<goto "MorningHubShower">>
<</link>>
<<link "Leave it, not today">>
<<goto "MorningHubShower">>
<</link>>
<<case "chest">>
<p>The razor's still in your hand. You catch yourself looking down, considering it in a way you didn't used to.</p>
<<link "Go ahead">>
<<set $shaveProgress.chest to true>>
<<goto "MorningHubShower">>
<</link>>
<<link "Not today">>
<<goto "MorningHubShower">>
<</link>>
<<case "legs">>
<p>You're already this far. It feels like a bigger decision than it should, standing here weighing it.</p>
<<link "Go ahead">>
<<set $shaveProgress.legs to true>>
<<goto "MorningHubShower">>
<</link>>
<<link "Not today">>
<<goto "MorningHubShower">>
<</link>>
<<case "intimate">>
<p>The last of it. You hesitate longer than the previous times combined before you actually decide.</p>
<<link "Go ahead">>
<<set $shaveProgress.intimate to true>>
<<goto "MorningHubShower">>
<</link>>
<<link "Not today">>
<<goto "MorningHubShower">>
<</link>>
<</switch>><<img "shower_steam">>
You catch yourself running a hand along your own arm, unhurried, in a way you wouldn't have paused to do a few months ago. Every part of the routine that used to be habit-fast is smooth now, deliberate, nothing left half-finished. It's not a big moment. It just feels like one anyway, a quiet kind of noticing, with nobody around to see you do it.
<<set $shaveProgressCelebrated to true>>
<<set $bodyFeatures.skin to Math.max($bodyFeatures.skin, 2)>>
<<recalcTransformation>>
[[Get dressed->MorningHubWardrobe]]<<img "shower_steam">>
The water takes a minute to warm up, same as always. You don't linger, just enough to feel human again before the day starts.
Toweled off, teeth brushed, hair dragged into some kind of order. Downstairs, you can already smell something cooking.
[[Head down for breakfast->Breakfast]]<<img "sink_closeup">>
No time to waste on a full shower. You brush your teeth, splash some water on your face, and drag a comb through your hair without much ceremony.
Downstairs, you can already smell something cooking.
[[Head down for breakfast->Breakfast]]You catch your reflection in the mirror on the closet door for a moment before looking away.
<<textbox "$player.name" "Alex" "Wardrobe">>
<<link "Continue" "Wardrobe">>
<<if $player.name is "">>
<<set $player.name to "Alex">>
<</if>>
<</link>><<img "bedroom">>
Digging through an old jacket pocket for nothing in particular, your fingers close around a crumpled bill you'd completely forgotten about.
<<set $money to $money + 10>>
<em>+$10.</em>
<<link "Pocket it and move on">>
<<goto $randomEventContinue>>
<</link>><<img "kitchen">>
You reach for a mug half-asleep and knock it straight off the counter. Coffee everywhere, including down the front of whatever you were about to wear.
<<set $money to $money - 5>>
<em>You spend a few minutes cleaning up and end up needing a wash cycle you hadn't budgeted for. -$5.</em>
<<link "Deal with it and keep going">>
<<goto $randomEventContinue>>
<</link>><<img "bedroom">>
Your phone buzzes. Sam, predictably, already awake and already spiraling about something session-related.
<em>"ok but what if the dice thing isn't even about Jordan. what if it's about ALL of us. anyway good morning"</em>
You have no idea what to do with that this early. You put the phone down and get moving.
<<link "Continue">>
<<goto $randomEventContinue>>
<</link>><<img "bedroom">>
You reach up out of habit and realize one earring's missing, must have come loose overnight. A quick check of the sheets turns it up, but not before a minute of quiet panic.
<<if $bodyFeatures.face gte 2>>
Standing in front of the mirror putting it back in, you catch yourself checking your whole reflection instead of just your ear, half-bracing to find something else off, half something closer to relieved when you don't. The panic was never really about the earring.
<</if>>
<<link "Put it back on and move on">>
<<goto $randomEventContinue>>
<</link>><<img "hallway">>
Someone you barely know says something offhand and kind on the way across the quad, nothing dramatic, just noticed and said out loud. It sticks with you longer than it probably should.
<<link "Continue">>
<<goto $randomEventContinue>>
<</link>><<img "bedroom">>
You wake up with the tail end of a dream still clinging to you: Jordan's dice set, glowing faintly, rolling themselves across a table that isn't Jordan's basement. Gone the second you try to look at it directly.
You lie there a moment longer than you mean to, then get up anyway.
<<link "Shake it off">>
<<goto $randomEventContinue>>
<</link>><<img "kitchen">>
Ryan's already up, eating cereal straight out of the box, and mentions, completely unbothered, that he borrowed something of yours without asking. Again.
<<link "Not worth the argument today">>
<<goto $randomEventContinue>>
<</link>><<set _maxStage to Math.max.apply(null, Object.keys($bodyFeatures).filter(function (k) { return k !== "voice"; }).map(function (k) { return $bodyFeatures[k]; }))>>
<<if _maxStage lte 1>>
<<img "bedroom">>
You catch your reflection in the hallway mirror on the way past and don't think twice about it.
<<else>>
<<img "mirror_stage3">>
You catch your reflection in the hallway mirror on the way past and, for half a second, don't recognize yourself. It passes before you can decide whether that was strange or not, or whether the flicker of something other than alarm is the part you should be paying attention to.
<</if>>
<<link "Continue">>
<<goto $randomEventContinue>>
<</link>><<img "school_exterior">>
Jordan spots you first, waving you over. Sam's leaning against the railing, already deep into some argument with Max about whether a house rule from last session was fair.
"Tell him," Sam says the second you're in earshot. "Tell him a natural 1 should still let you try again."
Max just grins. "You're only mad because it was your natural 1."
Jordan laughs. "Save it for tonight. I've got the map ready and everything."
The day on campus stretches out ahead of you, but honestly, all you can think about is tonight's session.
[[Head inside->SchoolDay]]<<img "cafeteria">>
The dining hall is doing its usual impression of a war zone. You find Sam and Jordan already at the usual table, Jordan scribbling something in a battered notebook he refuses to let anyone read yet.
"No spoilers," Jordan says without looking up, sliding the notebook further from view.
Sam steals a fry off your tray without asking. "Guy's been weirdly locked in about this session. It's kind of unsettling."
[[Back to the day->SchoolDay]]<<img "classroom">>
The lecture drags. You spend most of it sketching a rough map of the shrine from memory in the margins of a notebook, half-listening to the professor work through the slides.
Max leans over from the next desk. "You're doing that shrine thing again, aren't you."
"Maybe," you say, not looking up.
[[Back to the day->SchoolDay]]The day passes in pieces, the way campus days do when your mind is somewhere else entirely.
<<schoolSceneList>>
<<if $school.visited.length >= setup.schoolScenes.length>>
[[Skip ahead to game night->Start]]
<</if>><<img "hallway">>
Between classes, you end up at the lockers by the lounge at the same time as Max, who's still going on about the fairness of natural 1s like it's a personal grievance.
"You're a cleric now," Max says, apropos of nothing. "You could just heal my feelings about it."
"Doesn't work like that," you say, grabbing a textbook.
"Should, though."
[[Back to the day->SchoolDay]]<h2>Carried Items</h2>
<<sessionItemList "full">>
<<if State.length gt 1>>
<<set _backTarget to State.peek(1).title>>
<p><<link "Back">>
<<goto _backTarget>>
<</link>></p>
<</if>>You open the app out of habit more than curiosity.
<<set _eligible to setup.socialPosts.filter(function (p) { return $currentEpisode >= p.episode && $transformation >= p.minTransformation; })>>
<<if _eligible.length is 0>>
<p><em>Nothing new. You close it again.</em></p>
<<else>>
<<for _post range _eligible>>
<<set _stageKey to "captionStage0">>
<<if _post.captionStage3 isnot undefined and $transformation gte 18>>
<<set _stageKey to "captionStage3">>
<<elseif _post.captionStage2 isnot undefined and $transformation gte 12>>
<<set _stageKey to "captionStage2">>
<<elseif _post.captionStage1 isnot undefined and $transformation gte 6>>
<<set _stageKey to "captionStage1">>
<</if>>
<p><strong><<= _post.author>>:</strong> <<= _post[_stageKey]>></p>
<</for>>
<<if $publicAttention.peers lt 1 and $currentEpisode is 12>>
<<set $publicAttention.peers to $publicAttention.peers + 1>>
<</if>>
<</if>>
[[Back->EveningHub]]<<img "basement">>
The smell of reheated pizza and cold soda fills Jordan's basement. <<print $player.name>> drops a battered backpack full of dice, minis, and a well-worn Player's Handbook onto the table.
"Alright," Jordan says, spreading out a stack of pre-gen character sheets. "First session, so let's sort out the party before we dive in."
Sam grabs a sheet without even looking at the others. "Fighter. Calling it now."
Max snatches another. "Ranger's mine. Somebody's gotta track things."
That leaves one sheet on the table. <<print $player.name>> reaches for it out of habit more than anything, and stops.
"Cleric," Jordan says, tapping the sheet. "Somebody's gotta keep us alive, and you're the only one left."
<<print $player.name>> picks it up, only half paying attention to the fine print. "Sure. Whatever heals the party."
Jordan grins, sliding over an old, unusually heavy set of dice. New ones, picked up at an estate sale a week ago. "Good, because I already reworked the whole opening around a shrine to the Mother Goddess. Fits perfectly."
<<print $player.name>> shrugs and jots down the class. Kaelen Thornwood, Cleric of the Mother Goddess. Not what they pictured going in, but it's just one session.
<<link "Sit down and get ready">>
<<set $characterSheetReturn to "Meta_TableTalk">>
<<goto "CharacterSheet">>
<</link>>That's it for now, the end of this chapter of TFDungeon.
More of Kaelen's story, and <<print $player.name>>'s, is still to come.<<set $player to {
name: "",
character: "Kaelen Thornwood",
class: "Cleric of the Mother Goddess"
}>>
<<set $friends to ["Sam", "Jordan", "Max"]>>
<<set $family to {
brother: "Ryan",
mother: "Diane",
father: "Mark"
}>>
<<set $currentEpisode to 1>>
<<set $characterSheetReturn to "">>
<<set $jobNoticedReturn to "">>
<<set $nadia to {
trust: 0,
attraction: 0,
metCount: 0
}>>
<<set $nadiaSeen to []>>
<<set $theo to {
trust: 0,
attraction: 0,
metCount: 0
}>>
<<set $theoSeen to []>>
<<set $ashwyn to {
trust: 0,
attraction: 0,
metCount: 0
}>>
<<set $e1GuardianApproach to "">>
<<set $e1SearchSeen to []>>
<<set $e1WardApproach to "">>
<<set $e7CollarWeakPoint to false>>
<<set $e2LurkerApproach to "">>
<<set $e3InvestigationApproach to "">>
<<set setup.nadiaScenePool to [
{ id: "nadia_library", label: "The girl from the library table", passage: "Day_Nadia_Library", minEpisode: 3 },
{ id: "nadia_lunch", label: "Nadia's at the next table over", passage: "Day_Nadia_Lunch", minEpisode: 3 },
{ id: "nadia_group_project", label: "Nadia's in your group project", passage: "Day_Nadia_GroupProject", minEpisode: 4 },
{ id: "nadia_quad", label: "Nadia waves you over on the quad", passage: "Day_Nadia_Quad", minEpisode: 4 },
{ id: "nadia_escalation2", label: "Nadia's waiting by the gate after class", passage: "Day_Nadia_Quad", minEpisode: 13 }
]>>
<<set setup.theoScenePool to [
{ id: "theo_gym", label: "Theo's spotting at the gym", passage: "Day_Theo_Gym", minEpisode: 6 },
{ id: "theo_hallway", label: "Theo catches up with you between classes", passage: "Day_Theo_Hallway", minEpisode: 6 },
{ id: "theo_lunch", label: "Theo waves you over at lunch", passage: "Day_Theo_Lunch", minEpisode: 7 },
{ id: "theo_library", label: "Theo's killing time in the library", passage: "Day_Theo_Library", minEpisode: 7 }
]>>
<<set $session to {
number: 1,
inCombat: false
}>>
<<set $partyHp to {
player: { current: 6, maxHp: 6 },
sam: { current: 10, maxHp: 10 },
max: { current: 7, maxHp: 7 }
}>>
<<set setup.partyMembers to {
player: { label: "Kaelen", portrait: "player_portrait", attack: 3, defense: 1, speed: 3 },
sam: { label: "Sam", portrait: "sam_portrait", attack: 4, defense: 3, speed: 2 },
max: { label: "Max", portrait: "max_portrait", attack: 3, defense: 1, speed: 4 }
}>>
<<set $combat to null>>
<<set $combatPendingAction to "">>
<<set $combatPendingTarget to "">>
<<set $bodyFeatures to {
rear: 0,
face: 0,
chest: 0,
waist: 0,
hair: 0,
skin: 0,
voice: 0
}>>
<<set $transformation to 0>>
<<set $sessionItemsHeld to []>>
<<set $outfitStreak to {
waist: 0, chest: 0, face: 0, hair: 0, skin: 0,
lastCheckedDay: 0
}>>
<<set $shaveProgressCelebrated to false>>
<<set $menswearReactionSeen to false>>
<<set $publicAttention to {
strangers: 0,
peers: 0,
normalized: false
}>>
<<set $socialSeen to []>>
<<set setup.socialPosts to [
{ id: "riley_party_throwback", episode: 11, author: "Riley",
captionStage0: "riley posted an old pic from her party. \"throwback to this one lol\"",
captionStage1: "riley posted an old pic from her party. \"throwback to this one lol\" -- somebody commented \"wait you look different now??\"",
captionStage2: "riley posted an old pic from her party. the comments have turned into a whole thread about how different you look compared to the photo.",
captionStage3: "riley posted an old pic from her party again -- unprompted, months later. \"still can't get over this pic tbh\" -- forty-some comments, none of them about riley.",
minTransformation: 0 },
{ id: "e6_party_photo_resurface", episode: 12, author: "unknown classmate",
captionStage0: "someone tagged you in an old group photo from riley's party.",
captionStage1: "someone tagged you in an old group photo from riley's party. a few replies ask which one you are in it.",
captionStage2: "the old party photo is circulating again, replies openly comparing it to how you look now.",
captionStage3: "the old party photo has become a whole recurring bit in the comments -- people tag their friends just to point out how little the photo matches you anymore.",
minTransformation: 8 },
{ id: "quiet_stranger_post", episode: 14, author: "unknown",
captionStage0: "a stranger's story mentions \"that girl from the coffee place,\" no name, no tag.",
captionStage1: "a stranger's story mentions \"that girl from the coffee place\" again -- it's just how the post refers to you now, like it's not even a description anymore.",
minTransformation: 12 }
]>>
<<set $rollResult to "">>
<<set $rollValue to 0>>
<<set $money to 100>>
<<set $dayNumber to 1>>
<<set $currentDayName to "Monday">>
<<set $skippedShift to false>>
<<set $nextDay to "">>
<<set $weekendTarget to "Weekend">>
<<set $eventChoice to "">>
<<set $tipBonus to 0>>
<<set $shaveProgress to {
face: false,
chest: false,
legs: false,
intimate: false
}>>
<<set $job to {
type: "",
label: "",
basePay: 0,
tips: false,
noticed: false,
milestoneSeen: false
}>>
<<set $henderson to {
trust: 0,
fondness: 0,
milestoneSeen: false
}>>
<<set setup.jobs to [
{ id: "cafe", label: "Coffee shop barista", basePay: 8, tips: true,
blurb: "Lower base pay, but tips can add up on a good shift." },
{ id: "fastfood", label: "Fast food counter", basePay: 7, tips: false,
blurb: "The lowest base pay of the three, and no tips to make up for it." },
{ id: "babysitting", label: "Babysitting", basePay: 10, tips: false,
blurb: "Solid, dependable pay. No tips, but no bad nights either." }
]>>
<<set setup.workWeek to []>>
<<set $school to {
visited: []
}>>
<<set setup.schoolScenes to [
{ id: "classroom", label: "Lecture", passage: "SchoolClassroom" },
{ id: "cafeteria", label: "Dining hall", passage: "SchoolCafeteria" },
{ id: "hallway", label: "Hallway lockers", passage: "SchoolHallway" }
]>>
<<set $daySeen to []>>
<<set $dayReturnTarget to "">>
<<set $daySceneStep to 0>>
<<set $eveningSeen to []>>
<<set $eveningReturnTarget to "">>
<<set $eveningFamilySeen to {
brother: [],
mother: [],
father: []
}>>
<<set $familyBond to {
brother: { closeness: 0, awareness: 0 },
mother: { closeness: 0, awareness: 0 },
father: { closeness: 0, awareness: 0 }
}>>
<<set setup.eveningBrotherScenes to [
{ id: "brother_couch", label: "Talk to your brother", passage: "Evening_Brother" },
{ id: "brother_snack", label: "Talk to your brother", passage: "Evening_Brother_Snack" },
{ id: "brother_homework", label: "Talk to your brother", passage: "Evening_Brother_Homework" },
{ id: "brother_music", label: "Talk to your brother", passage: "Evening_Brother_Music" },
{ id: "brother_teach", label: "Talk to your brother", passage: "Evening_Brother_Teach" },
{ id: "brother_rematch", label: "Talk to your brother", passage: "Evening_Brother_Rematch" },
{ id: "brother_movie_pick", label: "Talk to your brother", passage: "Evening_Brother_MoviePick" },
{ id: "brother_noticing", label: "Talk to your brother", passage: "Evening_Brother_Noticing", minTransformation: 6 },
{ id: "brother_defending", label: "Talk to your brother", passage: "Evening_Brother_Defending", minTransformation: 11 }
]>>
<<set setup.eveningMotherScenes to [
{ id: "mother_tea", label: "Talk to your mother", passage: "Evening_Mother" },
{ id: "mother_recipe", label: "Talk to your mother", passage: "Evening_Mother_Recipe" },
{ id: "mother_photos", label: "Talk to your mother", passage: "Evening_Mother_Photos" },
{ id: "mother_errand", label: "Talk to your mother", passage: "Evening_Mother_Errand" },
{ id: "mother_advice", label: "Talk to your mother", passage: "Evening_Mother_Advice" },
{ id: "mother_crossword", label: "Talk to your mother", passage: "Evening_Mother_Crossword" },
{ id: "mother_garden", label: "Talk to your mother", passage: "Evening_Mother_Garden" },
{ id: "mother_quiet_watching", label: "Talk to your mother", passage: "Evening_Mother_QuietWatching", minTransformation: 6 },
{ id: "mother_open_door", label: "Talk to your mother", passage: "Evening_Mother_OpenDoor", minTransformation: 11 }
]>>
<<set setup.eveningFatherScenes to [
{ id: "father_game", label: "Talk to your father", passage: "Evening_Father" },
{ id: "father_fixit", label: "Talk to your father", passage: "Evening_Father_FixIt" },
{ id: "father_drive", label: "Talk to your father", passage: "Evening_Father_Drive" },
{ id: "father_story", label: "Talk to your father", passage: "Evening_Father_Story" },
{ id: "father_quiz", label: "Talk to your father", passage: "Evening_Father_Quiz" },
{ id: "father_news", label: "Talk to your father", passage: "Evening_Father_News" },
{ id: "father_grill", label: "Talk to your father", passage: "Evening_Father_Grill" },
{ id: "father_garage_quiet", label: "Talk to your father", passage: "Evening_Father_GarageQuiet", minTransformation: 6 },
{ id: "father_steady", label: "Talk to your father", passage: "Evening_Father_Steady", minTransformation: 11 }
]>>
<<set $partySeen to []>>
<<set setup.partyScenes to [
{ id: "party_riley", label: "Find Riley again", passage: "E6_Party_Riley" },
{ id: "party_theo", label: "Talk to Theo by the snacks", passage: "E6_Party_Theo" },
{ id: "party_nadia", label: "Talk to Nadia on the porch", passage: "E6_Party_Nadia" },
{ id: "party_game", label: "Join the game in the living room", passage: "E6_Party_Game" },
{ id: "party_dance", label: "Dance for a while", passage: "E6_Party_Dance" },
{ id: "party_backyard", label: "Get some air in the backyard", passage: "E6_Party_Backyard" },
{ id: "party_karaoke", label: "Check out the karaoke setup", passage: "E6_Party_Karaoke" },
{ id: "party_photos", label: "Get pulled into a group photo", passage: "E6_Party_Photos" },
{ id: "party_kitchen", label: "Find Max raiding the kitchen", passage: "E6_Party_Kitchen" }
]>>
<<set setup.groupChatLines to [
{ speaker: "Jordan", text: "reminder: bring snacks this week, i'm not carrying the whole table again" },
{ speaker: "Sam", text: "does anyone actually know what a 'moon pendant' does mechanically or did Jordan just make that up" },
{ speaker: "Max", text: "i have a theory about the dice and nobody wants to hear it so i'm typing it here instead" },
{ speaker: "Jordan", text: "please stop theorizing about the dice in the group chat, Max" },
{ speaker: "Sam", text: "kaelen my beloved cleric please heal me next time before I'm literally dying, thanks" },
{ speaker: "Max", text: "who's free to playtest a one-shot idea before the next session" },
{ speaker: "Jordan", text: "does anyone else feel like the campaign is... escalating? asking for a friend" },
{ speaker: "Sam", text: "i found a d20 in my jacket pocket that isn't mine. anyone missing one" },
{ speaker: "Sam", text: "ok nobody's claimed the d20 and it's been two weeks, i'm just keeping it at this point" },
{ speaker: "Sam", text: "update: still using the mystery d20. it rolls fine. no further comment" },
{ speaker: "Max", text: "ok i wasn't going to say anything but i borrowed the mystery d20 off Sam last week and it rolled a 20 four times in one session, i counted" },
{ speaker: "Jordan", text: "Max please put the d20 back before Sam notices, and stop counting my dice rolls" }
]>>
<<set $groupChatSeen to []>>
<<set setup.dayScenePool to [
{ id: "day_lecture", label: "Sit through class", passage: "Day_Lecture" },
{ id: "day_hallway", label: "Between classes", passage: "Day_Hallway" },
{ id: "day_lunch", label: "Lunch with the group", passage: "Day_Lunch" },
{ id: "day_gym", label: "Gym class", passage: "Day_Gym" },
{ id: "day_group_project", label: "Group project work", passage: "Day_GroupProject" },
{ id: "day_schoolyard", label: "Free period on the quad", passage: "Day_Schoolyard" },
{ id: "day_detention", label: "Stuck in detention", passage: "Day_Detention" },
{ id: "day_art_class", label: "Art class", passage: "Day_ArtClass" },
{ id: "day_library", label: "A free period in the library", passage: "Day_Library" },
{ id: "day_club", label: "Club meeting after class", passage: "Day_Club" },
{ id: "day_assembly", label: "Campus town hall", passage: "Day_Assembly" },
{ id: "day_locker_room", label: "Locker room before gym", passage: "Day_LockerRoom" },
{ id: "day_substitute", label: "Substitute teacher chaos", passage: "Day_Substitute" },
{ id: "day_voice_crack", label: "Answering a question in class", passage: "Day_VoiceCrack" }
]>>
<<set $homeSeen to []>>
<<set setup.homeScenePool to [
{ id: "home_homework", label: "Homework at the kitchen table", passage: "Day_Homework" },
{ id: "home_family", label: "A few minutes with family", passage: "Day_Family" },
{ id: "home_room", label: "Decompress in your room", passage: "Day_Room" },
{ id: "home_laptop", label: "Mess around online for a while", passage: "Day_Laptop", requires: "laptop" },
{ id: "home_shower", label: "Take a shower before anything else", passage: "Day_Room_Shower" },
{ id: "home_journal", label: "Write for a while", passage: "Day_Room_Journal" }
]>>
<<set $mallReturnOverride to "">>
<<set $waystationSeen to []>>
<<set $sundaySeen to []>>
<<set $sundayOffered to []>>
<<set setup.sundayFamilyScenes to [
{ id: "sunday_family_outing", label: "Go somewhere with the family", passage: "Sunday_FamilyOuting" },
{ id: "sunday_family_cooking", label: "Cook together", passage: "Sunday_FamilyCooking" },
{ id: "sunday_family_movie", label: "Movie afternoon", passage: "Sunday_FamilyMovie" },
{ id: "sunday_family_gamenight", label: "Family game night", passage: "Sunday_FamilyGameNight" },
{ id: "sunday_family_yardwork", label: "Help out around the yard", passage: "Sunday_FamilyYardwork" },
{ id: "sunday_family_breakfast", label: "Big family breakfast", passage: "Sunday_FamilyBreakfast" },
{ id: "sunday_family_drive", label: "Take the long way somewhere", passage: "Sunday_FamilyDrive" }
]>>
<<set setup.sundayHobbyScenes to [
{ id: "sunday_hobby_draw", label: "Spend the afternoon drawing", passage: "Sunday_HobbyDraw" },
{ id: "sunday_hobby_game", label: "Lose a few hours to a game", passage: "Sunday_HobbyGame", requires: "laptop" },
{ id: "sunday_hobby_read", label: "Curl up with a book", passage: "Sunday_HobbyRead" },
{ id: "sunday_hobby_run", label: "Go for a run", passage: "Sunday_HobbyRun" }
]>>
<<set $outfit to {
underwear: "",
top: "",
bottom: "",
shoes: "",
neck: "",
hands: "",
head: ""
}>>
<<set setup.clothingItems to [
{ id: "boxers", label: "plain boxers", slot: "underwear", unlocked: true, image: "boxers", store: "men" },
{ id: "briefs", label: "athletic briefs", slot: "underwear", unlocked: true, image: "briefs", store: "men" },
{ id: "graphic_tee", label: "faded graphic t-shirt", slot: "top", unlocked: true, image: "graphic_tee", store: "men" },
{ id: "hoodie", label: "worn-in hoodie", slot: "top", unlocked: true, image: "hoodie", store: "men" },
{ id: "jeans", label: "straight-leg jeans", slot: "bottom", unlocked: true, image: "jeans", store: "men" },
{ id: "cargo_pants", label: "cargo pants", slot: "bottom", unlocked: true, image: "cargo_pants", store: "men" },
{ id: "sneakers", label: "beat-up sneakers", slot: "shoes", unlocked: true, image: "sneakers", store: "men" },
{ id: "boots", label: "scuffed boots", slot: "shoes", unlocked: true, image: "boots", store: "men" },
{ id: "no_neck", label: "nothing", slot: "neck", unlocked: true, store: "men" },
{ id: "no_hands", label: "nothing", slot: "hands", unlocked: true, store: "men" },
{ id: "no_head", label: "nothing", slot: "head", unlocked: true, store: "men" },
{ id: "button_shirt", label: "slim button-up shirt", slot: "top", unlocked: false, image: "button_shirt", price: 28, store: "men" },
{ id: "soft_cardigan", label: "soft knit cardigan", slot: "top", unlocked: false, image: "soft_cardigan", price: 34, store: "women" },
{ id: "slim_chinos", label: "slim-fit chinos", slot: "bottom", unlocked: false, image: "slim_chinos", price: 32, store: "men" },
{ id: "canvas_slipons", label: "canvas slip-ons", slot: "shoes", unlocked: false, image: "canvas_slipons", price: 26, store: "men" },
{ id: "thin_bracelet", label: "thin silver bracelet", slot: "hands", unlocked: false, image: "thin_bracelet", price: 12, store: "women" },
{ id: "lace_bralette", label: "lace bralette", slot: "underwear", unlocked: false, image: "lace_bralette", price: 22, store: "lingerie" },
{ id: "lace_briefs", label: "matching lace briefs", slot: "underwear", unlocked: false, image: "lace_briefs", price: 18, store: "lingerie" },
{ id: "cami_top", label: "silky camisole top", slot: "top", unlocked: false, image: "cami_top", price: 26, store: "lingerie" },
{ id: "sundress", label: "light sundress", slot: "top", unlocked: false, image: "sundress", price: 38, store: "women" },
{ id: "pleated_skirt", label: "pleated skirt", slot: "bottom", unlocked: false, image: "pleated_skirt", price: 30, store: "women" },
{ id: "skinny_jeans", label: "skinny jeans", slot: "bottom", unlocked: false, image: "skinny_jeans", price: 34, store: "women" },
{ id: "ballet_flats", label: "ballet flats", slot: "shoes", unlocked: false, image: "ballet_flats", price: 28, store: "women" },
{ id: "ankle_boots", label: "ankle boots", slot: "shoes", unlocked: false, image: "ankle_boots", price: 42, store: "women" },
{ id: "hoop_earrings", label: "small hoop earrings", slot: "head", unlocked: false, image: "hoop_earrings", price: 16, store: "women" },
{ id: "silver_chain", label: "simple silver chain", slot: "neck", unlocked: false, image: "silver_chain", price: 20, store: "any" },
{ id: "pendant_moon", label: "small moon pendant", slot: "neck", unlocked: false, image: "pendant_moon", price: 24, store: "any" },
{ id: "stud_earrings", label: "plain stud earrings", slot: "head", unlocked: false, image: "stud_earrings", price: 14, store: "any" },
{ id: "signet_ring", label: "engraved signet ring", slot: "hands", unlocked: false, image: "signet_ring", price: 26, store: "men" },
{ id: "charm_bracelet", label: "charm bracelet", slot: "hands", unlocked: false, image: "charm_bracelet", price: 22, store: "women" },
{ id: "drop_earrings", label: "delicate drop earrings", slot: "head", unlocked: false, image: "drop_earrings", price: 18, store: "women" },
{ id: "flannel_shirt", label: "flannel button-up", slot: "top", unlocked: false, image: "flannel_shirt", price: 30, store: "men" },
{ id: "bomber_jacket", label: "bomber jacket", slot: "top", unlocked: false, image: "bomber_jacket", price: 46, store: "men" },
{ id: "henley_shirt", label: "long-sleeve henley", slot: "top", unlocked: false, image: "henley_shirt", price: 24, store: "men" },
{ id: "denim_jacket", label: "denim jacket", slot: "top", unlocked: false, image: "denim_jacket", price: 40, store: "men" },
{ id: "track_pants", label: "track pants", slot: "bottom", unlocked: false, image: "track_pants", price: 22, store: "men" },
{ id: "leather_boots", label: "leather work boots", slot: "shoes", unlocked: false, image: "leather_boots", price: 38, store: "men" },
{ id: "high_top_sneakers", label: "high-top sneakers", slot: "shoes", unlocked: false, image: "high_top_sneakers", price: 30, store: "men" },
{ id: "leather_bracelet", label: "braided leather bracelet", slot: "hands", unlocked: false, image: "leather_bracelet", price: 14, store: "men" },
{ id: "canvas_cap", label: "canvas baseball cap", slot: "head", unlocked: false, image: "canvas_cap", price: 16, store: "men" },
{ id: "lace_teddy", label: "lace teddy bodysuit", slot: "underwear", unlocked: false, image: "lace_teddy", price: 34, store: "lingerie" },
{ id: "sheer_babydoll", label: "sheer babydoll slip", slot: "top", unlocked: false, image: "sheer_babydoll", price: 30, store: "lingerie" },
{ id: "garter_set", label: "lace garter set", slot: "underwear", unlocked: false, image: "garter_set", price: 32, store: "lingerie" },
{ id: "strappy_bralette", label: "strappy cage bralette", slot: "underwear", unlocked: false, image: "strappy_bralette", price: 28, store: "lingerie" },
{ id: "satin_slip_dress", label: "satin slip dress", slot: "top", unlocked: false, image: "satin_slip_dress", price: 36, store: "lingerie" },
{ id: "fishnet_stockings", label: "fishnet stockings", slot: "bottom", unlocked: false, image: "fishnet_stockings", price: 16, store: "lingerie" },
{ id: "thigh_high_boots", label: "thigh-high heeled boots", slot: "shoes", unlocked: false, image: "thigh_high_boots", price: 48, store: "lingerie" },
{ id: "crop_top", label: "fitted crop top", slot: "top", unlocked: false, image: "crop_top", price: 24, store: "women" },
{ id: "mini_skirt", label: "high-waisted mini skirt", slot: "bottom", unlocked: false, image: "mini_skirt", price: 26, store: "women" },
{ id: "bodycon_dress", label: "bodycon dress", slot: "top", unlocked: false, image: "bodycon_dress", price: 42, store: "women" },
{ id: "strappy_heels", label: "strappy stiletto heels", slot: "shoes", unlocked: false, image: "strappy_heels", price: 44, store: "women" },
{ id: "off_shoulder_top", label: "off-shoulder top", slot: "top", unlocked: false, image: "off_shoulder_top", price: 28, store: "women" },
{ id: "leather_leggings", label: "faux-leather leggings", slot: "bottom", unlocked: false, image: "leather_leggings", price: 32, store: "women" },
{ id: "choker_necklace", label: "velvet choker", slot: "neck", unlocked: false, image: "choker_necklace", price: 14, store: "women" }
]>>
<<set setup.pharmacyItems to [
{ id: "razor", label: "disposable razor pack", price: 6, blurb: "A pack of basic razors.", image: "razor" },
{ id: "shaving_cream", label: "shaving cream", price: 5, blurb: "Standard drugstore can.", image: "shaving_cream" },
{ id: "vitamins_mens", label: "men's multivitamin", price: 9, blurb: "The bottle you always grab out of habit.", image: "vitamins_mens" },
{ id: "vitamins_womens", label: "women's multivitamin", price: 9, blurb: "You pick it up without quite deciding to.", image: "vitamins_womens" },
{ id: "face_wash", label: "gentle face wash", price: 7, blurb: "Unscented, works for anyone.", image: "face_wash" },
{ id: "moisturizer", label: "daily moisturizer", price: 8, blurb: "SPF included.", image: "moisturizer" },
{ id: "deodorant", label: "deodorant", price: 5, blurb: "The usual brand.", image: "deodorant" },
{ id: "painkillers", label: "pain relief tablets", price: 6, blurb: "For the headaches you've been getting.", image: "painkillers" },
{ id: "bandages", label: "box of bandages", price: 4, blurb: "Always useful to have around.", image: "bandages" },
{ id: "shampoo", label: "shampoo and conditioner set", price: 11, blurb: "Two-in-one bottle.", image: "shampoo" },
{ id: "hair_ties", label: "pack of hair ties", price: 3, blurb: "You've been needing more of these lately.", image: "hair_ties" },
{ id: "lip_balm", label: "lip balm", price: 3, blurb: "Unscented, fits in any pocket.", image: "lip_balm" }
]>>
<<set $pharmacyBought to []>>
<<set setup.electronicsItems to [
{ id: "laptop", label: "budget laptop", price: 320, blurb: "Good enough for coursework and late-night forum browsing.", image: "laptop" },
{ id: "webcam", label: "USB webcam", price: 35, blurb: "For video calls, or streaming the next campaign online.", image: "webcam" },
{ id: "smartphone", label: "mid-range smartphone", price: 260, blurb: "A decent upgrade from what you've got now.", image: "smartphone" },
{ id: "headphones", label: "wireless headphones", price: 45, blurb: "Noise-cancelling enough to survive the campus shuttle.", image: "headphones" },
{ id: "phone_case", label: "phone case", price: 12, blurb: "Simple protection, several colors available.", image: "phone_case" },
{ id: "charger_cable", label: "extra charging cable", price: 8, blurb: "You can never have too many.", image: "charger_cable" }
]>>
<<set $electronicsOwned to []>>
<<set setup.bookstoreItems to [
{ id: "dnd_handbook", label: "tabletop RPG core rulebook", price: 24, blurb: "Not the one Jordan uses, but close enough to be useful.", image: "dnd_handbook" },
{ id: "fantasy_novel", label: "battered fantasy paperback", price: 9, blurb: "Cover art that looks suspiciously like the Frostbound Keep.", image: "fantasy_novel" },
{ id: "graphic_novel", label: "graphic novel", price: 14, blurb: "Sam's been trying to get you to read this one for weeks.", image: "graphic_novel" },
{ id: "sketchbook", label: "blank sketchbook", price: 7, blurb: "You've been sketching more than usual lately.", image: "sketchbook" },
{ id: "comic_singles", label: "stack of comic singles", price: 11, blurb: "This week's new releases.", image: "comic_singles" },
{ id: "puzzle_book", label: "puzzle and riddle book", price: 6, blurb: "Max would love this.", image: "puzzle_book" }
]>>
<<set $bookstoreOwned to []>>
<<set setup.beautyItems to [
{ id: "tinted_lip_balm", label: "tinted lip balm", price: 6, blurb: "Barely-there color.", image: "tinted_lip_balm" },
{ id: "concealer", label: "concealer stick", price: 9, blurb: "For covering up the tired mornings.", image: "concealer" },
{ id: "mascara", label: "mascara", price: 10, blurb: "You're not sure why you picked it up. You did anyway.", image: "mascara" },
{ id: "nail_polish", label: "bottle of nail polish", price: 7, blurb: "A soft, quiet color.", image: "nail_polish" },
{ id: "perfume_sample", label: "small perfume sample", price: 12, blurb: "Something floral, unfamiliar, oddly appealing.", image: "perfume_sample" },
{ id: "hair_serum", label: "hair shine serum", price: 11, blurb: "Promises to tame flyaways.", image: "hair_serum" },
{ id: "cologne_sample", label: "small cologne sample", price: 12, blurb: "Familiar, the scent you've always worn.", image: "cologne_sample" },
{ id: "facial_scrub", label: "exfoliating facial scrub", price: 8, blurb: "Basic, unscented.", image: "facial_scrub" }
]>>
<<set $beautyOwned to []>>
<<set $beautyFirstUseSeen to false>>
<<set $foodCourtSeen to []>>
<<set setup.foodCourtEvents to [
{ id: "foodcourt_nadia", label: "You spot Nadia a few tables over", passage: "FoodCourt_Nadia", minEpisode: 3 },
{ id: "foodcourt_theo", label: "Theo waves you over from the smoothie stand", passage: "FoodCourt_Theo", minEpisode: 6 },
{ id: "foodcourt_sam_max", label: "Sam and Max are arguing over a shared tray of fries", passage: "FoodCourt_SamMax" },
{ id: "foodcourt_mirror", label: "You catch your reflection in the food court's mirrored pillar", passage: "FoodCourt_Mirror" },
{ id: "foodcourt_stranger_comment", label: "Someone at the next table says something you weren't meant to hear", passage: "FoodCourt_StrangerComment" },
{ id: "foodcourt_receipt", label: "The cashier hands you your receipt and pauses a beat too long", passage: "FoodCourt_Receipt" },
{ id: "foodcourt_family", label: "You spot a familiar face from the neighborhood", passage: "FoodCourt_Family" },
{ id: "foodcourt_group_chat", label: "Your phone buzzes with a group chat notification", passage: "FoodCourt_GroupChat" },
{ id: "foodcourt_lost_tray", label: "You almost sit down at the wrong table", passage: "FoodCourt_LostTray" },
{ id: "foodcourt_quiet_moment", label: "You just sit with your food for a minute", passage: "FoodCourt_QuietMoment" }
]>>
<<set $randomEventChance to 25>>
<<set $randomEventsSeen to []>>
<<set $randomEventFired to false>>
<<set $randomEventContinue to "MorningHub">>
<<set setup.randomEvents to [
{ id: "found_money", passage: "RandomEvent_FoundMoney" },
{ id: "spilled_coffee", passage: "RandomEvent_SpilledCoffee" },
{ id: "text_from_sam", passage: "RandomEvent_TextFromSam" },
{ id: "lost_earring", passage: "RandomEvent_LostEarring", requires: "hoop_earrings|stud_earrings|drop_earrings" },
{ id: "hallway_compliment", passage: "RandomEvent_HallwayCompliment" },
{ id: "dice_dream", passage: "RandomEvent_DiceDream" },
{ id: "ryan_borrowed", passage: "RandomEvent_RyanBorrowed" },
{ id: "mirror_double_take", passage: "RandomEvent_MirrorDoubleTake" }
]>>
<<set setup.sessionItems to [
{ id: "sigil_mothers_favor", label: "Sigil of the Mother's Favor", image: "amulet_closeup", episode: 1, flavor: "A warm, heavy amulet pulled from the shrine's altar. Strengthens healing magic, but the goddess's blessing isn't subtle." },
{ id: "ritual_robes", label: "Ceremonial Robes", image: "ritual_robes", episode: 3, flavor: "Layered silk and embroidery from House Voss, fitted uncannily well on the first try." },
{ id: "bazaar_mace", label: "Bazaar Mace", image: "session_bazaar_mace", episode: 5, flavor: "A heavy, honest weapon bought at the bazaar's weaponsmith stall." },
{ id: "bazaar_scepter", label: "Bazaar Scepter", image: "session_bazaar_scepter", episode: 5, flavor: "Lighter than the mace, built for a different kind of grip entirely." },
{ id: "bazaar_iron_amulet", label: "Iron Amulet", image: "session_bazaar_iron_amulet", episode: 5, flavor: "Heavy, cold, efficient, exactly what it looks like." },
{ id: "bazaar_pendant", label: "Bazaar Pendant", image: "session_bazaar_pendant", episode: 5, flavor: "A fine chain that settled against the collarbone like it belonged there already." },
{ id: "bazaar_cloak", label: "Bazaar Cloak", image: "session_bazaar_cloak", episode: 5, flavor: "Warm, unremarkable, does exactly what a cloak does." },
{ id: "bazaar_robe", label: "Bazaar Robe", image: "session_bazaar_robe", episode: 5, flavor: "Tailored to a shape that didn't quite exist yet when it was bought." },
{ id: "carved_hairpin", label: "Carved Hairpin", image: "session_hairpin", episode: 5, flavor: "A free gift from a fairground trinket vendor. Sits like it already knew where it was going." },
{ id: "fair_ribbon", label: "Fairground Ribbon", image: "session_ribbon", episode: 5, flavor: "A cheap prize from the bell-ringer game, handed over grudgingly by the barker." },
{ id: "caricature_sketch", label: "Caricature Sketch", image: "session_caricature", episode: 5, flavor: "A lopsided, kindly-exaggerated sketch from a fairground artist's first sale of the day." },
{ id: "frost_circlet", label: "Frost-Touched Circlet", image: "frost_ward", episode: 7, flavor: "Set against the temple at the threshold. The cold stopped registering as cold at all." },
{ id: "shepherds_ward", label: "Shepherd's Ward Token", image: "session_shepherds_ward", episode: 8, flavor: "A small carved token, pressed into Kaelen's hand by a grateful refugee at the waystation." },
{ id: "ashwyn_pass_wrap", label: "Ashwyn's Wrap", image: "ashwyn_pass_wrap", episode: 10, flavor: "Worn close against the skin, it convinces the cold there's nowhere left to get in." },
{ id: "salt_road_robe", label: "Salt Road Pilgrim's Robe", image: "session_salt_road_robe", episode: 11, flavor: "Traded off a caravan pilgrim who insisted it would fit better than it had any right to. It did." },
{ id: "quiet_court_vestment", label: "Quiet Court Vestment", image: "session_quiet_court_vestment", episode: 12, flavor: "Ceremonial dress required for the public rite -- Kaelen only meant to wear it for the afternoon." },
{ id: "archive_gloves", label: "Archivist's Gloves", image: "session_archive_gloves", episode: 13, flavor: "Soft, worn leather, handed over by the archive keeper without being asked for back." },
{ id: "ledger_amulet", label: "Ledger Amulet", image: "session_ledger_amulet", episode: 13, flavor: "Found tucked inside the ledger itself, like it had been waiting there for exactly one specific reader." },
{ id: "reflection_ward_trinket", label: "Reflection Ward Trinket", image: "session_reflection_trinket", episode: 14, flavor: "Taken from the mirror-warded chamber. It's hard to say afterward which reflection actually reached for it first." }
]>><<widget "img">>
<<if $args[0]>>
<img @src="'assets/images/' + $args[0] + '.webp'" style="max-width:100%;height:auto;">
<</if>>
<</widget>>
<<widget "voiceSample">>
<<if $args[0] isnot undefined>>
<audio @src="'assets/audio/voice_stage' + $args[0] + '.mp3'" controls preload="none" style="height:2em;vertical-align:middle;"></audio>
<</if>>
<</widget>>
<<widget "wardrobeSlot">>
<<nobr>>
<<set _slot to $args[0]>>
<<set _returnPassage to (($args[1] is undefined) ? "Wardrobe" : $args[1])>>
<<set _options to setup.clothingItems.filter(function (item) { return item.slot === _slot && item.unlocked; })>>
<p><strong><<= _slot.toUpperFirst()>>:</strong></p>
<div style="display:flex; flex-wrap:wrap; gap:1em; margin-bottom:1em;">
<<for _i to 0; _i lt _options.length; _i++>>
<<set _item to _options[_i]>>
<<set _border to ($outfit[_slot] is _item.id) ? "#8fd" : "transparent">>
<<set _imgHtml to _item.image
? "<img src=\"assets/images/clothing/" + _item.image + ".webp\" style=\"width:80px;height:80px;object-fit:cover;border-radius:6px;border:2px solid " + _border + ";\"><br>"
: "">>
<<set _linkHtml to "<div style=\"text-align:center;\">" + _imgHtml + _item.label + "</div>">>
<<capture _slot, _item, _linkHtml, _returnPassage>>
<div>
<<link _linkHtml>>
<<set $outfit[_slot] to _item.id>>
<<goto _returnPassage>>
<</link>>
</div>
<</capture>>
<</for>>
</div>
<<if $outfit[_slot] neq "">> <em>(currently: <<= setup.clothingItems.find(function (item) { return item.id === $outfit[_slot]; }).label>>)</em><</if>>
<</nobr>>
<</widget>>
<<widget "schoolSceneList">>
<<nobr>>
<<for _s range setup.schoolScenes>>
<<capture _s>>
<<if $school.visited.includes(_s.id)>>
<p><s><<= _s.label>></s> <em>(done)</em></p>
<<else>>
<p><<link _s.label>>
<<set $school.visited.push(_s.id)>>
<<goto _s.passage>>
<</link>></p>
<</if>>
<</capture>>
<</for>>
<</nobr>>
<</widget>>
<<widget "dayScenePicker">>
<<nobr>>
<<set _eligible to setup.dayScenePool.filter(function (s) { return !s.requires || $electronicsOwned.includes(s.requires); })>>
<<set _available to _eligible.filter(function (s) { return !$daySeen.includes(s.id); })>>
<<if _available.length lt 1>>
<<set $daySeen to []>>
<<set _available to _eligible.slice(0)>>
<</if>>
<<set _shuffled to _available.slice(0).sort(function () { return Math.random() - 0.5; })>>
<<set _picks to _shuffled.slice(0, 2)>>
<<for _i to 0; _i lt _picks.length; _i++>>
<<set _pick to _picks[_i]>>
<<capture _pick>>
<p><<link _pick.label>>
<<set $daySeen.push(_pick.id)>>
<<set $daySceneStep to $daySceneStep + 1>>
<<goto _pick.passage>>
<</link>></p>
<</capture>>
<</for>>
<</nobr>>
<</widget>>
<<widget "homeScenePicker">>
<<nobr>>
<<set _eligible to setup.homeScenePool.filter(function (s) { return !s.requires || $electronicsOwned.includes(s.requires); })>>
<<set _available to _eligible.filter(function (s) { return !$homeSeen.includes(s.id); })>>
<<if _available.length lt 1>>
<<set $homeSeen to []>>
<<set _available to _eligible.slice(0)>>
<</if>>
<<set _pick to _available[random(0, _available.length - 1)]>>
<<capture _pick>>
<p><<link _pick.label>>
<<set $homeSeen.push(_pick.id)>>
<<goto _pick.passage>>
<</link>></p>
<</capture>>
<</nobr>>
<</widget>>
<<widget "foodCourtEventPicker">>
<<nobr>>
<<set _eligible to setup.foodCourtEvents.filter(function (e) { return e.minEpisode === undefined || $currentEpisode >= e.minEpisode; })>>
<<set _available to _eligible.filter(function (e) { return !$foodCourtSeen.includes(e.id); })>>
<<if _available.length lt 1>>
<<set $foodCourtSeen to []>>
<<set _available to _eligible.slice(0)>>
<</if>>
<<set _pick to _available[random(0, _available.length - 1)]>>
<<capture _pick>>
<p><<link _pick.label>>
<<set $foodCourtSeen.push(_pick.id)>>
<<goto _pick.passage>>
<</link>></p>
<</capture>>
<</nobr>>
<</widget>>
<<widget "romanceScenePicker">>
<<nobr>>
<<set _npcKey to $args[0]>>
<<set _pool to $args[1]>>
<<set _seenVar to $args[2]>>
<<if random(1,100) lte 45>>
<<set _eligible to _pool.filter(function (s) {
return $currentEpisode >= s.minEpisode && (s.minTransformation === undefined || $transformation >= s.minTransformation);
})>>
<<if _eligible.length gt 0>>
<<set _available to _eligible.filter(function (s) { return !_seenVar.includes(s.id); })>>
<<if _available.length lt 1>>
<<set _seenVar.splice(0, _seenVar.length)>>
<<set _available to _eligible.slice(0)>>
<</if>>
<<set _pick to _available[random(0, _available.length - 1)]>>
<<capture _npcKey, _seenVar, _pick>>
<p><<link _pick.label>>
<<set _seenVar.push(_pick.id)>>
<<set State.variables[_npcKey].metCount to State.variables[_npcKey].metCount + 1>>
<<goto _pick.passage>>
<</link>></p>
<</capture>>
<</if>>
<</if>>
<</nobr>>
<</widget>>
<<widget "relationshipSummary">>
<<nobr>>
<<set _key to $args[0]>>
<<set _label to $args[1]>>
<<set _stats to State.variables[_key]>>
<<set _trust to _stats.trust>>
<<if _key is "henderson">>
<<set _warmth to _stats.fondness>>
<<else>>
<<set _warmth to _stats.attraction>>
<</if>>
<<set _trustTier to (_trust lte 1) ? "barely knows them" : (_trust lte 3) ? "warming up to you" : (_trust lte 5) ? "trusts you" : "trusts you completely">>
<<set _warmthTier to (_warmth lte 1) ? "" : (_warmth lte 3) ? ", and there's something warmer there too" : (_warmth lte 5) ? ", and it's clearly more than friendly" : ", and it's about as obvious as it could be">>
<p><strong><<= _label>>:</strong> <<= _trustTier>><<= _warmthTier>>.</p>
<</nobr>>
<</widget>>
<<widget "familyBondSummary">>
<<nobr>>
<<set _members to [{ key: "brother", label: $family.brother }, { key: "mother", label: $family.mother }, { key: "father", label: $family.father }]>>
<<for _m range _members>>
<<set _closeness to $familyBond[_m.key].closeness>>
<<set _tier to (_closeness lte 1) ? "distant, mostly routine" : (_closeness lte 3) ? "a little closer than usual" : (_closeness lte 5) ? "genuinely close" : "closer than you've ever been">>
<p><strong><<= _m.label>>:</strong> <<= _tier>>.</p>
<</for>>
<</nobr>>
<</widget>>
<<widget "sundayOfferScenes">>
<<nobr>>
<<set _combined to setup.sundayFamilyScenes.concat(setup.sundayHobbyScenes)>>
<<set _eligible to _combined.filter(function (s) { return !s.requires || $electronicsOwned.includes(s.requires); })>>
<<set _shuffled to _eligible.slice(0).sort(function () { return Math.random() - 0.5; })>>
<<set _count to 3 + random(0, 2)>>
<<set $sundayOffered to _shuffled.slice(0, _count).map(function (s) { return s.id; })>>
<</nobr>>
<</widget>>
<<widget "sundayPicker">>
<<nobr>>
<<set _pool to $args[0]>>
<<set _eligible to _pool.filter(function (s) { return $sundayOffered.includes(s.id); })>>
<<for _s range _eligible>>
<<capture _s>>
<<if $sundaySeen.includes(_s.id)>>
<p><s><<= _s.label>></s> <em>(done)</em></p>
<<else>>
<p><<link _s.label>>
<<set $sundaySeen.push(_s.id)>>
<<goto _s.passage>>
<</link>></p>
<</if>>
<</capture>>
<</for>>
<</nobr>>
<</widget>>
<<widget "eveningFamilyMemberLink">>
<<nobr>>
<<set _pool to $args[0]>>
<<set _member to $args[1]>>
<<set _eligible to _pool.filter(function (s) {
return s.minTransformation === undefined || $transformation gte s.minTransformation;
})>>
<<set _available to _eligible.filter(function (s) { return !$eveningFamilySeen[_member].includes(s.id); })>>
<<if _available.length lt 1>>
<<set $eveningFamilySeen[_member] to []>>
<<set _available to _eligible.slice(0)>>
<</if>>
<<set _pick to _available[random(0, _available.length - 1)]>>
<<capture _pick, _member>>
<p><<link _pick.label>>
<<set $eveningFamilySeen[_member].push(_pick.id)>>
<<goto _pick.passage>>
<</link>></p>
<</capture>>
<</nobr>>
<</widget>>
<<widget "eveningFamilyList">>
<<nobr>>
<<eveningFamilyMemberLink setup.eveningBrotherScenes "brother">>
<<eveningFamilyMemberLink setup.eveningMotherScenes "mother">>
<<eveningFamilyMemberLink setup.eveningFatherScenes "father">>
<</nobr>>
<</widget>>
<<widget "partySceneList">>
<<nobr>>
<<for _p range setup.partyScenes>>
<<capture _p>>
<<if _p.id is "party_riley">>
<p><<link _p.label>>
<<if not $partySeen.includes(_p.id)>>
<<set $partySeen.push(_p.id)>>
<</if>>
<<goto _p.passage>>
<</link>></p>
<<elseif $partySeen.includes(_p.id)>>
<p><s><<= _p.label>></s> <em>(done)</em></p>
<<else>>
<p><<link _p.label>>
<<set $partySeen.push(_p.id)>>
<<goto _p.passage>>
<</link>></p>
<</if>>
<</capture>>
<</for>>
<</nobr>>
<</widget>>
<<widget "mallItemList">>
<<nobr>>
<<set _slotFilter to $args[0]>>
<<set _storeFilter to $args[1]>>
<<set _returnPassage to $args[2]>>
<<for _i to 0; _i lt setup.clothingItems.length; _i++>>
<<set _item to setup.clothingItems[_i]>>
<<if _item.slot is _slotFilter and _item.price isnot undefined and _item.store is _storeFilter>>
<<capture _item, _returnPassage>>
<div style="display:flex; align-items:center; gap:1em; margin-bottom:0.75em;">
<<if _item.image>>
<img @src="'assets/images/clothing/' + _item.image + '.webp'" style="width:64px;height:64px;object-fit:cover;border-radius:6px;">
<</if>>
<div>
<strong><<= _item.label>></strong> — <<print "$" + _item.price>>
<<if _item.unlocked>>
<br><em>(already own this)</em>
<<elseif $money gte _item.price>>
<br><<link "Buy">>
<<set $money to $money - _item.price>>
<<set _item.unlocked to true>>
<<goto _returnPassage>>
<</link>>
<<else>>
<br><em>(not enough money)</em>
<</if>>
</div>
</div>
<</capture>>
<</if>>
<</for>>
<</nobr>>
<</widget>>
<<widget "jobNoticedCheck">>
<<nobr>>
<<set _returnPassage to $args[0]>>
<<if $job.type isnot "" and $job.noticed is false and $transformation gte 14>>
<<set $jobNoticedReturn to _returnPassage>>
<<switch $job.type>>
<<case "cafe">>
<<goto "JobNoticed_Cafe">>
<<case "fastfood">>
<<goto "JobNoticed_Fastfood">>
<<default>>
<<goto "JobNoticed_Babysitting">>
<</switch>>
<</if>>
<</nobr>>
<</widget>>
<<widget "hendersonMilestoneCheck">>
<<nobr>>
<<set _returnPassage to $args[0]>>
<<if $job.type is "babysitting" and $henderson.milestoneSeen is false and $henderson.trust gte 4>>
<<set $jobNoticedReturn to _returnPassage>>
<<goto "Henderson_Milestone">>
<</if>>
<</nobr>>
<</widget>>
<<widget "jobMilestoneCheck">>
<<nobr>>
<<set _returnPassage to $args[0]>>
<<if ($job.type is "cafe" or $job.type is "fastfood") and $job.milestoneSeen is false and $transformation gte 11>>
<<set $jobNoticedReturn to _returnPassage>>
<<switch $job.type>>
<<case "cafe">>
<<goto "JobMilestone_Cafe">>
<<default>>
<<goto "JobMilestone_Fastfood">>
<</switch>>
<</if>>
<</nobr>>
<</widget>>
<<widget "outfitReaction">>
<<nobr>>
<<set _wornItems to Object.keys($outfit).map(function (slot) { return setup.clothingItems.find(function (item) { return item.id === $outfit[slot]; }); }).filter(function (item) { return item; })>>
<<set _wornStores to _wornItems.map(function (item) { return item.store; })>>
<<if _wornStores.includes("lingerie")>>
<p><em>You're hyper-aware of what's under your uniform today, in a way nobody else at work has any reason to notice.</em></p>
<<elseif _wornStores.includes("women")>>
<p><em>Nobody says anything about what you're wearing. Nobody's said anything about it in a while now.</em></p>
<</if>>
<</nobr>>
<</widget>>
<<widget "publicReactionCheck">>
<<nobr>>
<<set _context to $args[0]>>
<<set _normalized to $publicAttention.normalized>>
<<set _pool to []>>
<<if _context is "street">>
<<set _pool to [
"Someone waiting at the crosswalk looks a beat too long before the light changes.",
"A stranger holds the door a half-second longer than the moment calls for, eyes not quite where they should be.",
"A car slows alongside the sidewalk for a few seconds before it doesn't."
]>>
<<elseif _context is "school">>
<<set _pool to [
"Someone you don't recognize takes a photo without asking, doesn't even try to hide it.",
"A knot of underclassmen goes quiet as you pass, then not quiet at all right after.",
"Someone mutters something to a friend and neither of them bothers lowering their voice enough."
]>>
<<elseif _context is "job">>
<<set _pool to [
"A customer says \"thank you, miss\" without a flicker of hesitation.",
"Someone lingers at the counter a little longer than the order requires.",
"A regular starts timing their visits to your shifts, and it stopped being subtle a while ago."
]>>
<<elseif _context is "party">>
<<set _pool to [
"Someone you've never met compliments you like it's an opening line, not a compliment.",
"A stranger asks your friend, not you, \"who is that,\" like you're not standing right there."
]>>
<</if>>
<<if _normalized and _context is "street">>
<<set _pool to _pool.concat(["Nobody on the street reacts to you at all today -- not a glance, not a pause. It doesn't feel like relief."])>>
<</if>>
<<if $transformation gte 14 and _pool.length gt 0>>
<p><em><<= _pool[random(0, _pool.length - 1)]>></em></p>
<</if>>
<</nobr>>
<</widget>>
<<widget "jobObjectificationCheck">>
<<nobr>>
<<if $job.type isnot "" and $transformation gte 18>>
<<set _pool to [
"A regular has started timing his coffee runs to your shifts -- three days running now.",
"A coworker mentions, not quite as a joke, that tips have been better \"since you started looking like that.\"",
"The uniform doesn't sit right anymore, not in a way you can point to -- just a fit that used to make sense and doesn't."
]>>
<p><em><<= _pool[random(0, _pool.length - 1)]>></em></p>
<</if>>
<</nobr>>
<</widget>>
<<widget "recalcTransformation">>
<<set $transformation to Object.keys($bodyFeatures).reduce(function (sum, k) {
return sum + (k === "voice" ? 0 : $bodyFeatures[k]);
}, 0)>>
<</widget>>
<<widget "genderLabel">>
<<if $transformation lte 3>>
<<= "Male">>
<<elseif $transformation lte 7>>
<<= "Shifting">>
<<elseif $transformation lte 11>>
<<= "Uncertain">>
<<else>>
<<= "Female">>
<</if>>
<</widget>>
<<widget "outfitDailyCheck">>
<<nobr>>
<<if $outfitStreak.lastCheckedDay isnot $dayNumber>>
<<set $outfitStreak.lastCheckedDay to $dayNumber>>
<<set _wornItems to Object.keys($outfit).map(function (slot) {
return setup.clothingItems.find(function (item) { return item.id === $outfit[slot]; });
}).filter(function (item) { return item; })>>
<<set _lingerieCount to _wornItems.filter(function (item) { return item.store === "lingerie"; }).length>>
<<set _feminineTopBottom to _wornItems.some(function (item) {
return (item.slot === "top" || item.slot === "bottom") && (item.store === "women" || item.store === "lingerie");
})>>
<<set _lingerieUnderwear to _wornItems.some(function (item) { return item.slot === "underwear" && item.store === "lingerie"; })>>
<<set _faceJewelry to _wornItems.some(function (item) {
return (item.slot === "neck" || item.slot === "head") && (item.store === "women" || item.store === "any");
})>>
<<set _hairAccessory to _wornItems.some(function (item) { return item.slot === "hands" && item.store === "women"; })>>
<<if _feminineTopBottom>><<set $outfitStreak.waist to $outfitStreak.waist + 1>><<else>><<set $outfitStreak.waist to 0>><</if>>
<<if _lingerieUnderwear>><<set $outfitStreak.chest to $outfitStreak.chest + 1>><<else>><<set $outfitStreak.chest to 0>><</if>>
<<if _faceJewelry>><<set $outfitStreak.face to $outfitStreak.face + 1>><<else>><<set $outfitStreak.face to 0>><</if>>
<<if _hairAccessory>><<set $outfitStreak.hair to $outfitStreak.hair + 1>><<else>><<set $outfitStreak.hair to 0>><</if>>
<<if _lingerieCount gte 3>><<set $outfitStreak.skin to $outfitStreak.skin + 1>><<else>><<set $outfitStreak.skin to 0>><</if>>
<<set _bumped to false>>
<<for _k range ["waist", "chest", "face", "hair", "skin"]>>
<<capture _k>>
<<if $outfitStreak[_k] gte 5 and $bodyFeatures[_k] lt 4>>
<<set $bodyFeatures[_k] to $bodyFeatures[_k] + 1>>
<<set $outfitStreak[_k] to 0>>
<<set _bumped to true>>
<</if>>
<</capture>>
<</for>>
<<if _bumped>><<recalcTransformation>><</if>>
<</if>>
<</nobr>>
<</widget>>
<<widget "consumableList">>
<<nobr>>
<<set _catalog to $args[0]>>
<<set _ownedList to $args[1]>>
<<set _returnPassage to $args[2]>>
<<for _i to 0; _i lt _catalog.length; _i++>>
<<set _item to _catalog[_i]>>
<<capture _item, _ownedList, _returnPassage>>
<div style="display:flex; align-items:center; gap:1em; margin-bottom:0.75em;">
<<if _item.image>>
<img @src="'assets/images/products/' + _item.image + '.webp'" style="width:64px;height:64px;object-fit:cover;border-radius:6px;">
<</if>>
<div>
<strong><<= _item.label>></strong> — <<print "$" + _item.price>>
<br><em><<= _item.blurb>></em>
<<if _ownedList.includes(_item.id)>>
<br><em>(already bought)</em>
<<elseif $money gte _item.price>>
<br><<link "Buy">>
<<set $money to $money - _item.price>>
<<run _ownedList.push(_item.id)>>
<<goto _returnPassage>>
<</link>>
<<else>>
<br><em>(not enough money)</em>
<</if>>
</div>
</div>
<</capture>>
<</for>>
<</nobr>>
<</widget>>
<<widget "ownedItemList">>
<<nobr>>
<<set _label to $args[0]>>
<<set _catalog to $args[1]>>
<<set _ownedList to $args[2]>>
<<set _ownedItems to _catalog.filter(function (item) { return _ownedList.includes(item.id); })>>
<p><strong><<= _label>></strong></p>
<<if _ownedItems.length is 0>>
<p><em>Nothing bought yet.</em></p>
<<else>>
<<for _i to 0; _i lt _ownedItems.length; _i++>>
<<set _item to _ownedItems[_i]>>
<div style="display:flex; align-items:center; gap:1em; margin-bottom:0.5em;">
<<if _item.image>>
<img @src="'assets/images/products/' + _item.image + '.webp'" style="width:48px;height:48px;object-fit:cover;border-radius:6px;">
<</if>>
<span><<= _item.label>></span>
</div>
<</for>>
<</if>>
<</nobr>>
<</widget>>
<<widget "sessionItemList">>
<<nobr>>
<<set _mode to $args[0]>>
<<set _heldItems to setup.sessionItems.filter(function (item) { return $sessionItemsHeld.includes(item.id); })>>
<<if _heldItems.length is 0>>
<p><em>Nothing carried over from the sessions yet.</em></p>
<<else>>
<<if _mode is "compact">>
<div class="parchment-grid">
<<for _i to 0; _i lt _heldItems.length; _i++>>
<<set _item to _heldItems[_i]>>
<div>
<img @src="'assets/images/session_items/' + _item.image + '.webp'" style="width:80px;height:80px;object-fit:cover;border-radius:6px;">
<p><<= _item.label>></p>
</div>
<</for>>
</div>
<<else>>
<<for _i to 0; _i lt _heldItems.length; _i++>>
<<set _item to _heldItems[_i]>>
<div style="display:flex; align-items:center; gap:1em; margin-bottom:0.75em;">
<img @src="'assets/images/session_items/' + _item.image + '.webp'" style="width:64px;height:64px;object-fit:cover;border-radius:6px;">
<div>
<strong><<= _item.label>></strong> <em>(Episode <<= _item.episode>>)</em>
<br><<= _item.flavor>>
</div>
</div>
<</for>>
<</if>>
<</if>>
<</nobr>>
<</widget>>
<<widget "diceCheck">>
<<nobr>>
<<set _mode to $args[0]>>
<<set _threshold to (($args[1] is undefined) ? 11 : $args[1])>>
<<set _label to (($args[2] is undefined) ? "Roll" : $args[2])>>
<<set _target to (($args[3] is undefined) ? "" : $args[3])>>
<<set _linkText to "[" + _label + "]">>
<<capture _mode, _threshold, _target>>
<<link _linkText>>
<<switch _mode>>
<<case "fixed">>
<<set $rollValue to 0>>
<<set $rollResult to "fixed">>
<<case "success">>
<<set $rollValue to random(_threshold, 19)>>
<<set $rollResult to "success">>
<<case "fail">>
<<set $rollValue to random(2, _threshold - 1)>>
<<set $rollResult to "fail">>
<<default>>
<<set $rollValue to random(1, 20)>>
<<if $rollValue is 1>>
<<set $rollResult to "crit-fail">>
<<elseif $rollValue is 20>>
<<set $rollResult to "crit-success">>
<<elseif $rollValue gte _threshold>>
<<set $rollResult to "success">>
<<else>>
<<set $rollResult to "fail">>
<</if>>
<</switch>>
<<if _target isnot "">>
<<goto _target>>
<</if>>
<</link>>
<</capture>>
<</nobr>>
<</widget>>
<<widget "shiftPay">>
<<nobr>>
<<set _tipBase to $job.tips ? random(3, 18) : 0>>
<<set _tip to $job.tips ? Math.max(0, _tipBase + $tipBonus) : _tipBase>>
<<set _total to $job.basePay + _tip>>
<<set $money to $money + _total>>
<p><em>
<<if _tip gt 0>>
You clock out with <<print "$" + $job.basePay>> in wages plus <<print "$" + _tip>> in tips: <<print "$" + _total>> total.
<<else>>
You clock out with <<print "$" + _total>> in wages.
<</if>>
(<<print "$" + $money>> total now.)
</em></p>
<<set $tipBonus to 0>>
<</nobr>>
<</widget>>
<<widget "maybeRandomEvent">>
<<nobr>>
<<set _returnPassage to $args[0]>>
<<if random(1, 100) lte $randomEventChance>>
<<set _eligible to setup.randomEvents.filter(function (e) {
if (!e.requires) return true;
return e.requires.split("|").some(function (id) { return $outfit.top === id || $outfit.bottom === id || $outfit.neck === id || $outfit.hands === id || $outfit.head === id; });
})>>
<<set _available to _eligible.filter(function (e) { return !$randomEventsSeen.includes(e.id); })>>
<<if _available.length lt 1>>
<<set $randomEventsSeen to []>>
<<set _available to _eligible.slice(0)>>
<</if>>
<<if _available.length gt 0>>
<<set _pick to _available[random(0, _available.length - 1)]>>
<<set $randomEventsSeen.push(_pick.id)>>
<<set $randomEventContinue to _returnPassage>>
<<goto _pick.passage>>
<</if>>
<</if>>
<</nobr>>
<</widget>>
<<widget "rollBanner">>
<<if $rollResult isnot "">>
<p><em>
<<if $rollValue gt 0>>You roll... <<= $rollValue>>! <</if>>
<<switch $rollResult>>
<<case "crit-success">>Critical success.
<<case "crit-fail">>Critical failure.
<<case "success">>Success.
<<case "fail">>Failure.
<<case "fixed">>
<</switch>>
</em></p>
<</if>>
<</widget>>
<<widget "applyHp">>
<<nobr>>
<<set _member to $args[0]>>
<<set _delta to $args[1]>>
<<set $partyHp[_member].current to Math.max(0, Math.min($partyHp[_member].maxHp, $partyHp[_member].current + _delta))>>
<</nobr>>
<</widget>>
<<widget "kaelenShieldOffer">>
<<nobr>>
<<set _ally to $args[0]>>
<<set _allyLabel to (($args[1] is undefined) ? _ally.toUpperFirst() : $args[1])>>
<<set _incomingDamage to $args[2]>>
<<set _target to $args[3]>>
<<set _negIncoming to _incomingDamage * -1>>
<<if $partyHp.player.current gt 0>>
<<set _shieldCost to Math.min($partyHp.player.current, _incomingDamage)>>
<<set _negShieldCost to _shieldCost * -1>>
<p><em>There's a half-second where Kaelen could put a body between <<= _allyLabel>> and what's coming.</em></p>
<<capture _negShieldCost, _target>>
<p><<link "Step between them">>
<<applyHp "player" _negShieldCost>>
<<if $combat>><<set $combat.shieldPending to false>><</if>>
<<goto _target>>
<</link>></p>
<</capture>>
<<capture _ally, _negIncoming, _target>>
<p><<link "Let it land">>
<<applyHp _ally _negIncoming>>
<<if $combat>><<set $combat.shieldPending to false>><</if>>
<<goto _target>>
<</link>></p>
<</capture>>
<<else>>
<<applyHp _ally _negIncoming>>
<<if $combat>><<set $combat.shieldPending to false>><</if>>
<<run Engine.play(_target)>>
<</if>>
<</nobr>>
<</widget>>
<<widget "resetPartyHp">>
<<nobr>>
<<for _k range Object.keys($partyHp)>>
<<capture _k>>
<<set $partyHp[_k].current to $partyHp[_k].maxHp>>
<</capture>>
<</for>>
<</nobr>>
<</widget>>
<<widget "startCombat">>
<<nobr>>
<<set _enemies to $args[0]>>
<<set _onVictory to $args[1]>>
<<set _onDefeat to $args[2]>>
<<set _mods to (($args[3] is undefined) ? {} : $args[3])>>
<<set _loopPassage to $args[4]>>
<<set _order to []>>
<<for _pk range Object.keys(setup.partyMembers)>>
<<if $partyHp[_pk].current gt 0>>
<<set _order.push({ side: "party", id: _pk, name: setup.partyMembers[_pk].label, speed: setup.partyMembers[_pk].speed })>>
<</if>>
<</for>>
<<for _e range _enemies>>
<<set _order.push({ side: "enemy", id: _e.id, name: _e.name, speed: _e.speed })>>
<</for>>
<<set _order to _order.slice(0).sort(function (a, b) { return b.speed - a.speed; })>>
<<set _orderNoSpeed to _order.map(function (o) { return { side: o.side, id: o.id, name: o.name }; })>>
<<set $combat to {
active: true,
round: 1,
turnIndex: 0,
resolvedThisIndex: false,
order: _orderNoSpeed,
enemies: _enemies.map(function (e) {
return { id: e.id, name: e.name, hp: e.hp, maxHp: e.hp, attack: e.attack, defense: (e.defense is undefined) ? 0 : e.defense, alive: true };
}),
log: [],
guardedBy: {},
shieldPending: false,
fled: false,
onVictory: _onVictory,
onDefeat: _onDefeat,
approachMods: {
firstStrike: (_mods.firstStrike is undefined) ? false : _mods.firstStrike,
thresholdBonus: (_mods.thresholdBonus is undefined) ? 0 : _mods.thresholdBonus,
skipEnemyRound: (_mods.skipEnemyRound is undefined) ? false : _mods.skipEnemyRound
}
}>>
<<set $session.inCombat to true>>
<<if $combat.approachMods.firstStrike is true>>
<<set _bonusDmg to Math.max(1, setup.partyMembers.player.attack)>>
<<set _firstEnemy to $combat.enemies.find(function (e) { return e.alive; })>>
<<if _firstEnemy>>
<<set _firstEnemy.hp to Math.max(0, _firstEnemy.hp - _bonusDmg)>>
<<set $combat.log.push("Kaelen gets the drop on " + _firstEnemy.name + " before it can react, landing an opening hit for " + _bonusDmg + ".")>>
<<if _firstEnemy.hp lte 0>>
<<set _firstEnemy.alive to false>>
<<set $combat.log.push(_firstEnemy.name + " goes down before the fight even properly starts.")>>
<</if>>
<</if>>
<</if>>
<<goto _loopPassage>>
<</nobr>>
<</widget>>
<<widget "combatStatusPanel">>
<<nobr>>
<div class="parchment-sheet combat-panel">
<h2>Round <<= $combat.round>></h2>
<div class="parchment-stats">
<<for _pk range Object.keys(setup.partyMembers)>>
<span><<= setup.partyMembers[_pk].label>></span><<= $partyHp[_pk].current>>/<<= $partyHp[_pk].maxHp>>
<</for>>
</div>
<div class="parchment-stats">
<<for _e range $combat.enemies>>
<<if _e.alive>>
<span><<= _e.name>></span><<= _e.hp>>/<<= _e.maxHp>>
<</if>>
<</for>>
</div>
</div>
<<for _logLine range $combat.log>>
<p><em><<= _logLine>></em></p>
<</for>>
<</nobr>>
<</widget>>
<<widget "combatActionMenu">>
<<nobr>>
<<set _loopPassage to $args[0]>>
<<set _aliveEnemies to $combat.enemies.filter(function (e) { return e.alive; })>>
<p><strong>Kaelen's turn.</strong></p>
<<if $combatPendingAction is "attack" and _aliveEnemies.length gt 1>>
<p><em>Choose a target:</em></p>
<<for _target range _aliveEnemies>>
<<set _targetLinkText to "[" + _target.name + " (" + _target.hp + " HP)]">>
<<capture _target, _loopPassage, _targetLinkText>>
<p><<link _targetLinkText>>
<<set $combatPendingTarget to _target.id>>
<<goto _loopPassage>>
<</link>></p>
<</capture>>
<</for>>
<<else>>
<<capture _loopPassage>>
<p><<link "[Attack]">>
<<set $combatPendingAction to "attack">>
<<goto _loopPassage>>
<</link>></p>
<</capture>>
<<capture _loopPassage>>
<p><<link "[Channel (Bless)]">>
<<set $combatPendingAction to "bless">>
<<goto _loopPassage>>
<</link>></p>
<</capture>>
<<capture _loopPassage>>
<p><<link "[Defend]">>
<<set $combatPendingAction to "defend">>
<<goto _loopPassage>>
<</link>></p>
<</capture>>
<<capture _loopPassage>>
<p><<link "[Flee]">>
<<set $combatPendingAction to "flee">>
<<goto _loopPassage>>
<</link>></p>
<</capture>>
<</if>>
<</nobr>>
<</widget>>
<<widget "resolvePlayerAction">>
<<nobr>>
<<set _aliveEnemyCount to ($combat ? $combat.enemies.filter(function (e) { return e.alive; }).length : 0)>>
<<set _awaitingTarget to ($combatPendingAction is "attack" and $combatPendingTarget is "" and _aliveEnemyCount gt 1)>>
<<if $combatPendingAction isnot "" and _awaitingTarget is not true>>
<<set _threshold to 11 - $combat.approachMods.thresholdBonus>>
<<set _roll to random(1, 20)>>
<<set _outcome to "fail">>
<<if _roll is 1>>
<<set _outcome to "crit-fail">>
<<elseif _roll is 20>>
<<set _outcome to "crit-success">>
<<elseif _roll gte _threshold>>
<<set _outcome to "success">>
<</if>>
<<switch $combatPendingAction>>
<<case "attack">>
<<set _target to $combat.enemies.find(function (e) { return e.id === $combatPendingTarget && e.alive; })>>
<<if !_target>>
<<set _target to $combat.enemies.find(function (e) { return e.alive; })>>
<</if>>
<<if _target>>
<<if _outcome is "crit-fail">>
<<set $combat.log.push("Kaelen's strike goes wide, and " + _target.name + " barely notices.")>>
<<else>>
<<set _dmg to setup.partyMembers.player.attack - _target.defense>>
<<if _outcome is "crit-success">>
<<set _dmg to _dmg * 2>>
<</if>>
<<set _dmg to Math.max(1, _dmg)>>
<<if _outcome is "fail">>
<<set _dmg to Math.max(1, Math.floor(_dmg / 2))>>
<</if>>
<<set _target.hp to Math.max(0, _target.hp - _dmg)>>
<<set $combat.log.push("Kaelen strikes " + _target.name + " for " + _dmg + (_outcome is "crit-success" ? " -- a solid, clean hit." : "."))>>
<<if _target.hp lte 0>>
<<set _target.alive to false>>
<<set $combat.log.push(_target.name + " goes down.")>>
<</if>>
<</if>>
<</if>>
<<case "bless">>
<<if _outcome is "crit-fail">>
<<set $combat.log.push("The blessing sputters out before it takes hold.")>>
<<else>>
<<set _healBase to (_outcome is "crit-success") ? 4 : (_outcome is "success") ? 2 : 1>>
<<set _lowest to Object.keys(setup.partyMembers).reduce(function (best, k) {
return (best === null || ($partyHp[k].current / $partyHp[k].maxHp) < ($partyHp[best].current / $partyHp[best].maxHp)) ? k : best;
}, null)>>
<<applyHp _lowest _healBase>>
<<set $combat.log.push("Kaelen calls on the Mother's light, and it settles on " + setup.partyMembers[_lowest].label + " for " + _healBase + " healed.")>>
<</if>>
<<case "defend">>
<<set $combat.guardedBy["player"] to "self">>
<<set $combat.log.push("Kaelen braces, ready to shrug off whatever comes next.")>>
<<case "flee">>
<<if _outcome is "success" or _outcome is "crit-success">>
<<set $combat.log.push("The party breaks off and gets clear.")>>
<<set $combat.fled to true>>
<<else>>
<<set $combat.log.push("There's no clean way out -- the fight isn't done with you yet.")>>
<</if>>
<</switch>>
<<set $combatPendingAction to "">>
<<set $combatPendingTarget to "">>
<<advanceTurn>>
<</if>>
<</nobr>>
<</widget>>
<<widget "resolvePartyAiTurn">>
<<nobr>>
<<set _who to $args[0]>>
<<set _aliveEnemies to $combat.enemies.filter(function (e) { return e.alive; })>>
<<if _aliveEnemies.length gt 0 and $partyHp[_who].current gt 0>>
<<set _target to _aliveEnemies.reduce(function (weakest, e) { return (e.hp < weakest.hp) ? e : weakest; }, _aliveEnemies[0])>>
<<set _dmg to Math.max(1, setup.partyMembers[_who].attack - _target.defense)>>
<<set _target.hp to Math.max(0, _target.hp - _dmg)>>
<<set $combat.log.push(setup.partyMembers[_who].label + " goes after " + _target.name + " for " + _dmg + ".")>>
<<if _target.hp lte 0>>
<<set _target.alive to false>>
<<set $combat.log.push(_target.name + " goes down.")>>
<</if>>
<</if>>
<</nobr>>
<</widget>>
<<widget "resolveEnemyTurn">>
<<nobr>>
<<set _enemyId to $args[0]>>
<<set _loopPassage to $args[1]>>
<<set _enemy to $combat.enemies.find(function (e) { return e.id === _enemyId; })>>
<<if _enemy and _enemy.alive and $combat.approachMods.skipEnemyRound is not true>>
<<set _aliveMembers to Object.keys(setup.partyMembers).filter(function (k) { return $partyHp[k].current gt 0; })>>
<<if _aliveMembers.length gt 0>>
<<set _victim to _aliveMembers[random(0, _aliveMembers.length - 1)]>>
<<set _dmg to Math.max(1, _enemy.attack - setup.partyMembers[_victim].defense)>>
<<if $combat.guardedBy["player"] is "self" and _victim is "player">>
<<set _dmg to Math.max(1, Math.floor(_dmg / 2))>>
<<set $combat.log.push(_enemy.name + " goes after Kaelen, who's braced for it -- only " + _dmg + " gets through.")>>
<<set _negDmg to _dmg * -1>>
<<applyHp _victim _negDmg>>
<<elseif _victim is not "player" and $partyHp.player.current gt 0>>
<<set $combat.shieldPending to true>>
<<kaelenShieldOffer _victim setup.partyMembers[_victim].label _dmg _loopPassage>>
<<else>>
<<set $combat.log.push(_enemy.name + " lands a hit on " + setup.partyMembers[_victim].label + " for " + _dmg + ".")>>
<<set _negDmg to _dmg * -1>>
<<applyHp _victim _negDmg>>
<</if>>
<</if>>
<<elseif $combat.approachMods.skipEnemyRound is true>>
<<set $combat.log.push(_enemy.name + " is still off-balance and loses the round.")>>
<</if>>
<</nobr>>
<</widget>>
<<widget "advanceTurn">>
<<nobr>>
<<set $combat.resolvedThisIndex to false>>
<<set $combat.turnIndex to $combat.turnIndex + 1>>
<<if $combat.turnIndex gte $combat.order.length>>
<<set $combat.turnIndex to 0>>
<<set $combat.round to $combat.round + 1>>
<<set $combat.guardedBy to {}>>
<</if>>
<<set _entry to $combat.order[$combat.turnIndex]>>
<<set _isDead to (_entry.side is "party") ? ($partyHp[_entry.id].current lte 0) : (!$combat.enemies.find(function (e) { return e.id === _entry.id && e.alive; }))>>
<<set _anyPartyAlive to Object.keys(setup.partyMembers).some(function (k) { return $partyHp[k].current gt 0; })>>
<<set _anyEnemyAlive to $combat.enemies.some(function (e) { return e.alive; })>>
<<if _isDead and _anyPartyAlive and _anyEnemyAlive>>
<<advanceTurn>>
<</if>>
<</nobr>>
<</widget>>
<<widget "checkCombatEnd">>
<<nobr>>
<<set _anyPartyAlive to Object.keys(setup.partyMembers).some(function (k) { return $partyHp[k].current gt 0; })>>
<<set _anyEnemyAlive to $combat.enemies.some(function (e) { return e.alive; })>>
<<if $combat.fled is true>>
<<set _dest to $combat.onVictory>>
<<endCombat>>
<<goto _dest>>
<<elseif !_anyEnemyAlive>>
<<set _dest to $combat.onVictory>>
<<endCombat>>
<<goto _dest>>
<<elseif !_anyPartyAlive>>
<<set _dest to $combat.onDefeat>>
<<endCombat>>
<<goto _dest>>
<</if>>
<</nobr>>
<</widget>>
<<widget "endCombat">>
<<nobr>>
<<set $session.inCombat to false>>
<<set $combat to null>>
<<set $combatPendingAction to "">>
<<set $combatPendingTarget to "">>
<</nobr>>
<</widget>>
<<widget "resolveCombatUntilPlayerTurn">>
<<nobr>>
<<set _loopPassage to $args[0]>>
<<if $combat>>
<<set _entry to $combat.order[$combat.turnIndex]>>
<<if _entry.side is "enemy">>
<<set $combat.resolvedThisIndex to true>>
<<resolveEnemyTurn _entry.id _loopPassage>>
<<checkCombatEnd>>
<<if $combat and $combat.shieldPending is not true>>
<<advanceTurn>>
<<checkCombatEnd>>
<<if $combat>>
<<resolveCombatUntilPlayerTurn _loopPassage>>
<</if>>
<</if>>
<<elseif _entry.side is "party" and _entry.id is not "player">>
<<set $combat.resolvedThisIndex to true>>
<<resolvePartyAiTurn _entry.id>>
<<checkCombatEnd>>
<<if $combat>>
<<advanceTurn>>
<<checkCombatEnd>>
<<if $combat>>
<<resolveCombatUntilPlayerTurn _loopPassage>>
<</if>>
<</if>>
<</if>>
<</if>>
<</nobr>>
<</widget>>
<<widget "Diary_E1">>
<p><strong>Episode 1 — The Shrine</strong><br>
<<switch $e1GuardianApproach>>
<<case "liturgy">>
The old shrine's guardian answered Kaelen's liturgy with words nobody had written down for it, a call-and-response Jordan swears he never scripted. It knelt anyway, and the party walked out with the Sigil of the Mother's Favor and more questions than they'd walked in with.
<<case "force">>
The guardian never so much as raised a hand, even braced for a fight. It just watched Kaelen, patient, until the standoff resolved itself and it folded down on its own terms. The party left with the Sigil of the Mother's Favor, and the distinct sense the whole thing had never really been about winning.
<<case "touch">>
Kaelen touched the guardian instead of fighting or praying, and for half a second the feeling of being watched seemed to come from somewhere else entirely, gone before anyone could name it. The construct went still under his palm and let them pass. They left with the Sigil of the Mother's Favor.
<<default>>
The party found an old shrine to the Mother Goddess, talked its guardian down, and walked out with the Sigil of the Mother's Favor.
<</switch>>
That evening, Jordan mentioned the dice set was a recent estate-sale pickup, and that the Mother Goddess angle for the shrine had "shown up out of nowhere" while he was writing it, like it wrote itself.</p>
<</widget>>
<<widget "Diary_E2">>
<p><strong>Episode 2 — The Swamp</strong><br>
<<switch $e2LurkerApproach>>
<<case "fight">>
The lurkers in the swamp came out fighting, and the party fought back. It got messier than anyone wanted, everyone soaked and bruised by the time the last one gave up the water.
<<case "flank">>
The party slipped past the lurkers instead of fighting them outright, threading through water deep enough to swallow the sound of movement. It was closer than it should have been, but it worked.
<<case "faith">>
Kaelen held out the sigil instead of a weapon, and the lurkers actually flinched from the light, enough to buy the party room to get past without a full fight breaking out.
<<default>>
The party made it through the swamp's lurkers and reached the stranded caravan.
<</switch>>
Sam found a cracked pocket watch on a dead merchant's ledger and, for reasons he never quite explained, left it exactly where it was. Whatever happened to the wounded caravan guard, it cost something: a prayer, or more than a prayer.</p>
<</widget>>
<<widget "Diary_E3">>
<p><strong>Episode 3 — The Two Houses</strong><br>
<<switch $e3InvestigationApproach>>
<<case "voss_first">>
The party went to House Voss first, and Lady Voss made it clear she considered the accusation against her house an insult before it was even spoken.
<<case "thale_first">>
The party sat with House Thale first, while Lord Thale's grief was still raw enough that nothing about the meeting felt rehearsed.
<<case "investigate">>
The party skipped both houses and went straight to the docks where Thale's heir was found, before either side could tell them what they were supposed to see there.
<<default>>
The party navigated the standoff between House Voss and House Thale over a dead heir nobody could explain.
<</switch>>
At the peace talk, Kaelen invoked a line of Mother's doctrine that Jordan swears he never wrote down, "mending what's broken instead of breaking it further," and Max was the only one at the table willing to say out loud that something about that didn't add up.</p>
<</widget>>
<<widget "Diary_E4">>
<p><strong>Episode 4 — The Frostbound Keep</strong><br>
The party fought their way through a keep sealed in unnatural cold and put down the Frostbound Lord holding it. At the exact moment he fell, the basement they were playing in actually went cold, sharp enough that everyone's breath fogged for real, right there at the table. Nobody's explained it since.</p>
<</widget>>
<<widget "Diary_E5">>
<p><strong>Episode 5 — The Market</strong><br>
A breather week: a market and a fairground, coin spent on gear instead of blood. Whatever Kaelen walked away wearing or carrying from the bazaar and the fair stalls, it all somehow looked like it belonged together by the time the lanterns came on.</p>
<</widget>>
<<widget "Diary_E6">>
<p><strong>Episode 6 — The Party</strong><br>
No dungeon this week, just Riley's party, loud enough that the frost giant waiting in next week's session felt very far away for one night.</p>
<</widget>>
<<widget "Diary_E7">>
<p><strong>Episode 7 — The Pass</strong><br>
<<if $e7CollarWeakPoint is true>>
Kaelen spotted a weak point in the frost giant's collar before the fight even started, and it mattered: the killing blow came easier for having somewhere exact to aim.
<<else>>
The party went straight at the collared frost giant blocking the pass, no clever opening found first, and won anyway.
<</if>>
Past the giant, at the threshold of something older and colder than the mountain itself, the party glimpsed the Hollow King for the first time, and it looked back, specifically at Kaelen, like it already recognized him from somewhere he hadn't been yet.</p>
<</widget>>
<<widget "Diary_E8">>
<p><strong>Episode 8 — The Waystation</strong><br>
A quieter week at a waystation before the mountains: restocking, a tavern's worth of local rumor about the collar not being local craft, and a shepherd's ward token pressed into Kaelen's hand by a grateful refugee. Nobody wanted to talk about the frosted shrine on the ridge road.</p>
<</widget>>
<<widget "Diary_E9">>
<p><strong>Episode 9 — Ashwyn</strong><br>
Adalric's contact turned out to be Ashwyn, guarded, dry, and unnervingly aware of exactly who Kaelen was before anyone introduced him. Whether that makes them useful or the opposite is still an open question nobody at the table has settled.</p>
<</widget>>
<<widget "Diary_E10">>
<p><strong>Episode 10 — The Frost Line</strong><br>
Ashwyn came back, exactly like they said they would, and led the party across the frost line into Hollow King territory.
<<if $sessionItemsHeld.includes("ashwyn_pass_wrap")>>
Kaelen took the cold-weather wrap they offered, a small, deliberate trust extended in both directions.
<</if>>
Whatever's waiting past that line noticed them crossing. Jordan swears that's not the end of it.</p>
<</widget>>
<<widget "Diary_E11">>
<p><strong>Episode 11 — The Salt Road</strong><br>
The party escorted a pilgrim caravan through Hollow-touched borderland and found a whole settlement wearing the same frost-pattern collar the giant wore back at the pass -- except none of them think of it as a collar at all. Kaelen picked up a robe from a grateful pilgrim along the way.
Jordan mentioned, almost in passing, that the settlement's names came from a list he doesn't remember writing -- it was just already there in his notebook when he needed it.</p>
<</widget>>
<<widget "Diary_E12">>
<p><strong>Episode 12 — The Quiet Court</strong><br>
A third house, one that apparently knows both Adalric and Ashwyn "a long time," drew Kaelen into a public rite as the Mother's representative in front of the whole court -- the most visible the goddess's mark on him has ever been in the fiction.
Max kept a running count, out loud, of how often session nights land on real dates that shouldn't line up. Sam finally asked him to stop counting -- the first real crack in the group's usual agreement to let it go.</p>
<</widget>>
<<widget "Diary_E13">>
<p><strong>Episode 13 — The Ledger of Names</strong><br>
No fight this week -- just an archive, old records of past "Hollow audiences," and an entry that matched Kaelen's own path down to the date and the gear, filed under somebody else's name entirely.
Nobody at the table said anything about it out loud. Jordan just read it slower than everything else that night.</p>
<</widget>>
<<widget "Diary_E14">>
<p><strong>Episode 14 — The Reflection Ward</strong><br>
A warded chamber that wanted Kaelen to pick out his own reflection from an identical double before it would let the party through -- an illusion built to not have an obviously correct answer.
Ashwyn said something about having stood on the other side of a room like that once, then asked everyone to forget she'd said it. Nobody's brought it up since, out loud.</p>
<</widget>>
<<widget "Diary_E15">>
<p><strong>Episode 15 — The Hollow Court</strong><br>
The Hollow King finally talked, at length, for the first time -- and knew something about Kaelen that nobody at the table had ever said out loud, in or out of character.
For a few seconds afterward, nobody reached for their dice, their notes, or a joke to cut the silence. Not even Sam.</p>
<</widget>><<set $currentDayName to "Sunday">>
<<img "bedroom">>
<<if $sundayOffered.length is 0>>
<<sundayOfferScenes>>
<</if>>
Sunday. No shift, no classes, nothing on the schedule except whatever you decide to make of it.
<p><strong>Family</strong></p>
<<sundayPicker setup.sundayFamilyScenes>>
<p><strong>Your own time</strong></p>
<<sundayPicker setup.sundayHobbyScenes>>
<p><strong>Errands</strong></p>
<<if not $sundaySeen.includes("sunday_mall")>>
<p><<link "Head to the mall">>
<<set $sundaySeen.push("sunday_mall")>>
<<set $mallReturnOverride to "Sunday">>
<<goto "Mall">>
<</link>></p>
<<else>>
<p><s>Head to the mall</s> <em>(done)</em></p>
<</if>>
<<link "Call it a day">>
<<set $sundaySeen to []>>
<<set $sundayOffered to []>>
<<set $dayNumber to $dayNumber + 1>>
<<set $currentDayName to "Monday">>
<<set $randomEventFired to false>>
<<goto "MorningHub">>
<</link>><<img "family_outing">>
Diane suggests getting out of the house for a while, and somehow the whole family actually agrees on the same afternoon. You end up walking the same park loop you've done a hundred times, and it's better than it has any right to be.
Ryan complains the entire way and comes anyway. Mark points out the same tree he points out every time. Nothing about it is remarkable, and that's sort of the point.
[[Back->Sunday]]<<img "kitchen">>
You end up elbow-deep in flour next to Diane, who has strong and frequently contradictory opinions about how much garlic is too much garlic. Ryan hovers near the stove mostly to steal bites before anything's ready.
It's chaotic and a little too loud and you don't mind any of it.
[[Back->Sunday]]<<img "living_room_movie">>
Somebody finds something everyone can agree to half-watch, and the afternoon dissolves into commentary that has nothing to do with the actual movie. Mark falls asleep twenty minutes in. Nobody wakes him up.
[[Back->Sunday]]<<img "living_room_movie">>
Somebody drags out an old board game with half its pieces missing, and the rules get reinvented on the spot to compensate. Ryan cheats openly and denies it just as openly. Diane wins anyway, somehow, and refuses to explain her strategy.
It runs long past when anyone meant to stop, mostly because nobody wants to be the one who ends it.
[[Back->Sunday]]<<img "family_outing">>
Mark's got a list of yard tasks that's been growing for weeks, and today's the day it finally gets tackled, mostly. You end up raking while Ryan "supervises" from a lawn chair until Diane puts him to work too.
Nobody finishes the whole list. Everyone agrees it counts as progress anyway, and the four of you sit on the porch afterward doing absolutely nothing, which might be the actual point.
[[Back->Sunday]]<<img "kitchen">>
For once nobody's rushing anywhere, so breakfast turns into an actual production: Diane on pancakes, Mark burning the bacon slightly and insisting that's how he likes it, Ryan mostly just eating whatever's ready first.
You end up at the table longer than any of you planned, plates empty, nobody quite ready to be the one who gets up first.
[[Back->Sunday]]<<img "family_outing">>
Nobody has anywhere to be, so the drive doesn't really have a destination, just backroads Mark insists are "faster," even though they clearly aren't. Ryan complains about the music choice from the back seat. Diane hums along to it anyway.
You end up somewhere none of you planned, get ice cream because you're already there, and drive home with the windows down for no particular reason.
[[Back->Sunday]]<<img "art_desk">>
You lose track of time with a sketchbook, filling pages with nothing in particular, half-remembered scenes from the campaign mostly, before you notice what you've actually been drawing.
<<switch $bodyFeatures.face>>
<<case 0>>
The figures on the page look about like you'd expect.
<<case 1>>
More than one of the figures ends up looking a little different than you meant them to. You don't erase it.
<<case 2 3>>
You stop pretending it's an accident somewhere around the fourth face: same jaw, same eyes, all of them quietly closer to how you actually look now than to anything you consciously decided to draw. You keep going anyway, slower now, paying more attention than you meant to.
<<default>>
You're not sketching a character anymore, not really. You're drawing yourself, over and over, in poses and expressions you don't let yourself think too hard about while your hand's still moving. It's the most honest thing you've drawn in weeks, and that's what makes you close the sketchbook a little too fast when you're done.
<</switch>>
[[Back->Sunday]]<<img "living_room_movie">>
The laptop's out, and a few hours vanish into a game you've been meaning to get back to. It's a good kind of nothing: no responsibilities, no dice with an agenda, just a controller and a couch.
[[Back->Sunday]]<<img "reading_nook">>
You curl up with a book you've been meaning to finish for weeks and actually finish it, which feels like a genuine accomplishment given the state of your nightstand.
[[Back->Sunday]]<<img "jogging_path">>
You get out early before the day can talk you out of it. The route's familiar enough not to think about, which leaves plenty of room to think about everything else instead.
<<set _maxStage to Math.max.apply(null, ["chest", "waist", "rear"].map(function (f) { return $bodyFeatures[f]; }))>>
<<if _maxStage gte 1 and _maxStage lte 2>>
Somewhere around the second mile you notice your stride's changed, weight landing differently than it used to, nothing you'd call bad, just different: a rhythm you're still catching up to.
<<elseif _maxStage gte 3>>
By the time you're back, breathing hard, shirt clinging, you're more aware of your own body than the run usually leaves you: every place it moves differently now, the pull and give of it, a warmth that isn't just from the exertion. You stand in the driveway a minute longer than you need to before going in, catching your breath and, if you're honest, enjoying the way it feels to be this aware of yourself.
<</if>>
[[Back->Sunday]]Thursday. Almost through the week. You catch yourself doodling the Mother Goddess sigil from the shrine in the corner of a notebook page, the wheat, the tree, without really deciding to.
<<switch $job.type>>
<<case "cafe">>
<<img "thu_cafe">>
Slow shift, mostly restocking and wiping down machines. Plenty of time to overthink things.
<<link "Use the downtime to study">>
<<goto "ThursdayEvent">>
<</link>>
<<link "Just zone out">>
<<goto "ThursdayEvent">>
<</link>>
<<case "fastfood">>
<<img "thu_fastfood">>
A kid throws a full milkshake on the floor directly in front of the register. You get to clean it up personally.
<<link "Handle it with a smile">>
<<goto "ThursdayEvent">>
<</link>>
<<link "Barely mask your annoyance">>
<<goto "ThursdayEvent">>
<</link>>
<<default>>
<<img "thu_babysitting">>
Just one kid tonight; the parents are out late. Low-key evening: dinner, a movie, lights out on schedule.
<<link "Let them pick the movie">>
<<goto "ThursdayEvent">>
<</link>>
<<link "Pick something yourself">>
<<goto "ThursdayEvent">>
<</link>>
<</switch>><<switch $job.type>>
<<case "cafe">>
<<img "thu_event_cafe">>
You actually get some reading done between customers. Productive, for once.
<<case "fastfood">>
<<img "thu_event_fastfood">>
Smile it is. The kid's mom apologizes about six times, which almost makes up for the mop bucket.
<<default>>
<<img "thu_event_babysitting">>
The movie pick turns out to be a surprisingly good one. You end up watching more of it than you meant to.
<</switch>>
<<shiftPay>>
<<link "Head home">>
<<set $eveningReturnTarget to "Friday">>
<<goto "EveningDinner">>
<</link>>By the time your last class lets out, you've almost forgotten you have a shift today. In the bathroom mirror this morning, you noticed your hair's gotten just long enough to tuck behind your ears without trying. You made a mental note to get it cut and immediately forgot about it.
<<switch $job.type>>
<<case "cafe">>
<<img "tue_cafe">>
Another shift at the coffee shop. Your manager compliments your latte art, which is generous, since it's mostly just a blob with a vague leaf shape.
<<link "Practice on a slow order">>
<<goto "TuesdayEvent">>
<</link>>
<<link "Just get through the shift">>
<<goto "TuesdayEvent">>
<</link>>
<<case "fastfood">>
<<img "tue_fastfood">>
Same fryer, same headset, same manager. Today it's inventory day, which means counting frozen patties for forty-five minutes.
<<link "Volunteer for the walk-in freezer">>
<<goto "TuesdayEvent">>
<</link>>
<<link "Stay on the front counter">>
<<goto "TuesdayEvent">>
<</link>>
<<default>>
<<img "tue_babysitting">>
Back at the Hendersons'. Today it's the older kid's turn to be difficult about homework, and you end up doing more negotiating than actual babysitting.
<<link "Bribe them with screen time">>
<<goto "TuesdayEvent">>
<</link>>
<<link "Hold the line, no negotiating">>
<<goto "TuesdayEvent">>
<</link>>
<</switch>><<switch $job.type>>
<<case "cafe">>
<<img "tue_event_cafe">>
The leaf-shaped blob turns into something almost recognizable by the end of the shift. A customer actually says "cute" about it, which you'll take.
<<case "fastfood">>
<<img "tue_event_fastfood">>
The walk-in freezer is exactly as miserable as it sounds, but at least nobody yells at you in there.
<<default>>
<<img "tue_event_babysitting">>
The homework standoff ends in a draft treaty involving fifteen more minutes of screen time. Diplomacy: successful.
<</switch>>
<<shiftPay>>
<<link "Head home">>
<<set $eveningReturnTarget to "Wednesday">>
<<goto "EveningDinner">>
<</link>>Sunlight cuts through a gap in the curtains, landing right across your face. The alarm hasn't even gone off yet, but you're already half awake, staring at the ceiling of your room.
Posters, a shelf sagging under paperbacks and old rulebooks, a desk buried under dice trays and character sheets. Tonight is game night. That thought alone is enough to get you moving.
<<img "bedroom">>
<<link "Check your phone before getting up">>
<<goto "WakeUpPhone">>
<</link>>
<<link "Just lie there another minute">>
<<goto "WakeUpLieIn">>
<</link>><<img "ceiling_view">>
You let yourself have one more minute, staring at the ceiling, running through the shrine map Jordan teased last week in your head. No plan survives contact with the dice, but it's fun to guess anyway.
Eventually you sit up, rub the sleep out of your eyes, and swing your legs out of bed.
[[Get moving->NameChoice]]<<img "phone_nightstand">>
You grab your phone off the nightstand. A few notifications, nothing important, except a text from Jordan, sent at some unreasonable hour: "dice are ready. tonight's gonna be good."
You toss the phone onto the blanket and finally sit up, rubbing the sleep out of your eyes.
[[Swing your legs out of bed->NameChoice]]Outside, the morning's already warming up.
<<link "Walk to campus, same as always">>
<<goto "WalkToSchoolOnFoot">>
<</link>>
<<link "Take the bike instead">>
<<goto "WalkToSchoolBike">>
<</link>><<img "bike_ride">>
You take the bike, cutting the trip down to half the time, enough for a slightly smug lap of the block before locking it up out front.
You can already see the front steps of the building, and a familiar cluster of backpacks near the entrance.
[[Head over->SchoolArrival]]<<img "sidewalk_walk">>
The walk to campus is the same as always: same cracked sidewalk, same shortcut through the Petersons' yard when nobody's looking.
You can already see the front steps of the building, and a familiar cluster of backpacks near the entrance.
[[Head over->SchoolArrival]]You pull open the closet and take stock of what's clean.
<<wardrobeSlot "underwear">>
<<wardrobeSlot "top">>
<<wardrobeSlot "bottom">>
<<wardrobeSlot "shoes">>
<<wardrobeSlot "neck">>
<<wardrobeSlot "hands">>
<<wardrobeSlot "head">>
<<if $outfit.underwear neq "" and $outfit.top neq "" and $outfit.bottom neq "" and $outfit.shoes neq "">>
[[Get dressed and head to the bathroom->MorningRoutine]]
<<else>>
<p><em>(Pick something for every slot before heading out.)</em></p>
<</if>>Wednesday. Sam texts a meme about the natural 1 argument that still hasn't died. You laugh, and catch yourself sounding a little different doing it, lighter somehow. Probably just a cold coming on.
<<switch $job.type>>
<<case "cafe">>
<<img "wed_cafe">>
Midweek rush. A regular asks if you're new, even though you've been there three weeks, says something about you "seeming different lately." You shrug it off.
<<link "Ask what he means">>
<<goto "WednesdayEvent">>
<</link>>
<<link "Change the subject">>
<<goto "WednesdayEvent">>
<</link>>
<<case "fastfood">>
<<img "wed_fastfood">>
Midweek and the manager's in a mood, riding everyone about speed. You keep your head down and move fast.
<<link "Pick up the pace">>
<<goto "WednesdayEvent">>
<</link>>
<<link "Work at your own pace anyway">>
<<goto "WednesdayEvent">>
<</link>>
<<default>>
<<img "wed_babysitting">>
The Hendersons' dog decides you're its new favorite person tonight, which makes homework supervision considerably harder.
<<link "Let the dog stay in the room">>
<<goto "WednesdayEvent">>
<</link>>
<<link "Put the dog outside">>
<<goto "WednesdayEvent">>
<</link>>
<</switch>><<switch $job.type>>
<<case "cafe">>
<<img "wed_event_cafe">>
"Just tired," you tell him, and he lets it go. You catch your reflection in the espresso machine's chrome panel afterward and can't quite place what he meant either.
<<case "fastfood">>
<<img "wed_event_fastfood">>
You pick up the pace and the manager actually nods approvingly for once. Small miracles.
<<default>>
<<img "wed_event_babysitting">>
The dog falls asleep on the couch approximately four minutes after being let in, thoroughly betraying the premise of "harder."
<</switch>>
<<shiftPay>>
<<link "Head home">>
<<set $eveningReturnTarget to "Thursday">>
<<goto "EveningDinner">>
<</link>>Saturday rolls around, paycheck-heavier and homework-lighter. By evening you're back at Jordan's, backpack over one shoulder, already smelling the reheated pizza before you're through the door.
<<img "basement">>
"There they are," Jordan says, waving you in. Sam's already at the table, flipping through his character sheet like it owes him money. Max is arranging dice by color, which is somehow both unnecessary and completely in character.
"Ready for round two?" Jordan asks, grinning in a way that suggests he's been looking forward to this all week.
<<link "Sit down and get ready">>
<<set $characterSheetReturn to "E2_TableTalk">>
<<goto "CharacterSheet">>
<</link>><<img "amulet_closeup">>
The amulet is heavier than it looks, warm despite the cold stone it rested on. The instant Kaelen's fingers close around it, the chamber seems to exhale. Dust sifts down from the ceiling, and the faint smell of turned earth and crushed wheat follows it.
"Put it on," Jordan says, "and I'll tell you what it does."
Kaelen doesn't hesitate. The chain settles cold against the back of the neck, then warms almost immediately, like it's settling into place rather than just resting there.
"It's a Sigil of the Mother's Favor," Jordan reads from his notes. "While worn, your healing spells are stronger, but the goddess's blessing isn't subtle. It changes you a little, the longer you carry it."
<<set $bodyFeatures.face to $bodyFeatures.face + 1>>
<<recalcTransformation>>
<<set $sessionItemsHeld.push("sigil_mothers_favor")>>
"Changes you how?" Kaelen asks. Alex asks, really, unsure for a second which one of them said it.
Jordan just smiles. "You'll see."
[[Head back to the entrance->E1_Return]]<<nobr>>
"Before you head for that doorway," Jordan says, "you notice there's more to look at in here, if you want to."
<<img "outer_chamber">>
Max is already poking at something in the corner. Sam's found a niche in the wall. There's a second pillar, further back, with different carvings than the rest.
<<if not $e1SearchSeen.includes("offerings")>>
<p><<link "Check what Max found">>
<<goto "E1_Search_Offerings">>
<</link>></p>
<<else>>
<p><s>Check what Max found</s></p>
<</if>>
<<if not $e1SearchSeen.includes("niche")>>
<p><<link "Look at Sam's niche">>
<<goto "E1_Search_Niche">>
<</link>></p>
<<else>>
<p><s>Look at Sam's niche</s></p>
<</if>>
<<if not $e1SearchSeen.includes("pillar")>>
<p><<link "Read the second pillar">>
<<goto "E1_Search_Pillar">>
<</link>></p>
<<else>>
<p><s>Read the second pillar</s></p>
<</if>>
<p><<link "Head for the doorway">>
<<goto "E1_Ward_Puzzle">>
<</link>></p>
<</nobr>><<if $combat is null>>
<<set _fallbackEnemies to [{ id: "guardian", name: "the guardian", hp: 5, attack: 2, defense: 1, speed: 2 }]>>
<<startCombat _fallbackEnemies "E1_Inner_Sanctum" "E1_Guardian_Defeat" {} "E1_Guardian_Combat">>
<<else>>
<<resolvePlayerAction>>
<<checkCombatEnd>>
<<if $combat and $combat.shieldPending is not true>>
<<resolveCombatUntilPlayerTurn "E1_Guardian_Combat">>
<</if>>
<<if $combat and $combat.shieldPending is not true>>
<<combatStatusPanel>>
<<combatActionMenu "E1_Guardian_Combat">>
<</if>>
<</if>>It's closer than anyone at the table wants to admit. The guardian gets the better of the exchange, root-fingers hauling hard enough that the whole party ends up scattered and breathing hard against the shrine wall. But whatever's driving it seems to lose interest the moment nobody's standing to meet its gaze anymore, and the pull behind Kaelen's ribs eases off just enough to let them all stagger upright again.
"Rough one," Jordan says, "but it's not pressing the advantage. You get clear enough to catch your breath."
Sam winces, rolling a shoulder. "Never doing this again without a healer who actually heals."
[[Catch your breath and go on->E1_Inner_Sanctum]]<<if $e1WardApproach is "disarm">>
The shard ward doesn't take well to being tampered with. Sam's fingers are still an inch from it when the whole ring rattles upright on its own, faster and angrier than a simple crossing would've triggered.
"Initiative roll," Jordan says, a little sharper than usual. "The shrine's guardian wakes up. And it noticed that."
<<elseif $e1WardApproach is "instinct">>
Kaelen crosses the ward without really thinking about it, feet finding the safe gaps like the pattern was always going to be obvious, and the shards barely stir, a slow rattle instead of a violent one.
"Initiative roll," Jordan says, "but, huh. It's waking up calm. That's new."
<<else>>
The glinting object turns out to be a shard of polished bone, arranged with several others into a crude ward. The moment Kaelen's boot crosses the line, the shards rattle upright.
"Initiative roll," Jordan says, not looking up from the notes. "The shrine's guardian wakes up."
<</if>>
What rises out of the shadows isn't quite a skeleton and isn't quite a statue: a construct of bone and root, vaguely feminine in shape, its chest hollow where a heart should be.
"It's not attacking," Jordan clarifies as the party braces. "It's just... watching Kaelen. Specifically."
Sam mutters, "Of course it is."
<<img "root_guardian">>
The construct doesn't move toward anyone else. Its hollow chest tilts, slow and deliberate, until it's facing Kaelen dead-on, and stays there, waiting.
"Okay," Jordan says, setting down his pen. "What does Kaelen do?"
<<link "Speak the liturgy">>
<<goto "E1_Path_Liturgy">>
<</link>>
<<link "Brace for a fight">>
<<goto "E1_Path_Force">>
<</link>>
<<link "Reach out and touch it">>
<<goto "E1_Path_Touch">>
<</link>><<if $e1GuardianApproach is "liturgy">>
The last echo of the guardian's answering rasp fades as it finally kneels, like a conversation reaching its natural end.
"Okay, that's a little unsettling," Jordan admits, breaking character for a second. "That response line wasn't supposed to be in my notes at all."
<<elseif $e1GuardianApproach is "force">>
The guardian never did raise a hand against anyone. It just watches Kaelen a moment longer, then folds down anyway, patient to the last. Root-fingers laced together, utterly still, like it had been waiting a long time for exactly this outcome.
"Nice," Max says. "How'd you know it'd just stop?"
Kaelen doesn't have a good answer.
<<elseif $e1GuardianApproach is "touch">>
Under Kaelen's palm, the guardian's stillness deepens into something else entirely: not defeat, more like permission finally given.
"Huh," Jordan says again, and still doesn't elaborate.
<</if>>
The doorway beyond the guardian opens into the inner sanctum: a vaulted chamber lit by no visible source, dominated by a stone altar carved in the shape of open, upturned hands. Resting in the palms is a single object, a heavy amulet on a tarnished chain, its face engraved with the same wheat-and-tree sigil from the entrance.
<<img "altar">>
Jordan's voice softens, just slightly. "It's clearly meant for a cleric. You'd know that too."
[[Approach the altar->E1_Amulet]]<<rollBanner>>
<<img "outer_chamber">>
"You know exactly what this place is," Jordan says. "A shrine to the Mother Goddess, old god of harvest, childbirth, and healing. Not actively worshipped anymore, but this looks maintained. Recently."
Kaelen runs a hand along the nearest pillar. The carvings depict robed figures kneeling before a great tree, hands upturned, receiving something from its branches.
"Kaelen would know the old prayers," Kaelen says, half to the table, half in character. "The order taught them to every new cleric before they were even ordained."
Sam raises an eyebrow. "Since when do you know all this lore?"
"Since I picked a cleric two minutes ago," Kaelen says, and everyone laughs.
"Character building," Jordan says, not looking up from his notes. "Kaelen's player is discovering feelings about a goddess in real time. Very method."
Max leans back in his chair. "Is there treasure in here or are we just going to keep admiring the wallpaper?"
"There's a second chamber," Jordan says. "Give me a second, I want to get this description right." He flips a page, and for a moment nobody says anything. It's the kind of pause that's normal for a DM checking notes and would be nothing at all, any other week.
Deeper in the chamber, a second doorway stands open, leading further into the dark.
[[Move toward the inner doorway->E1_Chamber_Search]]<<set $e1GuardianApproach to "force">>
<<img "guardian_force_standoff">>
Kaelen's hand goes to a weapon instead, and the rest of the party braces with him, but the guardian doesn't so much as flinch toward any of it. It just keeps watching, hollow-chested and patient, like the whole idea of being attacked hasn't occurred to it as a possibility worth reacting to.
"It's not defending itself," Jordan says. "It's still just looking at Kaelen."
"That's worse," Max mutters. "I'd almost rather it hit us."
Sam's grip tightens on an imaginary weapon of his own. "Kaelen, maybe don't be the one it's staring at."
Too late for that. Kaelen holds the line anyway, weapon raised, waiting to see if patience runs out first.
<<set _e1ForceEnemies to [{ id: "guardian", name: "the guardian", hp: 5, attack: 2, defense: 1, speed: 2 }]>>
<<set _e1ForceMods to { firstStrike: false, thresholdBonus: -2, skipEnemyRound: false }>>
<<startCombat _e1ForceEnemies "E1_Inner_Sanctum" "E1_Guardian_Defeat" _e1ForceMods "E1_Guardian_Combat">><<set $e1GuardianApproach to "liturgy">>
<<img "guardian_liturgy_response">>
Kaelen doesn't reach for a weapon. Instead the first line of the old liturgy comes out, deliberate this time, not stumbled into. Words that shouldn't be there, arriving fully formed like they'd always been.
The guardian's hollow chest seems to lean toward the sound. For a moment it's almost a call-and-response: a dry, root-and-bone rasp answering back in the same cadence, half a beat behind, like something remembering how to speak.
"That's not in my notes," Jordan says, quieter than usual. "I didn't write a response line."
Sam looks between the two of them. "So who wrote it?"
Nobody has an answer. Kaelen keeps speaking anyway, letting the words carry the moment forward.
<<set _e1LiturgyEnemies to [{ id: "guardian", name: "the guardian", hp: 5, attack: 2, defense: 1, speed: 2 }]>>
<<set _e1LiturgyMods to { firstStrike: true, thresholdBonus: 2, skipEnemyRound: false }>>
<<startCombat _e1LiturgyEnemies "E1_Inner_Sanctum" "E1_Guardian_Defeat" _e1LiturgyMods "E1_Guardian_Combat">><<set $e1GuardianApproach to "touch">>
<<img "guardian_touch_moment">>
Kaelen doesn't speak and doesn't reach for a weapon, just crosses the space between them and rests a hand flat against the guardian's root-woven chest, the way you'd calm something afraid rather than fight something dangerous.
For half a second the watching feeling doesn't seem to be coming from the construct at all. It's gone before Kaelen can decide it was ever there, and nobody at the table notices anything strange enough to mention.
The guardian goes very still under Kaelen's palm, root-fingers curling loosely inward, like it's the first touch it's had in longer than anyone at this table could guess.
"Huh," Jordan says, and doesn't elaborate.
<<set _e1TouchEnemies to [{ id: "guardian", name: "the guardian", hp: 5, attack: 2, defense: 1, speed: 2 }]>>
<<set _e1TouchMods to { firstStrike: false, thresholdBonus: 0, skipEnemyRound: false }>>
<<startCombat _e1TouchEnemies "E1_Inner_Sanctum" "E1_Guardian_Defeat" _e1TouchMods "E1_Guardian_Combat">><<resetPartyHp>>
<<img "shrine_exit">>
The walk back out of the shrine is quiet. The kneeling guardian doesn't stir as the party passes. Outside, the afternoon light feels too bright after so long in the dark.
"Good stopping point," Jordan says, closing his notebook. "Same time next week?"
[[Head back to the table->E1_Mystery_Beat]]<<set $e1SearchSeen.push("niche")>>
<<img "wall_niche_bones">>
Sam's found a shallow niche cut into the wall, empty except for a scatter of small bones arranged in a pattern too deliberate to be random. It's the same pattern, Kaelen realizes, that shows up again a few feet further in, just inside the next doorway.
"Somebody marks their wards the same way twice," Sam says. "Good to know before we walk into the second one blind."
Jordan taps his pen against his notes, unreadable. "Filed away for later, noted."
<<set $e1WardKnowledge to true>>
[[Back->E1_Chamber_Search]]<<set $e1SearchSeen.push("offerings")>>
<<img "shrine_offerings">>
Max is crouched over a shallow stone bowl set into the floor, half-buried in dust. Old offerings, gone to powder centuries ago, except for one thing that hasn't decayed at all: a fistful of dried wheat, tied with a cord that still looks freshly knotted.
"That's not old," Max says, turning it over in his hands. "That's, like, this-season old."
Jordan makes a note without saying what it's for. "Kaelen would recognize it as a binding offering, the kind you'd leave for a guardian construct, to keep it calm. Somebody's been maintaining this shrine. Recently."
Sam eyes the doorway ahead. "So whatever's back there might already be fed."
<<set $e1WardKnowledge to true>>
[[Back->E1_Chamber_Search]]<<set $e1SearchSeen.push("pillar")>>
<<img "carved_pillar">>
The second pillar's carvings don't match the rest of the chamber. Cruder, like they were added later by someone without the original mason's skill. A robed figure, hand upraised, and beneath it a line of script too worn to make out except for one word, repeated three times down the column.
"Read it out," Jordan says. "Actually, hang on — let me just—" He flips back through his notebook, past the page he'd bookmarked, frowning.
"You okay?" Sam asks.
"Yeah, just—" Jordan taps the page. "I didn't write a third repetition. I wrote it twice. It's written three times right here." He looks up. "I don't know why I said three the first time. Let's just go with it."
Nobody has a joke ready for that one.
[[Back->E1_Chamber_Search]]Kaelen Thornwood steps over the threshold, and the air changes.
Dust hangs undisturbed in shafts of pale light filtering through cracks in the ceiling. Rows of stone pillars, carved with symbols long faded, stretch into the dark. Somewhere ahead, water drips in a slow, steady rhythm.
The shrine has been sealed for centuries. Nothing about it feels welcoming, and yet, carved into the lintel overhead, a worn relief of a robed woman cradling a sheaf of wheat watches the party enter.
<<img "shrine_entrance">>
"Careful," Kaelen mutters back to the others. "Something's not right in here."
Jordan, from behind the DM screen: "Roll me a perception check, Kaelen. You're the one who'd recognize the iconography."
<<diceCheck "success" 11 "Roll Perception" "E1_Outer_Chamber">><<img "bone_ward_ring">>
Something glints faintly on the floor near the inner doorway, small, deliberate, not the kind of thing that ends up scattered by accident.
Kaelen crouches to look closer. A handful of bone shards, pale and polished, arranged in a loose ring just inside the threshold. Not decoration. A ward, laid by someone who wanted to know if anyone crossed it.
"That's not natural," Jordan says, mostly to himself, flipping back a page in his notes like he's double-checking something he wrote. "Nice catch, though. What do you do?"
<<if $e1WardKnowledge is true>>
<p><em>Whatever you learned back in the outer chamber, it applies here too. The pattern's the same.</em></p>
<</if>>
<<link "Step over it carefully, one at a time">>
<<set $e1WardApproach to "careful">>
<<goto "E1_Guardian_Encounter">>
<</link>>
<<link "Have Sam try to disarm it">>
<<set $e1WardApproach to "disarm">>
<<goto "E1_Guardian_Encounter">>
<</link>>
<<link "Kaelen just knows how to cross it safely">>
<<set $e1WardApproach to "instinct">>
<<goto "E1_Guardian_Encounter">>
<</link>>