JavaScript is required. Please enable it to continue.
Your browser lacks required capabilities. Please upgrade it or switch to another to continue.
Loading…
{ "name": "Jack's World", "ifid": "507b45c6-49cc-4528-b6b6-54ef7bc6cf1c", "format": "SugarCube", "format-version": "2.36.1", "start": "Start" }
<<set $player = { "name": "Jack", "portrait": "player.jpg", "current_location": "", "core_traits": {"money": 50}, "flags": {"game_started": false, "jack_arrived_complete": false, "chores_explained": false, "peek_unlocked": false, "bills_discovered": false, "job_started": false, "first_rent_paid": false, "rent_last_paid": false, "routine_noticed": false, "late_night_talk": false, "massage_offered": false, "towel_encounter_complete": false, "date_night_complete": false, "first_kiss_complete": false, "kiss_unlocked": false, "groping_unlocked": false, "oral_unlocked": false, "sex_unlocked": false, "date_proposed": false, "bath_invitation_complete": false} }>><<set $npcs = {"ea52da0b-a9bd-41ca-b6f7-ddec3f048a6c": {"name": "Angela", "portrait": "angela.jpg", "core_traits": {"love": 0, "trust": 0}, "flags": {}, "schedule": []}, "3e18e367-08df-440d-8775-0e2c494fcdde": {"name": "Maria", "portrait": "", "core_traits": {}, "flags": {}, "schedule": []}}>><<set $flags = { "game_started": true, "debug_mode": false }>><<set $flags_meta = {}>><<set $game_state = { "current_canvas": "", "visited_locations": [], "visited_nodes": [], "trigger_history": {}, "activity_trigger_history": {}, "visited_choices": {}, "time_state": { "current_hour": 6, "current_minute": 0, "current_day": "Monday", "current_week": 1, "day": 1 } }>><<script>> // Initialize setup object and time management functions if (typeof setup === 'undefined') { window.setup = {}; } // Limit history depth (default 100 is excessive — only need 1 back for Stats/Quest pages) Config.history.maxStates = 2; // Static lookup data — stored on setup (not State.variables) to avoid deep-clone on every passage transition setup.help_data = {"npcs": {"ea52da0b-a9bd-41ca-b6f7-ddec3f048a6c": {"name": "Angela", "activities": [{"node_id": "arrival", "name": "Jack Arrives", "linked_flag": "jack_arrived_complete", "canvas_id": "35660013-92ee-48da-b52a-950c05550e8c", "canvas_slug": "jack_arrives", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": null, "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "house_rules", "name": "House Rules", "linked_flag": "chores_explained", "canvas_id": "77af4bc5-05e2-4d6b-af2e-55d41fc29ddf", "canvas_slug": "house_rules", "linked_canvas_node": null, "requires_nodes": ["arrival"], "requires_group": null, "location": "Home", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "jack_arrived_complete", "operator": "gte"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "accidental_glimpse", "name": "The Accidental Glimpse", "linked_flag": "peek_unlocked", "canvas_id": "93e51bf3-ad19-4c8d-ab07-bbde172d4857", "canvas_slug": "accidental_glimpse", "linked_canvas_node": null, "requires_nodes": ["first_breakfast"], "requires_group": null, "location": "Bathroom", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_peek", "name": "Returning to Watch", "linked_flag": null, "canvas_id": "87e07146-10fa-4562-8bcc-a74410148af3", "canvas_slug": "activity_bath_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 8 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "the_bills", "name": "The Bills", "linked_flag": "bills_discovered", "canvas_id": "9701502a-4d5e-484e-aea0-3e0d7d197a21", "canvas_slug": "angelas_struggle", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "trait", "value": 4, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 5, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "the_job", "name": "The Job", "linked_flag": "job_started", "canvas_id": "84ee6921-81dc-4f9f-860e-0c58155ae774", "canvas_slug": "jack_gets_hired", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "bills_discovered", "operator": "is_true"}, {"type": "trait", "value": 6, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "her_routine", "name": "Her Routine", "linked_flag": "routine_noticed", "canvas_id": "a62c89df-8d90-4384-bca2-67fe117f589e", "canvas_slug": "angelas_evening", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_rent", "name": "First Rent", "linked_flag": "first_rent_paid", "canvas_id": "604413f9-8651-4bba-81a3-c1d5fa797110", "canvas_slug": "first_rent_day", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 10, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "cant_sleep", "name": "Can't Sleep", "linked_flag": "late_night_talk", "canvas_id": "86000293-ba47-4430-ba02-0fc2d0303732", "canvas_slug": "late_night_kitchen", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 9 PM - 2 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 14, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "towel_moment", "name": "The Towel Encounter", "linked_flag": "towel_encounter_complete", "canvas_id": "0d7627ce-1680-4f9b-8c90-10d8ea655aaa", "canvas_slug": "towel_encounter", "linked_canvas_node": null, "requires_nodes": ["arrival"], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_kiss", "name": "The First Kiss", "linked_flag": "first_kiss_complete", "canvas_id": "0250fcc1-bf27-445e-82b2-975bc27ca36f", "canvas_slug": "first_kiss", "linked_canvas_node": null, "requires_nodes": ["towel_moment"], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_massage", "name": "She Let Him In", "linked_flag": "massage_offered", "canvas_id": "1a773236-066c-4ef8-a48b-b01d73d888d8", "canvas_slug": "massage_offer", "linked_canvas_node": null, "requires_nodes": ["first_kiss"], "requires_group": null, "location": "Living Room", "schedule": "between 6 PM - 9 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_kiss_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "first_kiss_complete", "operator": "gte"}, {"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "deep_talk", "name": "Opening Up", "linked_flag": null, "canvas_id": "e4698ed6-1afc-440b-aa47-039c91a7f904", "canvas_slug": "activity_deep_conversation_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "massage_offered", "operator": "gte"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "bath_invitation", "name": "She Noticed", "linked_flag": null, "canvas_id": "cdd88fe2-180f-4acd-9c09-fc688a78f760", "canvas_slug": "event_bath_invitation", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "date_proposal", "name": "Somewhere Else", "linked_flag": "date_proposed", "canvas_id": "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2", "canvas_slug": "event_date_proposal", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 75, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "date_night", "name": "Somewhere Nobody Knows Us", "linked_flag": "date_night_complete", "canvas_id": "d0929ed7-f634-4c19-b048-29d4ea77ef9c", "canvas_slug": "activity_date_night_hotel_angela", "linked_canvas_node": null, "requires_nodes": ["date_proposal"], "requires_group": null, "location": "Hotel Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "date_proposed", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 300, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "exploring", "name": "Trust Without Words", "linked_flag": null, "canvas_id": "2209f645-0991-4f24-b4c9-6349d8b536b9", "canvas_slug": "activity_exploring_kink_angela", "linked_canvas_node": null, "requires_nodes": ["date_night"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 4, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 90, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "date_night_complete", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": null, "name": "Grocery Shopping", "linked_flag": null, "canvas_id": "810f0d67-837a-4a58-9eda-bd895f2b821a", "requires_nodes": [], "requires_group": null, "location": "Street", "schedule": "between 9 AM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Do Laundry", "linked_flag": null, "canvas_id": "6f5b0f28-b244-4b13-ac82-95fba4cee208", "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Clean Apartment", "linked_flag": null, "canvas_id": "369cdcd8-2094-4c08-8535-727c194a297b", "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Cook Dinner", "linked_flag": null, "canvas_id": "0af73f66-ab63-4be8-a6d7-16567c83c7ee", "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 5 PM - 7 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Wash Dishes", "linked_flag": null, "canvas_id": "25b75401-e63e-49aa-9293-9c5e8c6ebabf", "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Rent Day", "linked_flag": null, "canvas_id": "5dff160b-08c7-45c8-9954-44d5f86becdf", "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "days_since_flag", "value": 7, "subject": "player", "flag_key": "rent_last_paid", "operator": "gte"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Cafe Shift", "linked_flag": null, "canvas_id": "31a70088-6fd9-4fb3-b412-0c471b8896e1", "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "money", "value": 70}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Cafe Shift", "linked_flag": null, "canvas_id": "24f8a779-39d5-4312-be02-81f6510c4788", "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 7 AM - 12 PM", "trait_requirements": [], "trait_effects": [{"trait": "money", "value": 70}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Angela's Morning", "linked_flag": null, "canvas_id": "66aa2625-2586-4275-852b-9cb50879f43d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 6 AM - 7 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}]}}, "trait_activities": {"love": [{"node_id": "arrival", "name": "Jack Arrives", "linked_flag": "jack_arrived_complete", "canvas_id": "35660013-92ee-48da-b52a-950c05550e8c", "canvas_slug": "jack_arrives", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": null, "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "accidental_glimpse", "name": "The Accidental Glimpse", "linked_flag": "peek_unlocked", "canvas_id": "93e51bf3-ad19-4c8d-ab07-bbde172d4857", "canvas_slug": "accidental_glimpse", "linked_canvas_node": null, "requires_nodes": ["first_breakfast"], "requires_group": null, "location": "Bathroom", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "accidental_glimpse", "name": "The Accidental Glimpse", "linked_flag": "peek_unlocked", "canvas_id": "93e51bf3-ad19-4c8d-ab07-bbde172d4857", "canvas_slug": "accidental_glimpse", "linked_canvas_node": null, "requires_nodes": ["first_breakfast"], "requires_group": null, "location": "Bathroom", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_peek", "name": "Returning to Watch", "linked_flag": null, "canvas_id": "87e07146-10fa-4562-8bcc-a74410148af3", "canvas_slug": "activity_bath_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 8 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "node_conditions": null}, {"node_id": "first_peek", "name": "Returning to Watch", "linked_flag": null, "canvas_id": "87e07146-10fa-4562-8bcc-a74410148af3", "canvas_slug": "activity_bath_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 8 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "node_conditions": null}, {"node_id": "first_peek", "name": "Returning to Watch", "linked_flag": null, "canvas_id": "87e07146-10fa-4562-8bcc-a74410148af3", "canvas_slug": "activity_bath_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 8 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "node_conditions": null}, {"node_id": "first_peek", "name": "Returning to Watch", "linked_flag": null, "canvas_id": "87e07146-10fa-4562-8bcc-a74410148af3", "canvas_slug": "activity_bath_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 8 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "the_bills", "name": "The Bills", "linked_flag": "bills_discovered", "canvas_id": "9701502a-4d5e-484e-aea0-3e0d7d197a21", "canvas_slug": "angelas_struggle", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "trait", "value": 4, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 5, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "the_job", "name": "The Job", "linked_flag": "job_started", "canvas_id": "84ee6921-81dc-4f9f-860e-0c58155ae774", "canvas_slug": "jack_gets_hired", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "bills_discovered", "operator": "is_true"}, {"type": "trait", "value": 6, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "her_routine", "name": "Her Routine", "linked_flag": "routine_noticed", "canvas_id": "a62c89df-8d90-4384-bca2-67fe117f589e", "canvas_slug": "angelas_evening", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_rent", "name": "First Rent", "linked_flag": "first_rent_paid", "canvas_id": "604413f9-8651-4bba-81a3-c1d5fa797110", "canvas_slug": "first_rent_day", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 10, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "cant_sleep", "name": "Can't Sleep", "linked_flag": "late_night_talk", "canvas_id": "86000293-ba47-4430-ba02-0fc2d0303732", "canvas_slug": "late_night_kitchen", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 9 PM - 2 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 14, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "towel_moment", "name": "The Towel Encounter", "linked_flag": "towel_encounter_complete", "canvas_id": "0d7627ce-1680-4f9b-8c90-10d8ea655aaa", "canvas_slug": "towel_encounter", "linked_canvas_node": null, "requires_nodes": ["arrival"], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_kiss", "name": "The First Kiss", "linked_flag": "first_kiss_complete", "canvas_id": "0250fcc1-bf27-445e-82b2-975bc27ca36f", "canvas_slug": "first_kiss", "linked_canvas_node": null, "requires_nodes": ["towel_moment"], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_kiss", "name": "The First Kiss", "linked_flag": "first_kiss_complete", "canvas_id": "0250fcc1-bf27-445e-82b2-975bc27ca36f", "canvas_slug": "first_kiss", "linked_canvas_node": null, "requires_nodes": ["towel_moment"], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_massage", "name": "She Let Him In", "linked_flag": "massage_offered", "canvas_id": "1a773236-066c-4ef8-a48b-b01d73d888d8", "canvas_slug": "massage_offer", "linked_canvas_node": null, "requires_nodes": ["first_kiss"], "requires_group": null, "location": "Living Room", "schedule": "between 6 PM - 9 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_kiss_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "first_kiss_complete", "operator": "gte"}, {"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "deep_talk", "name": "Opening Up", "linked_flag": null, "canvas_id": "e4698ed6-1afc-440b-aa47-039c91a7f904", "canvas_slug": "activity_deep_conversation_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "massage_offered", "operator": "gte"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "deep_talk", "name": "Opening Up", "linked_flag": null, "canvas_id": "e4698ed6-1afc-440b-aa47-039c91a7f904", "canvas_slug": "activity_deep_conversation_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "massage_offered", "operator": "gte"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "deep_talk", "name": "Opening Up", "linked_flag": null, "canvas_id": "e4698ed6-1afc-440b-aa47-039c91a7f904", "canvas_slug": "activity_deep_conversation_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "massage_offered", "operator": "gte"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "bath_invitation", "name": "She Noticed", "linked_flag": null, "canvas_id": "cdd88fe2-180f-4acd-9c09-fc688a78f760", "canvas_slug": "event_bath_invitation", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "date_proposal", "name": "Somewhere Else", "linked_flag": "date_proposed", "canvas_id": "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2", "canvas_slug": "event_date_proposal", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 75, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "date_night", "name": "Somewhere Nobody Knows Us", "linked_flag": "date_night_complete", "canvas_id": "d0929ed7-f634-4c19-b048-29d4ea77ef9c", "canvas_slug": "activity_date_night_hotel_angela", "linked_canvas_node": null, "requires_nodes": ["date_proposal"], "requires_group": null, "location": "Hotel Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "date_proposed", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 300, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "exploring", "name": "Trust Without Words", "linked_flag": null, "canvas_id": "2209f645-0991-4f24-b4c9-6349d8b536b9", "canvas_slug": "activity_exploring_kink_angela", "linked_canvas_node": null, "requires_nodes": ["date_night"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 4, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 90, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "date_night_complete", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Angela's Morning", "linked_flag": null, "canvas_id": "66aa2625-2586-4275-852b-9cb50879f43d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 6 AM - 7 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Angela's Morning", "linked_flag": null, "canvas_id": "66aa2625-2586-4275-852b-9cb50879f43d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 6 AM - 7 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Angela's Morning", "linked_flag": null, "canvas_id": "66aa2625-2586-4275-852b-9cb50879f43d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 6 AM - 7 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Angela's Morning", "linked_flag": null, "canvas_id": "66aa2625-2586-4275-852b-9cb50879f43d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 6 AM - 7 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}], "trust": [{"node_id": "house_rules", "name": "House Rules", "linked_flag": "chores_explained", "canvas_id": "77af4bc5-05e2-4d6b-af2e-55d41fc29ddf", "canvas_slug": "house_rules", "linked_canvas_node": null, "requires_nodes": ["arrival"], "requires_group": null, "location": "Home", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "jack_arrived_complete", "operator": "gte"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_breakfast", "name": "First Breakfast Together", "linked_flag": null, "canvas_id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "canvas_slug": "activity_breakfast_angela", "linked_canvas_node": null, "requires_nodes": ["house_rules"], "requires_group": null, "location": "Kitchen", "schedule": "between 7 AM - 9 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_peek", "name": "Returning to Watch", "linked_flag": null, "canvas_id": "87e07146-10fa-4562-8bcc-a74410148af3", "canvas_slug": "activity_bath_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 8 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "node_conditions": null}, {"node_id": "first_peek", "name": "Returning to Watch", "linked_flag": null, "canvas_id": "87e07146-10fa-4562-8bcc-a74410148af3", "canvas_slug": "activity_bath_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 8 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "first_movie", "name": "Movie Night", "linked_flag": null, "canvas_id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "canvas_slug": "activity_movie_night_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 10 PM - 12 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": null, "node_conditions": null}, {"node_id": "the_bills", "name": "The Bills", "linked_flag": "bills_discovered", "canvas_id": "9701502a-4d5e-484e-aea0-3e0d7d197a21", "canvas_slug": "angelas_struggle", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "trait", "value": 4, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 5, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "the_bills", "name": "The Bills", "linked_flag": "bills_discovered", "canvas_id": "9701502a-4d5e-484e-aea0-3e0d7d197a21", "canvas_slug": "angelas_struggle", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "trait", "value": 4, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 5, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "the_job", "name": "The Job", "linked_flag": "job_started", "canvas_id": "84ee6921-81dc-4f9f-860e-0c58155ae774", "canvas_slug": "jack_gets_hired", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "bills_discovered", "operator": "is_true"}, {"type": "trait", "value": 6, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "her_routine", "name": "Her Routine", "linked_flag": "routine_noticed", "canvas_id": "a62c89df-8d90-4384-bca2-67fe117f589e", "canvas_slug": "angelas_evening", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_rent", "name": "First Rent", "linked_flag": "first_rent_paid", "canvas_id": "604413f9-8651-4bba-81a3-c1d5fa797110", "canvas_slug": "first_rent_day", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 10, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_rent", "name": "First Rent", "linked_flag": "first_rent_paid", "canvas_id": "604413f9-8651-4bba-81a3-c1d5fa797110", "canvas_slug": "first_rent_day", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 10, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "cant_sleep", "name": "Can't Sleep", "linked_flag": "late_night_talk", "canvas_id": "86000293-ba47-4430-ba02-0fc2d0303732", "canvas_slug": "late_night_kitchen", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 9 PM - 2 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 14, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "towel_moment", "name": "The Towel Encounter", "linked_flag": "towel_encounter_complete", "canvas_id": "0d7627ce-1680-4f9b-8c90-10d8ea655aaa", "canvas_slug": "towel_encounter", "linked_canvas_node": null, "requires_nodes": ["arrival"], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_kiss", "name": "The First Kiss", "linked_flag": "first_kiss_complete", "canvas_id": "0250fcc1-bf27-445e-82b2-975bc27ca36f", "canvas_slug": "first_kiss", "linked_canvas_node": null, "requires_nodes": ["towel_moment"], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "first_massage", "name": "She Let Him In", "linked_flag": "massage_offered", "canvas_id": "1a773236-066c-4ef8-a48b-b01d73d888d8", "canvas_slug": "massage_offer", "linked_canvas_node": null, "requires_nodes": ["first_kiss"], "requires_group": null, "location": "Living Room", "schedule": "between 6 PM - 9 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_kiss_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "first_kiss_complete", "operator": "gte"}, {"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "deep_talk", "name": "Opening Up", "linked_flag": null, "canvas_id": "e4698ed6-1afc-440b-aa47-039c91a7f904", "canvas_slug": "activity_deep_conversation_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "massage_offered", "operator": "gte"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "deep_talk", "name": "Opening Up", "linked_flag": null, "canvas_id": "e4698ed6-1afc-440b-aa47-039c91a7f904", "canvas_slug": "activity_deep_conversation_angela", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "massage_offered", "operator": "gte"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "spa_night", "name": "The Massage", "linked_flag": null, "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "canvas_slug": "activity_spa_massage_angela", "linked_canvas_node": null, "requires_nodes": ["first_massage"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 6, "npc_id": "npc_angela"}, {"trait": "trust", "value": 4, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "bath_invitation", "name": "She Noticed", "linked_flag": null, "canvas_id": "cdd88fe2-180f-4acd-9c09-fc688a78f760", "canvas_slug": "event_bath_invitation", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 7 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "date_proposal", "name": "Somewhere Else", "linked_flag": "date_proposed", "canvas_id": "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2", "canvas_slug": "event_date_proposal", "linked_canvas_node": null, "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 3, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 75, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "date_night", "name": "Somewhere Nobody Knows Us", "linked_flag": "date_night_complete", "canvas_id": "d0929ed7-f634-4c19-b048-29d4ea77ef9c", "canvas_slug": "activity_date_night_hotel_angela", "linked_canvas_node": null, "requires_nodes": ["date_proposal"], "requires_group": null, "location": "Hotel Room", "schedule": "between 7 PM - 10 PM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "date_proposed", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 300, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": "exploring", "name": "Trust Without Words", "linked_flag": null, "canvas_id": "2209f645-0991-4f24-b4c9-6349d8b536b9", "canvas_slug": "activity_exploring_kink_angela", "linked_canvas_node": null, "requires_nodes": ["date_night"], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 4, "npc_id": "npc_angela"}, {"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "trait", "value": 90, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "date_night_complete", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "node_conditions": null}, {"node_id": null, "name": "Grocery Shopping", "linked_flag": null, "canvas_id": "810f0d67-837a-4a58-9eda-bd895f2b821a", "requires_nodes": [], "requires_group": null, "location": "Street", "schedule": "between 9 AM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Do Laundry", "linked_flag": null, "canvas_id": "6f5b0f28-b244-4b13-ac82-95fba4cee208", "requires_nodes": [], "requires_group": null, "location": "Bathroom", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Clean Apartment", "linked_flag": null, "canvas_id": "369cdcd8-2094-4c08-8535-727c194a297b", "requires_nodes": [], "requires_group": null, "location": "Living Room", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Cook Dinner", "linked_flag": null, "canvas_id": "0af73f66-ab63-4be8-a6d7-16567c83c7ee", "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 5 PM - 7 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Wash Dishes", "linked_flag": null, "canvas_id": "25b75401-e63e-49aa-9293-9c5e8c6ebabf", "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": "between 9 AM - 2 PM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Rent Day", "linked_flag": null, "canvas_id": "5dff160b-08c7-45c8-9954-44d5f86becdf", "requires_nodes": [], "requires_group": null, "location": "Kitchen", "schedule": null, "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "days_since_flag", "value": 7, "subject": "player", "flag_key": "rent_last_paid", "operator": "gte"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Cafe Shift", "linked_flag": null, "canvas_id": "31a70088-6fd9-4fb3-b412-0c471b8896e1", "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "money", "value": 70}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Cafe Shift", "linked_flag": null, "canvas_id": "24f8a779-39d5-4312-be02-81f6510c4788", "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 7 AM - 12 PM", "trait_requirements": [], "trait_effects": [{"trait": "money", "value": 70}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Night Together", "linked_flag": null, "canvas_id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 10 PM - 1 AM", "trait_requirements": [], "trait_effects": [{"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 2, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Angela's Morning", "linked_flag": null, "canvas_id": "66aa2625-2586-4275-852b-9cb50879f43d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 6 AM - 7 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Angela's Morning", "linked_flag": null, "canvas_id": "66aa2625-2586-4275-852b-9cb50879f43d", "requires_nodes": [], "requires_group": null, "location": "Angela's Bedroom", "schedule": "between 6 AM - 7 AM", "trait_requirements": [], "trait_effects": [{"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 1, "npc_id": "npc_angela"}, {"trait": "love", "value": 5, "npc_id": "npc_angela"}, {"trait": "trust", "value": 3, "npc_id": "npc_angela"}, {"trait": "love", "value": 8, "npc_id": "npc_angela"}, {"trait": "trust", "value": 5, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}], "money": [{"node_id": null, "name": "Cafe Shift", "linked_flag": null, "canvas_id": "31a70088-6fd9-4fb3-b412-0c471b8896e1", "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 2 PM - 5 PM", "trait_requirements": [], "trait_effects": [{"trait": "money", "value": 70}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}, {"node_id": null, "name": "Cafe Shift", "linked_flag": null, "canvas_id": "24f8a779-39d5-4312-be02-81f6510c4788", "requires_nodes": [], "requires_group": null, "location": "Cafe", "schedule": "between 7 AM - 12 PM", "trait_requirements": [], "trait_effects": [{"trait": "money", "value": 70}, {"trait": "trust", "value": 1, "npc_id": "npc_angela"}], "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "is_repeatable": true}]}, "flag_unlock_map": {"first_kiss_complete": {"canvas_name": "The First Kiss", "canvas_id": "0250fcc1-bf27-445e-82b2-975bc27ca36f", "location": "Living Room", "schedule": "between 7 PM - 10 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}, "towel_encounter_complete": {"canvas_name": "Towel Encounter", "canvas_id": "0d7627ce-1680-4f9b-8c90-10d8ea655aaa", "location": "Bathroom", "schedule": "between 7 PM - 10 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, "kiss_unlocked": {"canvas_name": "The Offer", "canvas_id": "1a773236-066c-4ef8-a48b-b01d73d888d8", "location": "Living Room", "schedule": "between 6 PM - 9 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_kiss_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "first_kiss_complete", "operator": "gte"}, {"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, "massage_offered": {"canvas_name": "The Offer", "canvas_id": "1a773236-066c-4ef8-a48b-b01d73d888d8", "location": "Living Room", "schedule": "between 6 PM - 9 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_kiss_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "first_kiss_complete", "operator": "gte"}, {"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, "late_night_talk": {"canvas_name": "Can't Sleep", "canvas_id": "86000293-ba47-4430-ba02-0fc2d0303732", "location": "Kitchen", "schedule": "between 9 PM - 2 AM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 14, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, "first_rent_paid": {"canvas_name": "First Rent", "canvas_id": "604413f9-8651-4bba-81a3-c1d5fa797110", "location": "Kitchen", "schedule": null, "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 10, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}}, "rent_last_paid": {"canvas_name": "First Rent", "canvas_id": "604413f9-8651-4bba-81a3-c1d5fa797110", "location": "Kitchen", "schedule": null, "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 10, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}}, "routine_noticed": {"canvas_name": "Her Routine", "canvas_id": "a62c89df-8d90-4384-bca2-67fe117f589e", "location": "Living Room", "schedule": "between 7 PM - 10 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, "job_started": {"canvas_name": "Apply Job", "canvas_id": "84ee6921-81dc-4f9f-860e-0c58155ae774", "location": "Cafe", "schedule": "between 9 AM - 2 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "bills_discovered", "operator": "is_true"}, {"type": "trait", "value": 6, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, "bills_discovered": {"canvas_name": "The Bills", "canvas_id": "9701502a-4d5e-484e-aea0-3e0d7d197a21", "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "trait", "value": 4, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 5, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}, "peek_unlocked": {"canvas_name": "The Accidental Glimpse", "canvas_id": "93e51bf3-ad19-4c8d-ab07-bbde172d4857", "location": "Bathroom", "schedule": null, "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}, "chores_explained": {"canvas_name": "House Rules", "canvas_id": "77af4bc5-05e2-4d6b-af2e-55d41fc29ddf", "location": "Home", "schedule": null, "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "jack_arrived_complete", "operator": "gte"}], "logic": "AND", "version": "1.0"}}, "game_started": {"canvas_name": "Jack Arrives", "canvas_id": "35660013-92ee-48da-b52a-950c05550e8c", "location": null, "schedule": null, "canvas_conditions": null}, "jack_arrived_complete": {"canvas_name": "Jack Arrives", "canvas_id": "35660013-92ee-48da-b52a-950c05550e8c", "location": null, "schedule": null, "canvas_conditions": null}, "groping_unlocked": {"canvas_name": "Spa/Massage", "canvas_id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "location": "Angela's Bedroom", "schedule": "between 2 PM - 5 PM", "canvas_conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, "date_night_complete": {"canvas_name": "Date Night Hotel", "canvas_id": "d0929ed7-f634-4c19-b048-29d4ea77ef9c", "location": "Hotel Room", "schedule": "between 7 PM - 10 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "date_proposed", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 300, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}}, "sex_unlocked": {"canvas_name": "Date Night Hotel", "canvas_id": "d0929ed7-f634-4c19-b048-29d4ea77ef9c", "location": "Hotel Room", "schedule": "between 7 PM - 10 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "date_proposed", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 300, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}}, "date_proposed": {"canvas_name": "Somewhere Else", "canvas_id": "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2", "location": "Kitchen", "schedule": "between 7 PM - 10 PM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 75, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, "oral_unlocked": {"canvas_name": "She Noticed", "canvas_id": "cdd88fe2-180f-4acd-9c09-fc688a78f760", "location": "Bathroom", "schedule": "between 7 PM - 1 AM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}, "bath_invitation_complete": {"canvas_name": "She Noticed", "canvas_id": "cdd88fe2-180f-4acd-9c09-fc688a78f760", "location": "Bathroom", "schedule": "between 7 PM - 1 AM", "canvas_conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}}, "starting_canvas_id": "35660013-92ee-48da-b52a-950c05550e8c", "locationCanvases": {"c4c4487c-40f0-4259-ad0e-2cb8ae06add8": [{"id": "77af4bc5-05e2-4d6b-af2e-55d41fc29ddf", "name": "House Rules", "displayName": "House Rules", "passageName": "Canvas_house_rules_Node_1", "priority": 10, "hasSchedules": false, "scheduleParams": [], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "jack_arrived_complete", "operator": "gte"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}], "9f385aa2-5ef4-4895-b867-bb70a80afb05": [{"id": "86000293-ba47-4430-ba02-0fc2d0303732", "name": "Can't Sleep", "displayName": "Can't Sleep", "passageName": "Canvas_late_night_kitchen_Node_1", "priority": 10, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "21:00", "endTime": "02:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 14, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "604413f9-8651-4bba-81a3-c1d5fa797110", "name": "First Rent", "displayName": "First Rent", "passageName": "Canvas_first_rent_day_Node_1", "priority": 10, "hasSchedules": false, "scheduleParams": [], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 10, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": null}, {"id": "9701502a-4d5e-484e-aea0-3e0d7d197a21", "name": "The Bills", "displayName": "The Bills", "passageName": "Canvas_angelas_struggle_Node_1", "priority": 10, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "22:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "trait", "value": 4, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 5, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2", "name": "Somewhere Else", "displayName": "Somewhere Else", "passageName": "Canvas_event_date_proposal_Node_1", "priority": 2, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "22:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 75, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": 1, "npcId": "npc_angela"}, {"id": "0af73f66-ab63-4be8-a6d7-16567c83c7ee", "name": "Cook Dinner", "displayName": "Cook Dinner", "passageName": "Canvas_activity_cook_dinner_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "17:00", "endTime": "19:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": null}, {"id": "25b75401-e63e-49aa-9293-9c5e8c6ebabf", "name": "Wash Dishes", "displayName": "Wash Dishes", "passageName": "Canvas_activity_wash_dishes_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "09:00", "endTime": "14:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": null}, {"id": "5dff160b-08c7-45c8-9954-44d5f86becdf", "name": "Rent Day", "displayName": "Rent Day", "passageName": "Canvas_activity_weekly_rent_Node_1", "priority": 2, "hasSchedules": false, "scheduleParams": [], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "days_since_flag", "value": 7, "subject": "player", "flag_key": "rent_last_paid", "operator": "gte"}, {"type": "trait", "value": 200, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}, {"id": "8519e25e-acbb-4414-a56c-57e5225ab01b", "name": "Breakfast Together", "displayName": "Breakfast Together", "passageName": "Canvas_activity_breakfast_angela_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "07:00", "endTime": "09:00"}], "conditions": null, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}], "58f8de9e-e4af-45a5-81e6-9d02fc27ae07": [{"id": "0250fcc1-bf27-445e-82b2-975bc27ca36f", "name": "The First Kiss", "displayName": "The First Kiss", "passageName": "Canvas_first_kiss_Node_1", "priority": 10, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "22:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "1a773236-066c-4ef8-a48b-b01d73d888d8", "name": "The Offer", "displayName": "The Offer", "passageName": "Canvas_massage_offer_Node_1", "priority": 10, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "18:00", "endTime": "21:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_kiss_complete", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "first_kiss_complete", "operator": "gte"}, {"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "a62c89df-8d90-4384-bca2-67fe117f589e", "name": "Her Routine", "displayName": "Her Routine", "passageName": "Canvas_angelas_evening_Node_1", "priority": 10, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "22:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "369cdcd8-2094-4c08-8535-727c194a297b", "name": "Clean Apartment", "displayName": "Clean Apartment", "passageName": "Canvas_activity_clean_apartment_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "09:00", "endTime": "14:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": null}, {"id": "e4698ed6-1afc-440b-aa47-039c91a7f904", "name": "Deep Conversation", "displayName": "Deep Conversation", "passageName": "Canvas_activity_deep_conversation_angela_Node_1", "priority": 6, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "22:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "massage_offered", "operator": "gte"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81", "name": "Movie Night", "displayName": "Movie Night", "passageName": "Canvas_activity_movie_night_angela_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "22:00", "endTime": "00:00"}], "conditions": null, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}], "663cf0e9-28d8-4522-a7f2-167f17de3978": [{"id": "2209f645-0991-4f24-b4c9-6349d8b536b9", "name": "Exploring Kink", "displayName": "Exploring Kink", "passageName": "Canvas_activity_exploring_kink_angela_Node_1", "priority": 8, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "22:00", "endTime": "01:00"}], "conditions": {"items": [{"type": "trait", "value": 90, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "date_night_complete", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}, {"id": "c6680908-b444-4e94-8f5e-d7648d09de3c", "name": "Spa/Massage", "displayName": "Spa/Massage", "passageName": "Canvas_activity_spa_massage_angela_Node_1", "priority": 7, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "14:00", "endTime": "17:00"}], "conditions": {"items": [{"type": "trait", "value": 40, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}], "logic": "AND", "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}, {"id": "0eb588c2-f2de-4d84-a239-c279ea4d475d", "name": "Night Together", "displayName": "Night Together", "passageName": "Canvas_activity_morning_together_angela_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "22:00", "endTime": "01:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 25, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}, {"id": "66aa2625-2586-4275-852b-9cb50879f43d", "name": "Angela's Morning", "displayName": "Angela's Morning", "passageName": "Canvas_activity_morning_angela_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "06:00", "endTime": "07:30"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}], "6dbbdc4a-9288-49fe-9d44-8ead45647e75": [{"id": "0d7627ce-1680-4f9b-8c90-10d8ea655aaa", "name": "Towel Encounter", "displayName": "Towel Encounter", "passageName": "Canvas_towel_encounter_Node_1", "priority": 10, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "22:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "jack_arrived_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "first_rent_paid", "operator": "is_true"}, {"type": "trait", "value": 20, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 18, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "93e51bf3-ad19-4c8d-ab07-bbde172d4857", "name": "The Accidental Glimpse", "displayName": "The Accidental Glimpse", "passageName": "Canvas_accidental_glimpse_Node_1", "priority": 10, "hasSchedules": false, "scheduleParams": [], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}, {"type": "trait", "value": 8, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": "npc_angela"}, {"id": "6f5b0f28-b244-4b13-ac82-95fba4cee208", "name": "Do Laundry", "displayName": "Do Laundry", "passageName": "Canvas_activity_do_laundry_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "09:00", "endTime": "14:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": null}, {"id": "cdd88fe2-180f-4acd-9c09-fc688a78f760", "name": "She Noticed", "displayName": "She Noticed", "passageName": "Canvas_event_bath_invitation_Node_1", "priority": 2, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "01:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": 1, "npcId": "npc_angela"}, {"id": "87e07146-10fa-4562-8bcc-a74410148af3", "name": "Angela's Bath", "displayName": "Angela's Bath", "passageName": "Canvas_activity_bath_angela_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "20:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "peek_unlocked", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}], "6c2f3d40-f029-496a-9760-ff92f9325613": [{"id": "5d6a739c-bc13-4c0d-876f-2776a73fb52a", "name": "Bed", "displayName": "Bed", "passageName": "Canvas_activity_jacks_room_Node_1", "priority": 1, "hasSchedules": false, "scheduleParams": [], "conditions": null, "isRepeatable": true, "maxPerDay": null, "npcId": null}], "8569b260-35b8-4946-95ef-8a3fa9bb50ba": [{"id": "810f0d67-837a-4a58-9eda-bd895f2b821a", "name": "Grocery Shopping", "displayName": "Grocery Shopping", "passageName": "Canvas_activity_grocery_shopping_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "09:00", "endTime": "17:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "chores_explained", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": null}], "5a6f94eb-c917-43d4-95a7-92e322f39f43": [{"id": "84ee6921-81dc-4f9f-860e-0c58155ae774", "name": "Apply Job", "displayName": "Apply Job", "passageName": "Canvas_jack_gets_hired_Node_1", "priority": 10, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "09:00", "endTime": "14:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "bills_discovered", "operator": "is_true"}, {"type": "trait", "value": 6, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}, "isRepeatable": false, "maxPerDay": null, "npcId": null}, {"id": "31a70088-6fd9-4fb3-b412-0c471b8896e1", "name": "Cafe Shift", "displayName": "Cafe Shift", "passageName": "Canvas_activity_cafe_shift_afternoon_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "14:00", "endTime": "17:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 2, "npcId": null}, {"id": "24f8a779-39d5-4312-be02-81f6510c4788", "name": "Cafe Shift", "displayName": "Cafe Shift", "passageName": "Canvas_activity_cafe_shift_morning_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "07:00", "endTime": "12:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "job_started", "operator": "is_true"}], "version": "1.0"}, "isRepeatable": true, "maxPerDay": 2, "npcId": null}], "fbd71126-198b-4a62-8eac-432645348482": [{"id": "d0929ed7-f634-4c19-b048-29d4ea77ef9c", "name": "Date Night Hotel", "displayName": "Date Night Hotel", "passageName": "Canvas_activity_date_night_hotel_angela_Node_1", "priority": 1, "hasSchedules": true, "scheduleParams": [{"weekdays": [], "startTime": "19:00", "endTime": "22:00"}], "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "date_proposed", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 30, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}, {"type": "trait", "value": 300, "subject": "player", "operator": "gte", "trait_key": "money"}], "logic": "AND", "version": "1.0"}, "isRepeatable": true, "maxPerDay": 1, "npcId": "npc_angela"}]}, "canvasIdToActivityName": {"77af4bc5-05e2-4d6b-af2e-55d41fc29ddf": "House Rules", "86000293-ba47-4430-ba02-0fc2d0303732": "Can't Sleep", "604413f9-8651-4bba-81a3-c1d5fa797110": "First Rent", "9701502a-4d5e-484e-aea0-3e0d7d197a21": "The Bills", "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2": "Somewhere Else", "0af73f66-ab63-4be8-a6d7-16567c83c7ee": "Cook Dinner", "25b75401-e63e-49aa-9293-9c5e8c6ebabf": "Wash Dishes", "5dff160b-08c7-45c8-9954-44d5f86becdf": "Rent Day", "8519e25e-acbb-4414-a56c-57e5225ab01b": "Breakfast Together", "0250fcc1-bf27-445e-82b2-975bc27ca36f": "The First Kiss", "1a773236-066c-4ef8-a48b-b01d73d888d8": "The Offer", "a62c89df-8d90-4384-bca2-67fe117f589e": "Her Routine", "369cdcd8-2094-4c08-8535-727c194a297b": "Clean Apartment", "e4698ed6-1afc-440b-aa47-039c91a7f904": "Deep Conversation", "56f91831-8fbc-45d9-9b8a-b5cfb86cec81": "Movie Night", "2209f645-0991-4f24-b4c9-6349d8b536b9": "Exploring Kink", "c6680908-b444-4e94-8f5e-d7648d09de3c": "Spa/Massage", "0eb588c2-f2de-4d84-a239-c279ea4d475d": "Night Together", "66aa2625-2586-4275-852b-9cb50879f43d": "Angela's Morning", "0d7627ce-1680-4f9b-8c90-10d8ea655aaa": "Towel Encounter", "93e51bf3-ad19-4c8d-ab07-bbde172d4857": "The Accidental Glimpse", "6f5b0f28-b244-4b13-ac82-95fba4cee208": "Do Laundry", "cdd88fe2-180f-4acd-9c09-fc688a78f760": "She Noticed", "87e07146-10fa-4562-8bcc-a74410148af3": "Angela's Bath", "5d6a739c-bc13-4c0d-876f-2776a73fb52a": "Bed", "810f0d67-837a-4a58-9eda-bd895f2b821a": "Grocery Shopping", "84ee6921-81dc-4f9f-860e-0c58155ae774": "Apply Job", "31a70088-6fd9-4fb3-b412-0c471b8896e1": "Cafe Shift", "24f8a779-39d5-4312-be02-81f6510c4788": "Cafe Shift", "d0929ed7-f634-4c19-b048-29d4ea77ef9c": "Date Night Hotel"}, "canvasConditionalChoices": {"8519e25e-acbb-4414-a56c-57e5225ab01b": [{"key": "8519e25e-acbb-4414-a56c-57e5225ab01b:cc0", "conditions": {"items": [{"type": "trait", "value": 22, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "version": "1.0"}}, {"key": "8519e25e-acbb-4414-a56c-57e5225ab01b:cc1", "conditions": {"items": [{"type": "trait", "value": 42, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, {"key": "8519e25e-acbb-4414-a56c-57e5225ab01b:cc2", "conditions": {"items": [{"type": "trait", "value": 62, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, {"key": "8519e25e-acbb-4414-a56c-57e5225ab01b:cc3", "conditions": {"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}], "56f91831-8fbc-45d9-9b8a-b5cfb86cec81": [{"key": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc0", "conditions": {"items": [{"type": "trait", "value": 50, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, {"key": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc1", "conditions": {"items": [{"type": "trait", "value": 65, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, {"key": "56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc2", "conditions": {"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}], "c6680908-b444-4e94-8f5e-d7648d09de3c": [{"key": "c6680908-b444-4e94-8f5e-d7648d09de3c:cc0", "conditions": {"items": [{"type": "trait", "value": 45, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "version": "1.0"}}, {"key": "c6680908-b444-4e94-8f5e-d7648d09de3c:cc1", "conditions": {"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, {"key": "c6680908-b444-4e94-8f5e-d7648d09de3c:cc2", "conditions": {"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}], "0eb588c2-f2de-4d84-a239-c279ea4d475d": [{"key": "0eb588c2-f2de-4d84-a239-c279ea4d475d:cc0", "conditions": {"items": [{"type": "trait", "value": 72, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, {"key": "0eb588c2-f2de-4d84-a239-c279ea4d475d:cc1", "conditions": {"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}, {"key": "0eb588c2-f2de-4d84-a239-c279ea4d475d:cc2", "conditions": {"items": [{"type": "trait", "value": 92, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"}}], "66aa2625-2586-4275-852b-9cb50879f43d": [{"key": "66aa2625-2586-4275-852b-9cb50879f43d:cc0", "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "towel_encounter_complete", "operator": "gte"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 22, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"}}, {"key": "66aa2625-2586-4275-852b-9cb50879f43d:cc1", "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}], "87e07146-10fa-4562-8bcc-a74410148af3": [{"key": "87e07146-10fa-4562-8bcc-a74410148af3:cc0", "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "bath_invitation_complete", "operator": "gte"}, {"type": "trait", "value": 65, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}, {"key": "87e07146-10fa-4562-8bcc-a74410148af3:cc1", "conditions": {"items": [{"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"}}]}}; setup.npc_slug_map = {"npc_angela": "ea52da0b-a9bd-41ca-b6f7-ddec3f048a6c", "npc_maria": "3e18e367-08df-440d-8775-0e2c494fcdde"}; setup.locations = {"loc_hallway": {"name": "Home", "id": "c4c4487c-40f0-4259-ad0e-2cb8ae06add8"}, "loc_kitchen": {"name": "Kitchen", "id": "9f385aa2-5ef4-4895-b867-bb70a80afb05"}, "loc_living_room": {"name": "Living Room", "id": "58f8de9e-e4af-45a5-81e6-9d02fc27ae07"}, "loc_angelas_bedroom": {"name": "Angela's Bedroom", "id": "663cf0e9-28d8-4522-a7f2-167f17de3978"}, "loc_bathroom": {"name": "Bathroom", "id": "6dbbdc4a-9288-49fe-9d44-8ead45647e75"}, "loc_jacks_bedroom": {"name": "Jack's Bedroom", "id": "6c2f3d40-f029-496a-9760-ff92f9325613"}, "loc_street": {"name": "Street", "id": "8569b260-35b8-4946-95ef-8a3fa9bb50ba"}, "loc_cafe": {"name": "Cafe", "id": "5a6f94eb-c917-43d4-95a7-92e322f39f43"}, "loc_hotel": {"name": "Hotel", "id": "5380069a-9ff0-4877-b0b9-74bdbeac1cf3"}, "loc_hotel_lobby": {"name": "Hotel Lobby", "id": "6cb9664f-0e77-4974-b0c1-02de30fa8ff4"}, "loc_hotel_room": {"name": "Hotel Room", "id": "fbd71126-198b-4a62-8eac-432645348482"}}; setup.story_arc = {"hints": {}, "nodes": [{"id": "arrival", "npc": "npc_angela", "name": "Jack Arrives", "group": null, "chapter": "chapter_prologue", "linked_flag": "jack_arrived_complete", "is_milestone": true, "journal_entry": "I moved into Angela's apartment today. She put fresh sheets on the bed and folded a towel on the pillow. 'Welcome home, Jack.' Home. I'm not sure what that word means yet.", "linked_canvas": "jack_arrives", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "house_rules", "npc": "npc_angela", "name": "House Rules", "group": null, "chapter": "chapter_act1", "linked_flag": "chores_explained", "is_milestone": false, "journal_entry": "She handed me a sponge and a bottle of cleaning spray. 'You're not a guest. If you're living here, you help out.' No apology, no softening. Her house, her terms. Fair enough.", "linked_canvas": "house_rules", "requires_group": null, "requires_nodes": ["arrival"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "first_breakfast", "npc": "npc_angela", "name": "First Breakfast Together", "group": "early_bonding", "chapter": "chapter_act1", "linked_flag": null, "is_milestone": false, "journal_entry": "She makes coffee every morning. I started showing up. We don't say much, but the silence has gotten comfortable.", "linked_canvas": "activity_breakfast_angela", "requires_group": null, "requires_nodes": ["house_rules"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "accidental_glimpse", "npc": "npc_angela", "name": "The Accidental Glimpse", "group": null, "chapter": "chapter_act1", "linked_flag": "peek_unlocked", "is_milestone": false, "journal_entry": "I didn't mean to look. The door was cracked, the steam, the sound of water \u2014 and then I saw her. Just for a moment. But a moment was enough. I can't stop thinking about it.", "linked_canvas": "accidental_glimpse", "requires_group": null, "requires_nodes": ["first_breakfast"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "first_peek", "npc": "npc_angela", "name": "Returning to Watch", "group": "early_bonding", "chapter": "chapter_act1", "linked_flag": null, "is_milestone": false, "journal_entry": "I went back to the bathroom door. This time it wasn't an accident. I knew what I was doing. I watched her through the steam and told myself it was the last time. It wasn't.", "linked_canvas": "activity_bath_angela", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "first_movie", "npc": "npc_angela", "name": "Movie Night", "group": "early_bonding", "chapter": "chapter_act1", "linked_flag": null, "is_milestone": false, "journal_entry": "We watched a movie together. She sat close. Our legs touched. Neither of us moved.", "linked_canvas": "activity_movie_night_angela", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "the_bills", "npc": "npc_angela", "name": "The Bills", "group": null, "chapter": "chapter_act1", "linked_flag": "bills_discovered", "is_milestone": false, "journal_entry": "I found her at the counter with a laptop and overdue notices. She closed it fast. 'Just bills.' But I saw enough. She's been covering everything alone.", "linked_canvas": "angelas_struggle", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "the_job", "npc": "npc_angela", "name": "The Job", "group": null, "chapter": "chapter_act1", "linked_flag": "job_started", "is_milestone": false, "journal_entry": "I got a job at the cafe down the street. When I told her, she paused mid-cooking. 'You didn't have to do that.' 'I wanted to.' She went back to cooking, but she was smiling.", "linked_canvas": "jack_gets_hired", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "her_routine", "npc": "npc_angela", "name": "Her Routine", "group": null, "chapter": "chapter_act1", "linked_flag": "routine_noticed", "is_milestone": false, "journal_entry": "Came home from a shift. She was on the couch \u2014 wine, book, bare feet. She didn't hear me come in. She put a plate in the oven for me. She didn't have to.", "linked_canvas": "angelas_evening", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "first_rent", "npc": "npc_angela", "name": "First Rent", "group": null, "chapter": "chapter_act1", "linked_flag": "first_rent_paid", "is_milestone": false, "journal_entry": "I paid my first rent today. $200. She looked at me differently after. Not like a guest. Like someone who belongs here.", "linked_canvas": "first_rent_day", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "cant_sleep", "npc": "npc_angela", "name": "Can't Sleep", "group": null, "chapter": "chapter_act1", "linked_flag": "late_night_talk", "is_milestone": false, "journal_entry": "2 AM. Both of us in the kitchen. She told me about the divorce. 'People leave. That's what I learned.' I didn't have an answer. I just stayed. At the door she said, 'Jack? I'm glad you're here.'", "linked_canvas": "late_night_kitchen", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "towel_moment", "npc": "npc_angela", "name": "The Towel Encounter", "group": null, "chapter": "chapter_act2", "linked_flag": "towel_encounter_complete", "is_milestone": true, "journal_entry": "I walked in on her in the bathroom. She wasn't angry. She smiled. 'Knock next time, Jack.' But she didn't look away either.", "linked_canvas": "towel_encounter", "requires_group": null, "requires_nodes": ["arrival"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "first_kiss", "npc": "npc_angela", "name": "The First Kiss", "group": null, "chapter": "chapter_act2", "linked_flag": "first_kiss_complete", "is_milestone": true, "journal_entry": "She said the bathroom didn't embarrass her. 'That's what scares me.' Then I kissed her. Or she kissed me. I don't know who moved first. It doesn't matter. Everything before this was pretending.", "linked_canvas": "first_kiss", "requires_group": null, "requires_nodes": ["towel_moment"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "first_massage", "npc": "npc_angela", "name": "She Let Him In", "group": "mid_bonding", "chapter": "chapter_act2", "linked_flag": "massage_offered", "is_milestone": true, "journal_entry": "She'd been distant for days. Cold mornings, short answers, eyes that wouldn't meet mine. Tonight I found her on the couch \u2014 bills everywhere, neck tight, everything falling apart. I didn't say a word about the kiss. I just sat down and pressed my thumb into the knot. She tensed. Then she let go. 'I've been weird. I know. I got scared.' She offered massage oils in the bedroom. Not casually \u2014 deliberately. She was choosing to let me back in.", "linked_canvas": "massage_offer", "requires_group": null, "requires_nodes": ["first_kiss"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "deep_talk", "npc": "npc_angela", "name": "Opening Up", "group": "mid_bonding", "chapter": "chapter_act2", "linked_flag": null, "is_milestone": false, "journal_entry": "We talked. Really talked. About her life alone, about my father, about what happens when two people share a kitchen every morning. She said things I don't think she's told anyone.", "linked_canvas": "activity_deep_conversation_angela", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "spa_night", "npc": "npc_angela", "name": "The Massage", "group": "mid_bonding", "chapter": "chapter_act2", "linked_flag": null, "is_milestone": false, "journal_entry": "She kept her promise. Oils, candles, towels laid out on the bed. 'Lie down,' she said \u2014 then caught herself. 'I mean, sit. Or lie down. Whatever's comfortable.' Her hands were warm and steady. Neither of us said much. We didn't need to.", "linked_canvas": "activity_spa_massage_angela", "requires_group": null, "requires_nodes": ["first_massage"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "bath_invitation", "npc": "npc_angela", "name": "She Noticed", "group": "mid_bonding", "chapter": "chapter_act2", "linked_flag": null, "is_milestone": true, "journal_entry": "She caught me watching. I expected anger, embarrassment, a closed door. Instead she smiled and said 'come here.' She'd known all along. And she didn't mind.", "linked_canvas": "event_bath_invitation", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "date_proposal", "npc": "npc_angela", "name": "Somewhere Else", "group": null, "chapter": "chapter_act3", "linked_flag": "date_proposed", "is_milestone": true, "journal_entry": "She asked me to take her somewhere. Somewhere nobody knows us. Three hundred dollars and a hotel room downtown. She said 'when you're ready, I'll be ready.' I've never wanted to earn money so badly.", "linked_canvas": "event_date_proposal", "requires_group": null, "requires_nodes": [], "linked_canvas_node": null, "trait_requirements": []}, {"id": "date_night", "npc": "npc_angela", "name": "Somewhere Nobody Knows Us", "group": null, "chapter": "chapter_turning", "linked_flag": "date_night_complete", "is_milestone": true, "journal_entry": "'Somewhere nobody knows us.' She said it like a confession. The hotel, the champagne, the first time. For one night we weren't step-mom and step-son. We were just two people in love.", "linked_canvas": "activity_date_night_hotel_angela", "requires_group": null, "requires_nodes": ["date_proposal"], "linked_canvas_node": null, "trait_requirements": []}, {"id": "exploring", "npc": "npc_angela", "name": "Trust Without Words", "group": null, "chapter": "chapter_resolution", "linked_flag": null, "is_milestone": false, "journal_entry": "She knelt on the floor in pink lingerie with a collar beside her. 'I want to try something. Only if you want to.' The deepest intimacy isn't physical \u2014 it's the willingness to be vulnerable.", "linked_canvas": "activity_exploring_kink_angela", "requires_group": null, "requires_nodes": ["date_night"], "linked_canvas_node": null, "trait_requirements": []}], "groups": [{"id": "early_bonding", "name": "Getting to Know Her", "description": "Share daily moments with Angela \u2014 breakfast, movies, quiet glimpses of her private world.", "required_count": 2}, {"id": "mid_bonding", "name": "Growing Connection", "description": "The distance between you narrows. Conversations deepen. Afternoons change meaning.", "required_count": 1}], "version": "1.0", "chapters": [{"id": "chapter_prologue", "mood": "hopeful", "name": "A New Arrangement", "order": 1, "description": "Jack arrives at Angela's apartment. They're polite strangers under one roof."}, {"id": "chapter_act1", "mood": "hopeful", "name": "The New Normal", "order": 2, "description": "Daily routines establish. Shared breakfasts, accidental glimpses, evening movies. The apartment begins to feel like something more than a temporary arrangement."}, {"id": "chapter_act2", "mood": "romantic", "name": "Growing Closer", "order": 3, "description": "The careful distance narrows. Tension builds through daily rituals. She starts to relax around him. He starts to see her as more than his step-mom."}, {"id": "chapter_turning", "mood": "passionate", "name": "The Breaking Point", "order": 4, "description": "Weeks of tension finally break. Everything they've been holding back comes to the surface."}, {"id": "chapter_act3", "mood": "passionate", "name": "Deep Connection", "order": 5, "description": "All pretense is gone. Every moment together is charged with something real. Special nights, shared mornings, and a love that neither of them expected."}, {"id": "chapter_resolution", "mood": "peaceful", "name": "What We Became", "order": 6, "description": "The story concludes. What began as a practical living arrangement became something neither of them could have predicted."}], "emotion_mappings": {"love": {"ranges": [{"max": 20, "min": 0, "label": "strangers", "description": "Angela is polite but distant. You're a guest in her home, nothing more."}, {"max": 40, "min": 21, "label": "warming", "description": "Angela's smiles come easier now. She lingers at breakfast. Something is shifting."}, {"max": 60, "min": 41, "label": "attraction", "description": "Angela watches you when she thinks you aren't looking. The air between you is charged."}, {"max": 80, "min": 61, "label": "intimate", "description": "Angela reaches for you. Her touches are deliberate, her eyes full of want. The pretense is gone."}, {"max": 100, "min": 81, "label": "in love", "description": "Angela loves you. It shows in everything \u2014 the way she says your name, the way she holds you, the way she chose this."}], "default_npc": null, "trait_owner": "npc"}, "trust": {"ranges": [{"max": 10, "min": 0, "label": "guarded", "description": "Angela has her walls up. She's watching, measuring. You haven't earned her trust yet."}, {"max": 20, "min": 11, "label": "cautious", "description": "Angela is opening up, but carefully. She tests the water before she wades in."}, {"max": 30, "min": 21, "label": "trusting", "description": "Angela confides in you. She lets you see her when she's not performing strength."}, {"max": 40, "min": 31, "label": "safe", "description": "Angela feels safe with you. She sleeps soundly when you're beside her."}, {"max": 50, "min": 41, "label": "complete", "description": "Angela trusts you completely. 'I didn't expect this. Any of this.' But she's glad."}], "default_npc": null, "trait_owner": "npc"}}}; // NPC ID resolver: converts slugs to UUIDs for $npcs lookups // Conditions from TOML use NPC slugs (e.g., "elena") but $npcs is keyed by UUID setup.resolveNpcId = function(idOrSlug) { if (!idOrSlug) return null; var slugMap = setup.npc_slug_map || {}; var key = String(idOrSlug); // If it's a slug, resolve to UUID; otherwise return as-is (already UUID) return slugMap[key] || key; }; // Time period calculation function setup.getTimePeriod = function(hour) { if (hour >= 6 && hour < 12) return "Morning"; if (hour >= 12 && hour < 18) return "Afternoon"; if (hour >= 18 && hour < 22) return "Evening"; return "Night"; }; // Schedule evaluation function for trigger conditionals // Accepts array of schedule objects, returns true if ANY schedule is active (OR logic) setup.isScheduleActive = function(scheduleArray) { // Empty/null = always active (no schedule restriction) if (!scheduleArray || scheduleArray.length === 0) return true; const timeState = State.variables.game_state.time_state; const dayIndex = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].indexOf(timeState.current_day); const currentHour = timeState.current_hour; const currentMinute = timeState.current_minute; const currentTotal = (currentHour * 60) + currentMinute; // Check each schedule - return true if ANY matches (OR logic) for (var i = 0; i < scheduleArray.length; i++) { var sched = scheduleArray[i]; var weekdays = sched.weekdays || []; var startTime = sched.startTime; var endTime = sched.endTime; // Check day (empty weekdays = any day) if (weekdays.length > 0 && !weekdays.includes(dayIndex)) continue; // Parse start time var startParts = startTime.split(':'); var startTotal = parseInt(startParts[0]) * 60 + parseInt(startParts[1]); if (endTime) { // Range trigger var endParts = endTime.split(':'); var endTotal = parseInt(endParts[0]) * 60 + parseInt(endParts[1]); // Handle overnight schedules (e.g., 22:00 to 08:00) if (endTotal < startTotal) { if (currentTotal >= startTotal || currentTotal < endTotal) return true; } else { if (currentTotal >= startTotal && currentTotal < endTotal) return true; } } else { // Point trigger - active for 1 hour window if (currentTotal >= startTotal && currentTotal < startTotal + 60) return true; } } return false; }; // ===== NPC Schedule Helper Functions ===== // Schedules are derived dynamically from setup.help_data.locationCanvases at runtime. // This ensures non-repeatable canvases disappear after being played and // condition-gated canvases only appear when conditions are met. // Build reverse map: location UUID → TOML slug (cached per passage render) setup._getLocUuidToSlug = function() { if (setup._locUuidToSlugCache) return setup._locUuidToSlugCache; var locs = setup.locations || {}; var map = {}; for (var slug in locs) { if (locs[slug] && locs[slug].id) { map[String(locs[slug].id)] = slug; } } setup._locUuidToSlugCache = map; return map; }; // Build reverse map: NPC UUID → slug (cached) setup._getNpcUuidToSlug = function() { if (setup._npcUuidToSlugCache) return setup._npcUuidToSlugCache; var slugMap = setup.npc_slug_map || {}; var map = {}; for (var slug in slugMap) { map[String(slugMap[slug])] = slug; } setup._npcUuidToSlugCache = map; return map; }; // Check if a canvas is available (conditions + completion) WITHOUT checking time. // Used by schedule functions that need all time windows, not just currently active ones. setup._isCanvasAvailable = function(c) { try { // Check conditions if present if (c.conditions && !setup.triggerConditionsSatisfied(c.conditions)) { return false; } // Check repeatability (non-repeatable and already triggered ever) var hist = State.variables.game_state.trigger_history || {}; var rec = hist[String(c.id)]; if (!c.isRepeatable && rec && (rec.total || 0) >= 1) { return false; } return true; } catch (e) { return false; } }; // Core: Get available schedule entries for an NPC from canvas triggers // Checks conditions + completion but NOT current time (returns all time windows) // Returns array of {location, start_time, end_time, weekdays, activity} setup.getNpcScheduleFromCanvases = function(npcSlug) { try { var sv = State.variables; var helpData = setup.help_data || {}; var locationCanvases = helpData.locationCanvases || {}; var locUuidToSlug = setup._getLocUuidToSlug(); var result = []; var seen = {}; // Dedup key: "locSlug|startTime|endTime" for (var locUuid in locationCanvases) { var canvasList = locationCanvases[locUuid]; var locSlug = locUuidToSlug[locUuid]; if (!locSlug) continue; for (var i = 0; i < canvasList.length; i++) { var c = canvasList[i]; // Only canvases linked to this NPC if (c.npcId !== npcSlug) continue; // Must have schedules if (!c.hasSchedules || !c.scheduleParams || c.scheduleParams.length === 0) continue; // Check conditions + completion (but NOT time — we want all windows) if (!setup._isCanvasAvailable(c)) continue; // Extract schedule entries for (var s = 0; s < c.scheduleParams.length; s++) { var sp = c.scheduleParams[s]; var startTime = sp.startTime || "00:00"; var endTime = sp.endTime || null; var weekdays = sp.weekdays || []; // Dedup: same location + same time window → keep first var dedupKey = locSlug + "|" + startTime + "|" + endTime + "|" + weekdays.join(","); if (seen[dedupKey]) continue; seen[dedupKey] = true; result.push({ location: locSlug, start_time: startTime, end_time: endTime, weekdays: weekdays, activity: c.displayName || c.name || "" }); } } } return result; } catch (e) { return []; } }; // Get the current location of an NPC based on game time (dynamic) setup.getNpcLocation = function(npcId) { try { var resolvedId = setup.resolveNpcId(npcId); var uuidToSlug = setup._getNpcUuidToSlug(); var npcSlug = uuidToSlug[resolvedId]; if (!npcSlug) return null; var entries = setup.getNpcScheduleFromCanvases(npcSlug); if (entries.length === 0) return null; // Find first entry whose time window is currently active for (var i = 0; i < entries.length; i++) { var sch = entries[i]; if (setup.isCurrentTimeSlot(sch.start_time, sch.end_time)) { return { location: sch.location, activity: sch.activity || "" }; } } return null; } catch (e) { return null; } }; // Get schedule entries for a specific NPC on a specific day (dynamic) setup.getNpcDaySchedule = function(npcId, dayIndex) { try { var resolvedId = setup.resolveNpcId(npcId); var uuidToSlug = setup._getNpcUuidToSlug(); var npcSlug = uuidToSlug[resolvedId]; if (!npcSlug) return []; var entries = setup.getNpcScheduleFromCanvases(npcSlug); var result = []; for (var i = 0; i < entries.length; i++) { var sch = entries[i]; // Empty weekdays = all days if (!sch.weekdays || sch.weekdays.length === 0 || sch.weekdays.includes(dayIndex)) { result.push({ location: sch.location, start_time: sch.start_time || "00:00", end_time: sch.end_time || null, activity: sch.activity || "" }); } } return result; } catch (e) { return []; } }; // Get all NPCs with available schedule entries (dynamic) setup.getNpcsWithSchedules = function() { try { var sv = State.variables; var helpData = setup.help_data || {}; var locationCanvases = helpData.locationCanvases || {}; var npcs = sv.npcs || {}; var slugMap = setup.npc_slug_map || {}; var found = {}; // npcSlug → true var result = []; // Scan all location canvases for NPC-linked entries with schedules for (var locUuid in locationCanvases) { var canvasList = locationCanvases[locUuid]; for (var i = 0; i < canvasList.length; i++) { var c = canvasList[i]; if (!c.npcId || !c.hasSchedules) continue; if (found[c.npcId]) continue; // Check conditions + completion (NOT time — schedule button should show if NPC has any available canvases) if (!setup._isCanvasAvailable(c)) continue; found[c.npcId] = true; } } // Resolve slugs to UUIDs and build result for (var npcSlug in found) { var npcUuid = slugMap[npcSlug]; if (!npcUuid || !npcs[npcUuid]) continue; result.push({ id: npcUuid, name: npcs[npcUuid].name }); } return result; } catch (e) { return []; } }; // Get today's schedule sorted by start time setup.getTodayScheduleSorted = function(npcId) { var timeState = State.variables.game_state.time_state; var dayIndex = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] .indexOf(timeState.current_day); var schedules = setup.getNpcDaySchedule(npcId, dayIndex); // Sort by start time schedules.sort(function(a, b) { var aParts = a.start_time.split(':'); var bParts = b.start_time.split(':'); var aMinutes = parseInt(aParts[0]) * 60 + parseInt(aParts[1]); var bMinutes = parseInt(bParts[0]) * 60 + parseInt(bParts[1]); return aMinutes - bMinutes; }); return schedules; }; // Check if current game time is within a time slot setup.isCurrentTimeSlot = function(startTime, endTime) { var timeState = State.variables.game_state.time_state; var currentTotal = (timeState.current_hour * 60) + timeState.current_minute; var startParts = startTime.split(':'); var startTotal = parseInt(startParts[0]) * 60 + parseInt(startParts[1]); var endTotal; if (endTime) { var endParts = endTime.split(':'); endTotal = parseInt(endParts[0]) * 60 + parseInt(endParts[1]); } else { endTotal = startTotal + 60; } // Handle overnight (e.g., 22:00-06:00) if (endTotal < startTotal) { return currentTotal >= startTotal || currentTotal < endTotal; } return currentTotal >= startTotal && currentTotal < endTotal; }; // Format hour:minute to readable time (e.g., "10:30 AM") setup.formatTime = function(hour, minute) { var period = hour >= 12 ? 'PM' : 'AM'; var displayHour = hour % 12 || 12; var displayMinute = minute < 10 ? '0' + minute : minute; return displayHour + ':' + displayMinute + ' ' + period; }; // ===== Trigger Conditions Evaluator ===== // Evaluates v1.0 trigger conditions against current game state (flags/traits) setup.triggerConditionsSatisfied = function(conditions) { try { // Treat missing/empty conditions as satisfied if (!conditions || typeof conditions !== 'object') return true; if (!conditions.version || conditions.version !== '1.0') return true; var items = Array.isArray(conditions.items) ? conditions.items : []; if (items.length === 0) return true; var logic = (conditions.logic === 'OR') ? 'OR' : 'AND'; var sv = State.variables || {}; var player = sv.player || {}; var npcs = sv.npcs || {}; function hasKey(obj, key) { if (!obj || typeof obj !== 'object') return false; return Object.prototype.hasOwnProperty.call(obj, String(key)); } function coerceNumber(v) { var n = Number(v); return isNaN(n) ? null : n; } function compare(op, left, right) { if (op === 'eq') return left === right; if (op === 'ne') return left !== right; if (op === 'gt' || op === 'gte' || op === 'lt' || op === 'lte') { var lnum = coerceNumber(left); var rnum = coerceNumber(right); if (lnum === null || rnum === null) return false; if (op === 'gt') return lnum > rnum; if (op === 'gte') return lnum >= rnum; if (op === 'lt') return lnum < rnum; if (op === 'lte') return lnum <= rnum; } if (op === 'in') { if (!Array.isArray(right)) return false; return right.includes(left); } if (op === 'not_in') { if (!Array.isArray(right)) return false; return !right.includes(left); } if (op === 'contains') { if (Array.isArray(left)) return left.includes(right); if (typeof left === 'string') return String(left).includes(String(right)); return false; } if (op === 'not_contains') { if (Array.isArray(left)) return !left.includes(right); if (typeof left === 'string') return !String(left).includes(String(right)); return false; } if (op === 'exists') return (left !== undefined && left !== null); if (op === 'not_exists') return (left === undefined || left === null); return false; } var results = []; for (var i = 0; i < items.length; i++) { var it = items[i]; if (!it || typeof it !== 'object') { results.push(false); continue; } var type = it.type; var subject = it.subject; var satisfied = false; if (type === 'flag') { var key = it.flag_key; var op = it.operator; if (!key || (subject !== 'player' && subject !== 'npc')) { results.push(false); continue; } if (subject === 'player') { // Use global $flags for player flags (sv.flags = State.variables.flags) var flags = (sv.flags || {}); if (op === 'exists') { satisfied = hasKey(flags, key); } else if (op === 'is_true') { satisfied = (flags[String(key)] === true); } else if (op === 'is_false') { // Treat missing or strictly false as false var v = flags[String(key)]; satisfied = (v === false || v === undefined); } else { satisfied = false; } } else if (subject === 'npc') { var rawNpcId = it.npc_id || it.character_id || ''; var npcId = setup.resolveNpcId(rawNpcId); var npc = npcId ? (npcs[npcId] || null) : null; var flagsNpc = npc && npc.flags ? npc.flags : {}; if (op === 'exists') { satisfied = hasKey(flagsNpc, key); } else if (op === 'is_true') { satisfied = (flagsNpc[String(key)] === true); } else if (op === 'is_false') { var v2 = flagsNpc[String(key)]; satisfied = (v2 === false || v2 === undefined); } else { satisfied = false; } } results.push(satisfied); continue; } if (type === 'trait') { var tkey = it.trait_key; var top = it.operator; if (!tkey || (subject !== 'player' && subject !== 'npc')) { results.push(false); continue; } var leftVal = null; if (subject === 'player') { leftVal = (player.core_traits || {})[String(tkey)]; } else if (subject === 'npc') { var rawNpc2Id = it.npc_id || it.character_id || ''; var npc2Id = setup.resolveNpcId(rawNpc2Id); var npc2 = npc2Id ? (npcs[npc2Id] || null) : null; leftVal = npc2 && npc2.core_traits ? npc2.core_traits[String(tkey)] : undefined; } var rightVal = it.value; satisfied = compare(top, leftVal, rightVal); results.push(satisfied); continue; } // days_since_flag: Check how many days have passed since a flag was set if (type === 'days_since_flag') { var flagKey = it.flag_key; var dsOp = it.operator; var requiredDays = it.value; if (!flagKey) { results.push(false); continue; } var flagValue = false; var setDay = null; if (subject === 'player' || !subject) { flagValue = (sv.flags || {})[String(flagKey)]; var meta = (sv.flags_meta || {})[String(flagKey)]; setDay = meta ? meta.set_day : null; } else if (subject === 'npc') { var rawNpc3Id = it.npc_id || it.character_id || ''; var npc3Id = setup.resolveNpcId(rawNpc3Id); var npc3 = npc3Id ? (npcs[npc3Id] || null) : null; if (npc3) { flagValue = (npc3.flags || {})[String(flagKey)]; var npcMeta = (npc3.flags_meta || {})[String(flagKey)]; setDay = npcMeta ? npcMeta.set_day : null; } } // If flag not set or no metadata, condition fails if (!flagValue || setDay === null) { satisfied = false; } else { var currentDay = (sv.game_state && sv.game_state.time_state) ? sv.game_state.time_state.day : 1; var daysSince = currentDay - setDay; satisfied = compare(dsOp, daysSince, requiredDays); } results.push(satisfied); continue; } // Unknown type results.push(false); } if (results.length === 0) return false; if (logic === 'AND') return results.every(function(x) { return !!x; }); return results.some(function(x) { return !!x; }); } catch (e) { // Fail open to avoid breaking gameplay return true; } }; // ===== Trigger Repeatability & Limits ===== // Utility to build a unique day key combining week and day setup.getCurrentDayKey = function() { try { var ts = State.variables.game_state.time_state; return String(ts.current_week) + ':' + String(ts.current_day); } catch (e) { return '0:Monday'; } }; // Check if a canvas can trigger based on repeatability and per-day limit setup.canTriggerCanvas = function(canvasId, isRepeatable, maxPerDay) { try { var sv = State.variables; sv.game_state = sv.game_state || {}; var hist = sv.game_state.trigger_history = sv.game_state.trigger_history || {}; var rec = hist[String(canvasId)] || null; if (!rec) { // Never triggered before; allowed return true; } // Not repeatable and already triggered once if (!isRepeatable && (rec.total || 0) >= 1) { return false; } // Per-day limit check if (maxPerDay !== null && maxPerDay !== undefined) { var currentDayKey = setup.getCurrentDayKey(); var dayKey = rec.dayKey || ''; var dayCount = rec.dayCount || 0; if (dayKey !== currentDayKey) { // New day; reset dayCount logic allows trigger return true; } if (dayCount >= Number(maxPerDay)) { return false; } } return true; } catch (e) { // Fail open return true; } }; // Check if an activity can trigger based on shared per-day limit across all tiers // All tiers of the same activity name share the same daily limit setup.canTriggerActivity = function(activityName, maxPerDay) { try { // No limit set - always allowed if (maxPerDay === null || maxPerDay === undefined) { return true; } var sv = State.variables; sv.game_state = sv.game_state || {}; var actHist = sv.game_state.activity_trigger_history = sv.game_state.activity_trigger_history || {}; var rec = actHist[String(activityName)] || null; if (!rec) { // Never triggered before; allowed return true; } var currentDayKey = setup.getCurrentDayKey(); if (rec.dayKey !== currentDayKey) { // New day; allowed return true; } // Check daily limit if ((rec.dayCount || 0) >= Number(maxPerDay)) { return false; } return true; } catch (e) { return true; // Fail open } }; // Mark an activity as triggered: increments per-day counter at activity name level setup.markActivityTriggered = function(activityName) { try { var sv = State.variables; sv.game_state = sv.game_state || {}; var actHist = sv.game_state.activity_trigger_history = sv.game_state.activity_trigger_history || {}; var key = String(activityName); var rec = actHist[key] || { dayKey: '', dayCount: 0 }; var currentDayKey = setup.getCurrentDayKey(); if (rec.dayKey !== currentDayKey) { rec.dayKey = currentDayKey; rec.dayCount = 0; } rec.dayCount = (rec.dayCount || 0) + 1; actHist[key] = rec; } catch (e) { // ignore } }; // Mark a canvas as triggered: increments total and per-day counters // Also tracks at activity level for shared daily limits across tiers setup.markCanvasTriggered = function(canvasId) { try { var sv = State.variables; sv.game_state = sv.game_state || {}; var hist = sv.game_state.trigger_history = sv.game_state.trigger_history || {}; var key = String(canvasId); var rec = hist[key] || { total: 0, dayKey: '', dayCount: 0 }; var currentDayKey = setup.getCurrentDayKey(); if (rec.dayKey !== currentDayKey) { rec.dayKey = currentDayKey; rec.dayCount = 0; } rec.total = (rec.total || 0) + 1; rec.dayCount = (rec.dayCount || 0) + 1; hist[key] = rec; // Also track at activity level (all tiers share same daily limit) var helpData = setup.help_data || {}; var canvasNameMap = helpData.canvasIdToActivityName || {}; var activityName = canvasNameMap[key]; if (activityName) { setup.markActivityTriggered(activityName); } } catch (e) { // ignore } }; // Check if a canvas has never been completed (for highlighting new content) setup.isCanvasNew = function(canvasId) { try { var sv = State.variables; sv.game_state = sv.game_state || {}; var hist = sv.game_state.trigger_history || {}; var record = hist[String(canvasId)]; return !record || (record.total || 0) === 0; } catch (e) { return true; // Fail open - show as new if error } }; // Check if a location has any unvisited AND available canvases (for navigation indicators) // Uses selectCanvasByPriority to respect activity-level daily limits setup.locationHasNewCanvases = function(locationId) { try { var sv = State.variables; var helpData = setup.help_data || {}; var locationCanvases = helpData.locationCanvases || {}; var canvasList = locationCanvases[String(locationId)] || []; // Use selectCanvasByPriority to get ACTUALLY available canvases // This respects activity-level daily limits (all tiers share same limit) var availableCanvases = setup.selectCanvasByPriority(canvasList); // Check if any available canvas is new (unvisited) for (var i = 0; i < availableCanvases.length; i++) { if (setup.isCanvasNew(availableCanvases[i].id)) { return true; } } return false; } catch (e) { return false; // Fail closed - don't show indicator if error } }; // ===== Conditional Choice Unlock Tracking ===== // Check if a specific conditional choice has been visited setup.isChoiceVisited = function(choiceKey) { try { return !!State.variables.game_state.visited_choices[String(choiceKey)]; } catch (e) { return false; } }; // Mark a conditional choice as visited (called when player clicks it) setup.markChoiceVisited = function(choiceKey) { try { var vc = State.variables.game_state.visited_choices = State.variables.game_state.visited_choices || {}; vc[String(choiceKey)] = true; } catch (e) { // ignore } }; // Check if a canvas has any unlocked + unvisited conditional choices setup.canvasHasNewUnlockedChoices = function(canvasId) { try { var cc = (setup.help_data || {}).canvasConditionalChoices || {}; var choices = cc[String(canvasId)]; if (!choices || choices.length === 0) return false; for (var i = 0; i < choices.length; i++) { if (setup.triggerConditionsSatisfied(choices[i].conditions) && !setup.isChoiceVisited(choices[i].key)) { return true; } } return false; } catch (e) { return false; } }; // Check if a location has any canvases with newly unlocked choices setup.locationHasNewUnlockedChoices = function(locationId) { try { var lc = (setup.help_data || {}).locationCanvases || {}; var canvasList = lc[String(locationId)] || []; var selected = setup.selectCanvasByPriority(canvasList); for (var i = 0; i < selected.length; i++) { if (setup.canvasHasNewUnlockedChoices(selected[i].id)) { return true; } } return false; } catch (e) { return false; } }; // ===== Priority-Based Canvas Selection ===== // Check if a canvas is valid (schedule, conditions, repeatability) setup.isCanvasValid = function(c) { try { // Check schedule if present if (c.hasSchedules && c.scheduleParams) { if (!setup.isScheduleActive(c.scheduleParams)) { return false; } } // Check conditions if present if (c.conditions && !setup.triggerConditionsSatisfied(c.conditions)) { return false; } // Check repeatability if (!setup.canTriggerCanvas(c.id, c.isRepeatable, c.maxPerDay)) { return false; } return true; } catch (e) { return false; } }; // Check if canvas is valid for tiered selection (schedule + conditions only) // Does NOT check per-canvas daily limit since daily limit is checked at activity level // Used by selectCanvasByPriority where all tiers share the same daily limit setup.isCanvasValidForSelection = function(c) { try { // Check schedule if present if (c.hasSchedules && c.scheduleParams) { if (!setup.isScheduleActive(c.scheduleParams)) { return false; } } // Check conditions if present if (c.conditions && !setup.triggerConditionsSatisfied(c.conditions)) { return false; } // Check repeatability (non-repeatable and already triggered ever) var hist = State.variables.game_state.trigger_history || {}; var rec = hist[String(c.id)]; if (!c.isRepeatable && rec && (rec.total || 0) >= 1) { return false; } return true; } catch (e) { return false; } }; // Get NPCs that have available canvases at a location // Returns array of {id, name, portrait} for NPCs with valid activities // Uses selectCanvasByPriority to respect activity-level daily limits setup.getNpcsWithCanvasesAtLocation = function(locationId) { var result = []; try { var sv = State.variables; var helpData = setup.help_data || {}; var locationCanvases = helpData.locationCanvases || {}; var canvasList = locationCanvases[String(locationId)] || []; var npcs = sv.npcs || {}; // Use selectCanvasByPriority to get ACTUALLY available canvases // This respects activity-level daily limits (all tiers share same limit) var availableCanvases = setup.selectCanvasByPriority(canvasList); var addedNpcs = {}; for (var i = 0; i < availableCanvases.length; i++) { var c = availableCanvases[i]; if (!c.npcId) continue; if (addedNpcs[c.npcId]) continue; // Resolve NPC slug to UUID var npcUuid = setup.npc_slug_map ? setup.npc_slug_map[c.npcId] : null; if (!npcUuid) continue; var npc = npcs[npcUuid]; if (!npc) continue; addedNpcs[c.npcId] = true; result.push({ id: npcUuid, name: npc.name, portrait: npc.portrait }); } } catch (e) { // Fail gracefully - return empty array } return result; }; // Select appropriate canvas per activity name with tiered progression logic // Returns array of selected canvases (one per unique activity name) // Logic: // 1. Check if activity was triggered today -> skip entire group (one per day) // 2. Find lowest priority unvisited tier -> return it (progression) // 3. If all visited -> return highest priority only (replay mode) setup.selectCanvasByPriority = function(canvasList) { try { if (!canvasList || canvasList.length === 0) return []; // Check if any canvas has priority > 0 (new tiered system) var hasPriorities = false; for (var i = 0; i < canvasList.length; i++) { if ((canvasList[i].priority || 0) > 0) { hasPriorities = true; break; } } // Backward compatibility: if no priorities set, return all valid canvases if (!hasPriorities) { var allValid = []; for (var j = 0; j < canvasList.length; j++) { if (setup.isCanvasValid(canvasList[j])) { allValid.push(canvasList[j]); } } return allValid; } // New tiered system: group by activity name var groups = {}; for (var k = 0; k < canvasList.length; k++) { var canvas = canvasList[k]; var name = canvas.name || canvas.id; if (!groups[name]) groups[name] = []; groups[name].push(canvas); } var selected = []; var groupNames = Object.keys(groups); for (var g = 0; g < groupNames.length; g++) { var activityName = groupNames[g]; var group = groups[activityName]; // Get maxPerDay from first tier (all tiers share same limit) var maxPerDay = group[0].maxPerDay; // STEP 1: Check if activity was already triggered today // All tiers share the same daily limit at the activity level if (!setup.canTriggerActivity(activityName, maxPerDay)) { continue; // Skip entire activity group } // STEP 2: Filter to canvases valid for selection (schedule + conditions) // Uses isCanvasValidForSelection which excludes per-canvas daily limit var validCanvases = []; for (var v = 0; v < group.length; v++) { if (setup.isCanvasValidForSelection(group[v])) { validCanvases.push(group[v]); } } if (validCanvases.length === 0) continue; // STEP 3: Find unvisited tiers (never triggered ever) var unvisited = []; for (var u = 0; u < validCanvases.length; u++) { if (setup.isCanvasNew(validCanvases[u].id)) { unvisited.push(validCanvases[u]); } } if (unvisited.length > 0) { // STEP 4a: Return LOWEST priority unvisited tier (progression) // Sort ascending so lowest priority comes first unvisited.sort(function(a, b) { return (a.priority || 0) - (b.priority || 0); }); selected.push(unvisited[0]); } else { // STEP 4b: All visited - return HIGHEST priority tier only (replay mode) // Sort descending so highest priority comes first validCanvases.sort(function(a, b) { return (b.priority || 0) - (a.priority || 0); }); selected.push(validCanvases[0]); } } return selected; } catch (e) { return []; } }; // Render canvas links for a location with priority-based selection // Returns HTML string with SugarCube-compatible links setup.renderLocationCanvases = function(locationId) { try { var sv = State.variables; var helpData = setup.help_data || {}; var locationCanvases = helpData.locationCanvases || {}; var canvasList = locationCanvases[String(locationId)] || []; if (canvasList.length === 0) return ''; // Use priority selection to get valid canvases var selected = setup.selectCanvasByPriority(canvasList); if (selected.length === 0) return ''; var html = ''; for (var i = 0; i < selected.length; i++) { var c = selected[i]; var displayName = c.displayName || c.name || 'Unnamed'; var passageName = c.passageName || ''; if (!passageName) continue; // Check if this canvas is new (unvisited) or has unlocked choices var isNew = setup.isCanvasNew(c.id); var hasUnlocked = !isNew && setup.canvasHasNewUnlockedChoices(c.id); // Generate SugarCube-compatible link (using data-passage attribute) if (isNew) { html += '<span class="new-canvas">'; } else if (hasUnlocked) { html += '<span class="unlocked-choice-canvas">'; } html += '<a class="link-internal" data-passage="' + passageName + '">' + displayName + '</a>'; if (isNew || hasUnlocked) { html += '</span>'; } html += '<br>'; } return html; } catch (e) { return ''; } }; // Time advancement functions window.advanceTime = function(minutes) { // Add minutes to current time State.variables.game_state.time_state.current_minute += minutes; // Handle minute/hour rollover while (State.variables.game_state.time_state.current_minute >= 60) { State.variables.game_state.time_state.current_minute -= 60; State.variables.game_state.time_state.current_hour += 1; } // Handle day rollover while (State.variables.game_state.time_state.current_hour >= 24) { State.variables.game_state.time_state.current_hour -= 24; advanceDay(); } // Update display updateTimeDisplay(); }; window.advanceDay = function() { const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; const currentIndex = days.indexOf(State.variables.game_state.time_state.current_day); const nextIndex = (currentIndex + 1) % 7; State.variables.game_state.time_state.current_day = days[nextIndex]; // Increment numeric day counter for days_since_flag conditions State.variables.game_state.time_state.day += 1; // New week starts on Monday if (nextIndex === 0) { State.variables.game_state.time_state.current_week += 1; } }; window.updateTimeDisplay = function() { // Format time for 12-hour display const hour = State.variables.game_state.time_state.current_hour; const minute = State.variables.game_state.time_state.current_minute; const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour; const ampm = hour < 12 ? 'AM' : 'PM'; const formattedTime = displayHour + ':' + (minute < 10 ? '0' : '') + minute + ' ' + ampm; // Update time display const timeElement = document.getElementById('time-display'); if (timeElement) { timeElement.textContent = formattedTime; } // Update current day const currentDayElement = document.getElementById('current-day'); if (currentDayElement) { currentDayElement.textContent = State.variables.game_state.time_state.current_day; } // Update day count (dev mode only - element may not exist) const dayCountElement = document.getElementById('day-count'); if (dayCountElement) { dayCountElement.textContent = 'Day ' + State.variables.game_state.time_state.day; } }; // ===== Trait Helpers ===== window._traitClamp = function(v, min, max) { v = Number(v); if (isNaN(v)) { v = 0; } if (min !== undefined && v < min) v = min; if (max !== undefined && v > max) v = max; return v; }; window.applyTraitEffect = function(targetType, npcId, trait, op, val, clampFlag, cap) { try { var sv = State.variables; if (!sv) return; // Resolve NPC slug to UUID if (targetType === 'npc' && npcId) { npcId = setup.resolveNpcId(npcId); } // Resolve target object var traitsObj = null; if (targetType === 'player') { sv.player = sv.player || {}; sv.player.core_traits = sv.player.core_traits || {}; traitsObj = sv.player.core_traits; } else if (targetType === 'npc') { sv.npcs = sv.npcs || {}; var npc = sv.npcs[String(npcId)] || null; if (!npc) return; // Unknown NPC npc.core_traits = npc.core_traits || {}; traitsObj = npc.core_traits; } else { return; // Unknown target type } // Normalize inputs var key = String(trait); var current = Number(traitsObj[key]); if (isNaN(current)) current = 0; var value = Number(val); if (isNaN(value)) value = 0; // Apply operation var next = current; if (op === 'add') { next = current + value; } else if (op === 'set') { next = value; } else { // Unknown op; do nothing return; } // Clamp 0-100 if requested (default true) if (clampFlag === undefined || clampFlag === null) { clampFlag = true; } if (clampFlag) { next = window._traitClamp(next, 0, 100); } // Apply cap if provided if (cap !== undefined && cap !== null) { var capNum = Number(cap); if (!isNaN(capNum)) { if (next > capNum) next = capNum; } } traitsObj[key] = next; } catch (e) { // Ignore to avoid breaking navigation } }; // ===== Flag Helpers ===== window.applyFlagEffect = function(targetType, npcId, flag) { try { var sv = State.variables; if (!sv) return; // Resolve NPC slug to UUID if (targetType === 'npc' && npcId) { npcId = setup.resolveNpcId(npcId); } var key = String(flag); // Get current day for tracking when flag was set var currentDay = (sv.game_state && sv.game_state.time_state) ? sv.game_state.time_state.day : 1; if (targetType === 'player') { // Store in $flags (sv.flags) to match triggerConditionsSatisfied checks sv.flags = sv.flags || {}; sv.flags[key] = true; // Track when flag was set for days_since_flag conditions sv.flags_meta = sv.flags_meta || {}; sv.flags_meta[key] = { set_day: currentDay }; return; } if (targetType === 'npc') { sv.npcs = sv.npcs || {}; var npc = sv.npcs[String(npcId)]; if (!npc) return; npc.flags = npc.flags || {}; npc.flags[key] = true; // Track when flag was set for days_since_flag conditions npc.flags_meta = npc.flags_meta || {}; npc.flags_meta[key] = { set_day: currentDay }; return; } } catch (e) { // ignore } }; // ========== EFFECT NOTIFICATION SYSTEM ========== // Pending effects to show setup.pendingEffects = []; // Get current trait value helper setup.getTraitValue = function(targetType, npcId, trait) { var sv = State.variables; // Resolve NPC slug to UUID if (targetType === 'npc' && npcId) { npcId = setup.resolveNpcId(npcId); } if (targetType === 'player') { return (sv.player && sv.player.core_traits) ? (sv.player.core_traits[trait] || 0) : 0; } else if (targetType === 'npc' && npcId) { var npc = sv.npcs ? sv.npcs[String(npcId)] : null; return (npc && npc.core_traits) ? (npc.core_traits[trait] || 0) : 0; } return 0; }; // Apply trait and queue notification setup.applyAndNotifyTrait = function(targetType, npcId, trait, op, val, clampFlag, cap) { var oldVal = setup.getTraitValue(targetType, npcId, trait); applyTraitEffect(targetType, npcId, trait, op, val, clampFlag, cap); var newVal = setup.getTraitValue(targetType, npcId, trait); var delta = newVal - oldVal; if (delta !== 0) { var npcName = ''; if (targetType === 'npc' && npcId) { npcId = setup.resolveNpcId(npcId); var npc = State.variables.npcs ? State.variables.npcs[String(npcId)] : null; npcName = npc ? (npc.name || npcId) : npcId; } setup.pendingEffects.push({ type: 'trait', name: npcName, trait: trait, delta: delta }); } }; // Apply flag and queue notification setup.applyAndNotifyFlag = function(targetType, npcId, flag) { // Resolve NPC slug to UUID if (targetType === 'npc' && npcId) { npcId = setup.resolveNpcId(npcId); } applyFlagEffect(targetType, npcId, flag); var npcName = ''; if (targetType === 'npc' && npcId) { var npc = State.variables.npcs ? State.variables.npcs[String(npcId)] : null; npcName = npc ? (npc.name || npcId) : npcId; } setup.pendingEffects.push({ type: 'flag', name: npcName, flag: flag }); }; // Show notification and clear setup.showEffectNotification = function() { var effects = setup.pendingEffects; if (!effects || effects.length === 0) return; var lines = []; for (var i = 0; i < effects.length; i++) { var eff = effects[i]; if (eff.type === 'trait') { var sign = eff.delta > 0 ? '+' : ''; var traitDisplay = eff.trait.charAt(0).toUpperCase() + eff.trait.slice(1); var prefix = eff.name ? (eff.name + "'s ") : ''; lines.push(sign + eff.delta + ' ' + prefix + traitDisplay); } else if (eff.type === 'flag') { var flagDisplay = eff.flag.replace(/_/g, ' '); flagDisplay = flagDisplay.charAt(0).toUpperCase() + flagDisplay.slice(1); lines.push('🔓 ' + flagDisplay); } } if (lines.length === 0) { setup.pendingEffects = []; return; } var html = '<div class="effect-toast">' + lines.join(' • ') + '</div>'; jQuery('body').append(html); setTimeout(function() { jQuery('.effect-toast').remove(); }, 2000); setup.pendingEffects = []; }; // ========== STORY JOURNAL SYSTEM ========== // Detect current story position based on story arc and game state setup.detectStoryPosition = function() { var arc = setup.story_arc || {}; var result = { current_chapter: null, completed_nodes: [], available_nodes: [], locked_nodes: [], active_groups: [], is_stuck: false, progress_percent: 0 }; if (!arc.nodes || arc.nodes.length === 0) { // Use auto-inference if no story arc defined return setup.autoInferStoryPosition(); } var completedIds = []; var availableIds = []; var lockedIds = []; // Check each node's completion status arc.nodes.forEach(function(node) { var isCompleted = false; // Check linked_flag first (flags is an object, not an array) if (node.linked_flag) { isCompleted = State.variables.flags && State.variables.flags[node.linked_flag]; } // Check linked_canvas via visited_nodes tracking if (node.linked_canvas && !isCompleted) { var visitedNodes = State.variables.game_state.visited_nodes || []; if (node.linked_canvas_node) { // Specific node target — check that exact node was visited var nodeKey = node.linked_canvas + "." + node.linked_canvas_node; isCompleted = visitedNodes.indexOf(nodeKey) !== -1; } else { // Any node in canvas — check if ANY visited_node starts with canvas slug var canvasPrefix = node.linked_canvas + "."; isCompleted = visitedNodes.some(function(vn) { return vn.indexOf(canvasPrefix) === 0; }); } } if (isCompleted) { completedIds.push(node.id); result.completed_nodes.push({ id: node.id, name: node.name, chapter: node.chapter, journal_entry: node.journal_entry, is_milestone: node.is_milestone || false }); } }); // Determine available vs locked nodes arc.nodes.forEach(function(node) { if (completedIds.indexOf(node.id) !== -1) return; // Already completed var canUnlock = true; // Check requires_nodes if (node.requires_nodes && node.requires_nodes.length > 0) { for (var i = 0; i < node.requires_nodes.length; i++) { if (completedIds.indexOf(node.requires_nodes[i]) === -1) { canUnlock = false; break; } } } // Check requires_group if (canUnlock && node.requires_group && arc.groups) { var group = arc.groups.find(function(g) { return g.id === node.requires_group; }); if (group) { var groupNodes = arc.nodes.filter(function(n) { return n.group === group.id; }); var completedCount = groupNodes.filter(function(n) { return completedIds.indexOf(n.id) !== -1; }).length; if (completedCount < group.required_count) { canUnlock = false; } } } if (canUnlock) { availableIds.push(node.id); result.available_nodes.push({ id: node.id, name: node.name, chapter: node.chapter, journal_entry: node.journal_entry, group: node.group || null }); } else { lockedIds.push(node.id); result.locked_nodes.push({ id: node.id, name: node.name, chapter: node.chapter, requires_group: node.requires_group || null, requires_nodes: node.requires_nodes || [] }); } }); // Build active groups status if (arc.groups) { arc.groups.forEach(function(group) { var groupNodes = arc.nodes.filter(function(n) { return n.group === group.id; }); var completedCount = groupNodes.filter(function(n) { return completedIds.indexOf(n.id) !== -1; }).length; result.active_groups.push({ id: group.id, name: group.name, description: group.description || "", completed: completedCount, required: group.required_count, total: groupNodes.length, isComplete: completedCount >= group.required_count }); }); } // Determine current chapter based on most recent milestone or available nodes if (arc.chapters && arc.chapters.length > 0) { var currentChapterId = null; // Find the chapter of the most recent milestone var milestones = result.completed_nodes.filter(function(n) { return n.is_milestone; }); if (milestones.length > 0) { currentChapterId = milestones[milestones.length - 1].chapter; } // Or use chapter of first available node if (!currentChapterId && result.available_nodes.length > 0) { currentChapterId = result.available_nodes[0].chapter; } // Or use first chapter if (!currentChapterId) { currentChapterId = arc.chapters[0].id; } result.current_chapter = arc.chapters.find(function(c) { return c.id === currentChapterId; }); } // Calculate progress var totalNodes = arc.nodes.length; if (totalNodes > 0) { result.progress_percent = Math.round((completedIds.length / totalNodes) * 100); } // Check if stuck (no available nodes and not all completed) result.is_stuck = result.available_nodes.length === 0 && result.completed_nodes.length < totalNodes; return result; }; // Get completed activities as "memories" for the journal setup.getCompletedActivities = function() { var position = setup.detectStoryPosition(); return position.completed_nodes.map(function(node) { return { name: node.name, journal_entry: node.journal_entry || "A moment to remember...", is_milestone: node.is_milestone }; }); }; // Interpret NPC state as emotional description instead of numbers setup.interpretNpcState = function(npcId) { var arc = setup.story_arc || {}; var mappings = arc.emotion_mappings || {}; var result = { npc_name: "", primary_emotion: "neutral", description: "Your relationship is developing...", relationship_summary: "", trait_interpretations: [] }; // Get NPC data - $npcs is an object keyed by UUID, not an array var npc = null; var npcs = State.variables.npcs || {}; // Try direct lookup first (npcId might already be a UUID) if (npcs[npcId]) { npc = npcs[npcId]; } else { // Try resolving as slug var resolvedId = setup.resolveNpcId(npcId); if (resolvedId && npcs[resolvedId]) { npc = npcs[resolvedId]; } } // Fallback to npc_states if available if (!npc && State.variables.npc_states) { npc = State.variables.npc_states[npcId]; } if (!npc) return result; result.npc_name = npc.name || npcId; var traits = npc.core_traits || {}; // Process each mapped trait var primaryEmotion = null; var highestPriority = -1; var traitOrder = Object.keys(mappings); traitOrder.forEach(function(traitName, priority) { var traitValue = traits[traitName]; if (typeof traitValue !== "number") return; var mapping = mappings[traitName]; if (!mapping || !mapping.ranges) return; // Find matching range var matchedRange = null; for (var i = 0; i < mapping.ranges.length; i++) { var range = mapping.ranges[i]; if (traitValue >= range.min && traitValue <= range.max) { matchedRange = range; break; } } if (matchedRange) { result.trait_interpretations.push({ trait: traitName, value: traitValue, label: matchedRange.label, description: matchedRange.description }); // Track primary emotion (highest value trait) if (traitValue > highestPriority) { highestPriority = traitValue; primaryEmotion = matchedRange.label; result.description = matchedRange.description; } } }); if (primaryEmotion) { result.primary_emotion = primaryEmotion; } // Generate relationship summary based on trait interpretations if (result.trait_interpretations.length > 0) { var summaryParts = result.trait_interpretations.slice(0, 2).map(function(t) { return t.description; }); result.relationship_summary = summaryParts.join(" "); } return result; }; // Generate subtle narrative hint (not mechanical instructions) setup.generateNarrativeHint = function() { var arc = setup.story_arc || {}; var hints = arc.guidance || arc.hints || {}; var position = setup.detectStoryPosition(); var result = { hint_type: "none", text: "" }; // Only show hints when stuck or making slow progress if (!position.is_stuck && position.available_nodes.length > 0) { return result; } // Check active groups for progress hints var incompleteGroup = position.active_groups.find(function(g) { return !g.isComplete && g.completed < g.required; }); if (incompleteGroup) { result.hint_type = "observation"; var remaining = incompleteGroup.required - incompleteGroup.completed; if (hints.group_incomplete) { result.text = hints.group_incomplete .replace("{group_name}", incompleteGroup.name) .replace("{remaining}", remaining); } else { result.text = "There might be more to explore..."; } return result; } // Check locked nodes for what might be possible if (position.locked_nodes.length > 0) { result.hint_type = "suggestion"; if (hints.progress_needed) { result.text = hints.progress_needed; } else { result.text = "Perhaps spending more time together would reveal new possibilities..."; } return result; } // All done if (position.progress_percent >= 100) { result.hint_type = "completion"; result.text = hints.story_complete || "Your story together has reached a beautiful conclusion."; } return result; }; // ============== QUEST PAGE HELPER FUNCTIONS ============== // Get the next activity for an NPC (first incomplete in node order) setup.getNextActivity = function(npcId) { var helpData = setup.help_data || {}; var npcData = helpData.npcs ? helpData.npcs[npcId] : null; if (!npcData) return null; var activities = npcData.activities || []; var flags = State.variables.flags || {}; var hist = (State.variables.game_state && State.variables.game_state.trigger_history) || {}; // Activities are in node order from story_arc for (var i = 0; i < activities.length; i++) { var activity = activities[i]; // Skip non-story-arc activities (repeatable canvases without node_id) if (!activity.node_id) continue; // Skip completed activities var isCompleted = false; // Check linked_flag if (activity.linked_flag && flags[activity.linked_flag]) { isCompleted = true; } // Check visited_nodes tracking (replaces dead completed_canvases) if (!isCompleted && activity.canvas_slug) { var visitedNodes = State.variables.game_state.visited_nodes || []; if (activity.linked_canvas_node) { // Specific node target var nodeKey = activity.canvas_slug + "." + activity.linked_canvas_node; isCompleted = visitedNodes.indexOf(nodeKey) !== -1; } else { // Any node in canvas var canvasPrefix = activity.canvas_slug + "."; isCompleted = visitedNodes.some(function(vn) { return vn.indexOf(canvasPrefix) === 0; }); } } if (isCompleted) continue; // Skip activities without a location (intro/automatic activities) if (!activity.location) continue; // Check if canvas trigger conditions are met var canvasConditions = activity.canvas_conditions; if (canvasConditions && canvasConditions.items) { var items = canvasConditions.items; var traitItems = []; var flagItems = []; var daysSinceFlagItems = []; // Separate trait, flag, and days_since_flag conditions for (var k = 0; k < items.length; k++) { if (items[k].type === 'trait') traitItems.push(items[k]); else if (items[k].type === 'flag') flagItems.push(items[k]); else if (items[k].type === 'days_since_flag') daysSinceFlagItems.push(items[k]); } // Check trait conditions FIRST (priority) var unmetTraits = []; for (var t = 0; t < traitItems.length; t++) { if (!setup.checkSingleCondition(traitItems[t])) { unmetTraits.push(traitItems[t]); } } if (unmetTraits.length > 0) { // Trait not met - show trait requirement return { activity: activity, isLocked: false, conditionsNotMet: true, traitConditionsNotMet: true, canvasConditions: { items: unmetTraits, logic: canvasConditions.logic, version: '1.0' } }; } // Traits met - check flags var unmetFlags = []; for (var f = 0; f < flagItems.length; f++) { if (!setup.checkSingleCondition(flagItems[f])) { unmetFlags.push(flagItems[f]); } } if (unmetFlags.length > 0) { // Flags not met - show actionable hint var flagHint = setup.getBestFlagHint(unmetFlags); return { activity: activity, isLocked: false, conditionsNotMet: true, flagConditionsNotMet: true, flagHint: flagHint }; } // Flags met - check days_since_flag conditions var unmetDaysConditions = []; for (var d = 0; d < daysSinceFlagItems.length; d++) { if (!setup.checkSingleCondition(daysSinceFlagItems[d])) { unmetDaysConditions.push(daysSinceFlagItems[d]); } } if (unmetDaysConditions.length > 0) { // Days condition not met - check if actually need to wait var daysRemaining = setup.calculateDaysRemaining(unmetDaysConditions[0]); if (daysRemaining > 0) { // Still need to wait - show wait message return { activity: activity, isLocked: false, conditionsNotMet: true, daysConditionsNotMet: true, daysRemaining: daysRemaining }; } // daysRemaining is 0 - condition effectively met, fall through } } // Check node-level conditions (for linked_canvas_node targeting) var nodeConditions = activity.node_conditions; if (nodeConditions && nodeConditions.items) { var nodeItems = nodeConditions.items; var unmetNodeTraits = []; var unmetNodeFlags = []; for (var nc = 0; nc < nodeItems.length; nc++) { if (!setup.checkSingleCondition(nodeItems[nc])) { if (nodeItems[nc].type === 'trait') unmetNodeTraits.push(nodeItems[nc]); else if (nodeItems[nc].type === 'flag') unmetNodeFlags.push(nodeItems[nc]); } } if (unmetNodeTraits.length > 0) { return { activity: activity, isLocked: false, conditionsNotMet: true, nodeConditionsNotMet: true, traitConditionsNotMet: true, canvasConditions: { items: unmetNodeTraits, logic: nodeConditions.logic, version: '1.0' } }; } if (unmetNodeFlags.length > 0) { var nodeFlagHint = setup.getBestFlagHint(unmetNodeFlags); return { activity: activity, isLocked: false, conditionsNotMet: true, nodeConditionsNotMet: true, flagConditionsNotMet: true, flagHint: nodeFlagHint }; } } // Found next visitable activity - check if locked by traits var traitReqs = activity.trait_requirements || []; var missingTraits = []; for (var j = 0; j < traitReqs.length; j++) { if (!setup.checkTraitRequirement(traitReqs[j])) { missingTraits.push(traitReqs[j]); } } return { activity: activity, isLocked: missingTraits.length > 0, missingTraits: missingTraits, conditionsNotMet: false }; } return null; // All completed }; // Check if a trait requirement is met setup.checkTraitRequirement = function(req) { var player = State.variables.player || {}; var traits = player.traits || {}; var currentValue = traits[req.trait] || 0; switch (req.operator) { case ">": return currentValue > req.value; case ">=": return currentValue >= req.value; case "<": return currentValue < req.value; case "<=": return currentValue <= req.value; case "==": return currentValue === req.value; default: return currentValue >= req.value; } }; // Check a single condition item (trait or flag) setup.checkSingleCondition = function(item) { var sv = State.variables || {}; if (item.type === 'flag') { var flags = sv.flags || {}; if (item.subject === 'player') { if (item.operator === 'is_true') return flags[item.flag_key] === true; if (item.operator === 'is_false') return !flags[item.flag_key]; } return false; } if (item.type === 'trait') { var leftVal = null; if (item.subject === 'player') { leftVal = ((sv.player || {}).core_traits || {})[item.trait_key]; } else if (item.subject === 'npc') { var npcId = setup.resolveNpcId(item.npc_id || ''); var npc = npcId ? ((sv.npcs || {})[npcId] || null) : null; leftVal = npc && npc.core_traits ? npc.core_traits[item.trait_key] : undefined; } var rightVal = item.value; var op = item.operator; if (op === 'gte') return Number(leftVal) >= Number(rightVal); if (op === 'gt') return Number(leftVal) > Number(rightVal); if (op === 'lte') return Number(leftVal) <= Number(rightVal); if (op === 'lt') return Number(leftVal) < Number(rightVal); if (op === 'eq') return leftVal === rightVal; return false; } return false; }; // Find actionable hint for unmet flag conditions (with recursive chain resolution) setup.getBestFlagHint = function(unmetFlags) { var helpData = setup.help_data || {}; var flagUnlockMap = helpData.flag_unlock_map || {}; var startingCanvasId = helpData.starting_canvas_id; // First pass: find directly visitable unlock canvas for (var i = 0; i < unmetFlags.length; i++) { var flagKey = unmetFlags[i].flag_key; var unlockInfo = flagUnlockMap[flagKey]; if (unlockInfo) { // Skip flags set by starting canvas (it plays automatically via "Start Game") if (startingCanvasId && unlockInfo.canvas_id === startingCanvasId) { continue; } var canvasConditions = unlockInfo.canvas_conditions; var isVisitable = !canvasConditions || setup.triggerConditionsSatisfied(canvasConditions); if (isVisitable) { return { location: unlockInfo.location, schedule: unlockInfo.schedule, canvas_name: unlockInfo.canvas_name }; } } } // Second pass: recursive chain resolution // If unlock canvas has flag conditions, find what unlocks THOSE flags var visited = {}; for (var i = 0; i < unmetFlags.length; i++) { var flagKey = unmetFlags[i].flag_key; var chainHint = setup.resolveUnlockChain(flagKey, flagUnlockMap, visited, 0); if (chainHint) { return chainHint; } } // Final fallback: generic message (no specific hint available) return null; }; // Recursively resolve the unlock chain to find a visitable canvas setup.resolveUnlockChain = function(flagKey, flagUnlockMap, visited, depth) { // Prevent infinite loops and excessive depth if (depth > 10 || visited[flagKey]) return null; visited[flagKey] = true; var unlockInfo = flagUnlockMap[flagKey]; if (!unlockInfo) return null; var canvasConditions = unlockInfo.canvas_conditions; // Check if this canvas is visitable if (!canvasConditions || setup.triggerConditionsSatisfied(canvasConditions)) { return { location: unlockInfo.location, schedule: unlockInfo.schedule, canvas_name: unlockInfo.canvas_name }; } // Canvas has conditions - check what's blocking it var items = canvasConditions.items || []; var helpData = setup.help_data || {}; var startingCanvasId = helpData.starting_canvas_id; var hasUnresolvableCondition = false; for (var i = 0; i < items.length; i++) { var item = items[i]; // If it's a flag condition that's not met if (item.type === 'flag' && !setup.checkSingleCondition(item)) { // Check if this flag is set by starting canvas (will be auto-met) var nestedUnlockInfo = flagUnlockMap[item.flag_key]; if (startingCanvasId && nestedUnlockInfo && nestedUnlockInfo.canvas_id === startingCanvasId) { continue; // Auto-met by starting canvas, skip this condition } // Recurse to find hint for this blocking flag var nestedHint = setup.resolveUnlockChain(item.flag_key, flagUnlockMap, visited, depth + 1); if (nestedHint) { return nestedHint; } hasUnresolvableCondition = true; } } // If all blocking conditions were auto-met (skipped), this canvas IS visitable if (!hasUnresolvableCondition) { return { location: unlockInfo.location, schedule: unlockInfo.schedule, canvas_name: unlockInfo.canvas_name }; } return null; }; // Format flag hint as "Visit X between Y" setup.formatFlagHint = function(hint) { if (!hint) return "Complete a prerequisite activity"; if (hint.location && hint.schedule) { return "Visit " + hint.location + " " + hint.schedule; } else if (hint.location) { return "Visit " + hint.location; } return "Complete: " + (hint.canvas_name || "a prerequisite activity"); }; // Format trait requirements as "Love > 30 and Trust > 20 required" setup.formatTraitRequirements = function(missingTraits) { if (!missingTraits || missingTraits.length === 0) return ""; var parts = missingTraits.map(function(req) { var traitName = req.trait.charAt(0).toUpperCase() + req.trait.slice(1); return traitName + " " + (req.operator || ">") + " " + req.value; }); return parts.join(" and ") + " required"; }; // Calculate remaining days for a days_since_flag condition setup.calculateDaysRemaining = function(condition) { var sv = State.variables; var flagKey = condition.flag_key; var requiredDays = condition.value || 1; var meta = (sv.flags_meta || {})[String(flagKey)]; var setDay = meta ? meta.set_day : null; // If flag not set yet, return full required days if (setDay === null) return requiredDays; var currentDay = (sv.game_state && sv.game_state.time_state) ? sv.game_state.time_state.day : 1; var daysSince = currentDay - setDay; var remaining = requiredDays - daysSince; return Math.max(0, remaining); }; // Format canvas trigger conditions as clickable links "Required: Elena Affection ≥ 50" setup.formatCanvasConditions = function(conditions) { if (!conditions || !conditions.items) return "Conditions not met"; var parts = []; var items = conditions.items; for (var i = 0; i < items.length; i++) { var item = items[i]; if (item.type === "trait") { // Format as clickable link: "Elena Affection ≥ 50" or "Your Boldness ≥ 40" var npcId = item.npc_id || ""; var isPlayerTrait = item.subject === "player" || !npcId; var trait = item.trait_key; var value = item.value; var op = item.operator === "gte" ? "≥" : item.operator === "gt" ? ">" : item.operator === "lte" ? "≤" : item.operator === "lt" ? "<" : item.operator === "eq" ? "=" : "≥"; // Look up NPC display name via slug map, fall back to raw ID var displayNpc = "Your"; if (!isPlayerTrait) { var slugMap = setup.npc_slug_map || {}; var npcsData = State.variables.npcs || {}; var uuid = slugMap[npcId]; var npcName = (uuid && npcsData[uuid]) ? (npcsData[uuid].name || npcId) : npcId; displayNpc = npcName + "'s"; } var displayTrait = trait.charAt(0).toUpperCase() + trait.slice(1); // Wrap in clickable span with data attributes var link = '<span class="trait-requirement-link" ' + 'data-npc="' + npcId + '" ' + 'data-trait="' + trait + '" ' + 'data-value="' + value + '">' + displayNpc + ' ' + displayTrait + ' ' + op + ' ' + value + '</span>'; parts.push(link); } // Skip flag conditions - they're internal game mechanics, not player-actionable } if (parts.length === 0) return "Conditions not met"; var logic = conditions.logic === "OR" ? " or " : " and "; return "Required: " + parts.join(logic); }; // Format activity hint as "Visit X between Y" setup.formatActivityHint = function(activity) { if (activity.location && activity.schedule) { return "Visit " + activity.location + " " + activity.schedule; } else if (activity.location) { return "Visit " + activity.location; } return activity.name; }; // Show modal with activities that increase a specific trait for an NPC or player setup.showTraitActivitiesModal = function(npcId, traitKey, requiredValue) { var helpData = setup.help_data || {}; var npcs = State.variables.npcs || {}; // Detect if this is a player trait (empty/undefined npcId) var isPlayerTrait = !npcId || npcId === ""; var displayName, currentValue, activities; if (isPlayerTrait) { // Player trait - use player data and trait_activities index displayName = "Your"; var playerData = State.variables.player || {}; currentValue = (playerData.core_traits || {})[traitKey] || 0; // Get activities from trait_activities index that boost this trait for player var allTraitActivities = helpData.trait_activities ? helpData.trait_activities[traitKey] : []; activities = allTraitActivities.filter(function(act) { // Check if any effect targets player (no npc_id) with this trait var effects = act.trait_effects || []; for (var e = 0; e < effects.length; e++) { if (effects[e].trait === traitKey && !effects[e].npc_id && effects[e].value > 0) { return true; } } return false; }); } else { // NPC trait - use NPC data and NPC activities var resolvedId = setup.resolveNpcId(npcId); var npcData = npcs[resolvedId] || {}; displayName = npcData.name || npcId.charAt(0).toUpperCase() + npcId.slice(1); currentValue = (npcData.core_traits || {})[traitKey] || 0; var npcHelpData = helpData.npcs ? helpData.npcs[resolvedId] : null; activities = npcHelpData ? npcHelpData.activities : []; } // Filter to activities that: // 1. Have a location (skip intro activities) // 2. Canvas conditions met (NPC trait requirements) // 3. Activity trait requirements met (player traits) // 4. Increase the requested trait var relevantActivities = []; for (var i = 0; i < activities.length; i++) { var act = activities[i]; if (!act.location) continue; // Skip intro activities // Skip completed non-repeatable activities (story arc OR regular non-repeatable) if (act.is_repeatable === false || act.linked_flag) { var flags = State.variables.flags || {}; var hist = (State.variables.game_state && State.variables.game_state.trigger_history) || {}; var isCompleted = (act.linked_flag && flags[act.linked_flag]) || (act.canvas_id && hist[act.canvas_id] && hist[act.canvas_id].total > 0); if (isCompleted) continue; } // Check canvas conditions (NPC trait requirements like "Elena Affection >= 30") var canvasConditions = act.canvas_conditions; if (canvasConditions && !setup.triggerConditionsSatisfied(canvasConditions)) { continue; // Skip - canvas conditions not met } // Check activity trait requirements (player traits) var traitReqs = act.trait_requirements || []; var isLocked = false; for (var r = 0; r < traitReqs.length; r++) { if (!setup.checkTraitRequirement(traitReqs[r])) { isLocked = true; break; } } if (isLocked) continue; // Skip locked activities // Check if this activity boosts the requested trait // For player traits: match effects without npc_id // For NPC traits: match effects with any npc_id (already filtered to NPC's activities) var effects = act.trait_effects || []; for (var j = 0; j < effects.length; j++) { var eff = effects[j]; var traitMatches = eff.trait === traitKey && eff.value > 0; var targetMatches = isPlayerTrait ? !eff.npc_id : true; if (traitMatches && targetMatches) { relevantActivities.push({ name: act.name, location: act.location, schedule: act.schedule, bonus: eff.value }); break; } } } // Deduplicate by name: keep only highest bonus per activity name var byName = {}; for (var d = 0; d < relevantActivities.length; d++) { var ra = relevantActivities[d]; if (!byName[ra.name] || byName[ra.name].bonus < ra.bonus) { byName[ra.name] = ra; } } relevantActivities = Object.values(byName); // Build modal HTML (no inline onclick - use jQuery event delegation) var traitDisplay = traitKey.charAt(0).toUpperCase() + traitKey.slice(1); var html = '<div class="trait-modal-overlay">'; html += '<div class="trait-modal">'; html += '<div class="trait-modal-header">'; var titlePossessive = isPlayerTrait ? (displayName + ' ') : (displayName + "'s "); html += '<h3>How to increase ' + titlePossessive + traitDisplay + '</h3>'; html += '<span class="trait-modal-close">×</span>'; html += '</div>'; html += '<div class="trait-modal-progress">'; html += 'Current: ' + currentValue + ' / Required: ' + requiredValue; html += '</div>'; html += '<div class="trait-modal-body">'; if (relevantActivities.length === 0) { html += '<p>No activities found that increase this trait.</p>'; } else { html += '<ul class="trait-activity-list">'; for (var k = 0; k < relevantActivities.length; k++) { var ra = relevantActivities[k]; html += '<li>'; html += '<span class="activity-name">' + ra.name + '</span>'; html += '<span class="activity-bonus">+' + ra.bonus + ' ' + traitDisplay + '</span>'; html += '<div class="activity-hint">→ Visit ' + ra.location; if (ra.schedule) html += ' ' + ra.schedule; html += '</div>'; html += '</li>'; } html += '</ul>'; } html += '</div></div></div>'; jQuery(html).appendTo('#story'); }; // Close trait activities modal setup.closeTraitModal = function() { jQuery('.trait-modal-overlay').remove(); }; // ============== END QUEST PAGE HELPERS ============== // Auto-inference for games without author-defined story arc setup.autoInferStoryPosition = function() { var result = { current_chapter: { id: "auto_chapter", name: "Your Story", mood: "developing", description: "Your adventure continues..." }, completed_nodes: [], available_nodes: [], locked_nodes: [], active_groups: [], is_stuck: false, progress_percent: 0, is_auto_inferred: true }; // Check completed canvases from game state if (State.variables.game_state && State.variables.game_state.completed_canvases) { State.variables.game_state.completed_canvases.forEach(function(canvasId) { // Avoid duplicates var existing = result.completed_nodes.find(function(n) { return n.id === canvasId; }); if (!existing) { result.completed_nodes.push({ id: canvasId, name: canvasId.replace(/_/g, " "), chapter: "auto_chapter", journal_entry: "A chapter of your story...", is_milestone: true }); } }); } // Calculate progress estimate var total = result.completed_nodes.length + result.available_nodes.length + result.locked_nodes.length; if (total > 0) { result.progress_percent = Math.round((result.completed_nodes.length / total) * 100); } result.is_stuck = result.available_nodes.length === 0 && result.locked_nodes.length > 0; return result; }; // Initialize trait requirement click handlers (event delegation) jQuery(document).on('click', '.trait-requirement-link', function(e) { e.preventDefault(); var npcId = jQuery(this).data('npc'); var trait = jQuery(this).data('trait'); var value = jQuery(this).data('value'); setup.showTraitActivitiesModal(npcId, trait, value); }); // Modal close handlers (event delegation) jQuery(document).on('click', '.trait-modal-overlay', function(e) { // Close when clicking overlay (but not modal content) if (jQuery(e.target).hasClass('trait-modal-overlay')) { setup.closeTraitModal(); } }); jQuery(document).on('click', '.trait-modal-close', function(e) { e.preventDefault(); setup.closeTraitModal(); }); <</script>><<nobr>> <div class="game-intro"> <h1>Jack's World</h1> <p class="game-description">Jack moves into his step-mom Angela's apartment. What starts as a practical living arrangement becomes something neither of them expected. Over 30 days, daily routines — shared breakfasts, evening movies, accidental encounters — build into a genuine connection. Every intimate moment is earned through presence, small kindnesses, and emotional vulnerability. The forbidden element adds tension but doesn't define the relationship. Love is the driver, not lust. </p> <div class="developer-intro"> <p class="developer-about">We're a small indie studio crafting intimate, story-driven experiences. Every game is made with care, and your support helps us keep creating. If you enjoy our work, consider supporting us!</p> <p class="support-link">👉 <a href="https://www.patreon.com/cw/nutgames844" target="_blank" rel="noopener">Support us on Patreon</a></p> </div> <div class="age-gate"> <p class="age-warning">⚠️ This game contains adult content intended for players 18 years of age or older.</p> <div class="age-buttons"> [[✓ I am 18 or older - Enter Game->StartingCanvas_jack_arrives_Node_1]] [[✗ I am NOT 18 or older->AgeBlocked]] </div> </div> <div class="developer-footer"> <p class="developer-credit">Developed by <strong>NutGames</strong></p> <p class="support-link">👉 <a href="https://www.patreon.com/cw/nutgames844" target="_blank" rel="noopener">Support us on Patreon</a></p> </div> </div> <</nobr>>
<div class="blocked-page"> <h2>Access Denied</h2> <p>This content is not available for you.</p> <p>Please close this page.</p> </div>
<!-- Time Display Widgets --> <<widget "timeFormatted">> <<set _hour to $game_state.time_state.current_hour>> <<set _minute to $game_state.time_state.current_minute>> <<set _displayHour to (_hour is 0 ? 12 : (_hour > 12 ? _hour - 12 : _hour))>> <<set _ampm to (_hour < 12 ? "AM" : "PM")>> <<set _minuteStr to (_minute < 10 ? "0" + _minute : _minute)>> <<print _displayHour + ":" + _minuteStr + " " + _ampm>> <</widget>> <<widget "timeDisplay">> <div id="time-widget" class="time-display"> <div class="time-line"> <span id="time-display"><<timeFormatted>></span> | <span id="current-day"><<print $game_state.time_state.current_day>></span> </div> <div class="control-line"> <button class="time-btn" onclick="advanceTime(10)" title="Advance 10 minutes">></button> | <button class="time-btn" onclick="advanceTime(60)" title="Advance 1 hour">>></button> | <button class="time-btn" onclick="advanceTime(1440)" title="Advance 1 day">>>>>></button> </div> </div> <</widget>> <<widget "playerTraits">> <div id="traits-widget" class="traits-display"> <div class="traits-header">Traits</div> <<if $player and $player.core_traits and Object.keys($player.core_traits).length > 0>> <<set _keys to Object.keys($player.core_traits).sort()>> <ul class="traits-list"> <<for _i to 0; _i lt _keys.length; _i++>> <<set _k to _keys[_i]>> <li class="trait-item"> <span class="trait-name"><<print _k>></span> <span class="trait-value"><<print $player.core_traits[_k]>></span> </li> <</for>> </ul> <<else>> <div class="no-traits">No traits</div> <</if>> <div class="traits-hint">Updates after each choice.</div> </div> <</widget>> <<widget "playerFlags">> <div id="flags-widget" class="traits-display"> <div class="traits-header">Flags</div> <<if $player and $player.flags and Object.keys($player.flags).length > 0>> <<set _fkeys to Object.keys($player.flags).sort()>> <ul class="traits-list"> <<for _j to 0; _j lt _fkeys.length; _j++>> <<set _fk to _fkeys[_j]>> <li class="trait-item"> <span class="trait-name"><<print _fk>></span> <span class="trait-value"><<print $player.flags[_fk] ? '✔' : '✖'>></span> </li> <</for>> </ul> <<else>> <div class="no-traits">No flags</div> <</if>> <div class="traits-hint">Set by choices and story logic.</div> </div> <</widget>> <<widget "questsButton">> <!-- Debug/legacy quest view - shows raw mechanics --> <div id="quests-btn-widget"> <<link "📋 Quests" "QuestsPage">><</link>> </div> <</widget>> <<widget "missingMediaButton">> <<if $flags.debug_mode>> <div id="missing-media-btn-widget" style="margin-bottom:8px;"> <<link "⚠️ Missing Media" "MissingMediaPage">><</link>> </div> <</if>> <</widget>> <<widget "journalButton">> <!-- Guide - simplified what's next help --> <div id="journal-btn-widget"> <<link "📖 Guide" "QuestsPage">><</link>> </div> <</widget>> <<widget "statsButton">> <div id="stats-btn-widget"> <<link "📊 Stats" "StatsPage">><</link>> </div> <</widget>> <<widget "scheduleButton">> <<set _npcsWithSchedules to setup.getNpcsWithSchedules()>> <<if _npcsWithSchedules.length > 0>> <div id="schedule-btn-widget"> <<link "📅 Schedules" "SchedulePage">><</link>> </div> <</if>> <</widget>> <<widget "patreonButton">> <div id="patreon-btn-widget"> <a href="https://www.patreon.com/cw/nutgames844" target="_blank" rel="noopener" class="patreon-link"> <svg class="patreon-icon" viewBox="0 0 24 24" width="16" height="16"> <path fill="currentColor" d="M15.386.524c-4.764 0-8.64 3.876-8.64 8.64 0 4.75 3.876 8.613 8.64 8.613 4.75 0 8.614-3.864 8.614-8.613C24 4.4 20.136.524 15.386.524M.003 23.537h4.22V.524H.003"/> </svg> Support Us </a> </div> <</widget>>
<<missingMediaButton>> <<timeDisplay>> <<journalButton>> <<statsButton>> <<scheduleButton>> <<playerTraits>> <<patreonButton>> <style> #time-widget { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 8px; margin-bottom: 10px; font-family: 'Courier New', monospace; font-size: 14px; line-height: 1.2; color: #212529; } .time-line { text-align: center; margin-bottom: 4px; font-weight: bold; color: #000000; } .control-line { text-align: center; font-size: 12px; } .time-btn { background: #e9ecef; border: 1px solid #ced4da; border-radius: 3px; color: #495057; cursor: pointer; padding: 3px 6px; font-size: 12px; font-family: 'Courier New', monospace; font-weight: bold; } .time-btn:hover { background: #dee2e6; color: #000000; border-color: #adb5bd; } #traits-widget { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 8px; margin-top: 8px; font-family: 'Courier New', monospace; font-size: 13px; color: #212529; } #flags-widget { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 8px; margin-top: 8px; font-family: 'Courier New', monospace; font-size: 13px; color: #212529; } .traits-header { font-weight: bold; margin-bottom: 6px; } .traits-list { list-style: none; padding-left: 0; margin: 0; } .trait-item { display: flex; justify-content: space-between; border-bottom: 1px dashed #e2e3e5; padding: 2px 0; } .trait-name { color: #495057; } .trait-value { color: #0d6efd; font-weight: bold; } .no-traits { font-style: italic; color: #6c757d; } .traits-hint { margin-top: 6px; font-size: 11px; color: #6c757d; } #time-display, #current-day { font-family: 'Courier New', monospace; color: #000000; } #quests-btn-widget { background: #e7f3ff; border: 1px solid #007bff; border-radius: 4px; padding: 6px 8px; margin-bottom: 8px; text-align: center; } #quests-btn-widget a { color: #007bff; text-decoration: none; font-weight: bold; } #quests-btn-widget a:hover { color: #0056b3; text-decoration: underline; } #journal-btn-widget { background: linear-gradient(180deg, #fdf6e3 0%, #f5e6d3 100%); border: 1px solid #c9b896; border-radius: 4px; padding: 6px 8px; margin-bottom: 8px; text-align: center; } #journal-btn-widget a { color: #6d5a40; text-decoration: none; font-weight: bold; font-family: 'Georgia', serif; } #journal-btn-widget a:hover { color: #4a3f30; text-decoration: underline; } #stats-btn-widget { background: #f0f7e6; border: 1px solid #28a745; border-radius: 4px; padding: 6px 8px; margin-bottom: 8px; text-align: center; } #stats-btn-widget a { color: #28a745; text-decoration: none; font-weight: bold; } #stats-btn-widget a:hover { color: #1e7e34; text-decoration: underline; } #schedule-btn-widget { background: #e8f4fc; border: 1px solid #3b82f6; border-radius: 4px; padding: 6px 8px; margin-bottom: 8px; text-align: center; } #schedule-btn-widget a { color: #3b82f6; text-decoration: none; font-weight: bold; } #schedule-btn-widget a:hover { color: #2563eb; text-decoration: underline; } /* Patreon Button in Sidebar */ #patreon-btn-widget { margin: 15px 0 0 0; padding: 15px 0 0 0; border-top: 1px solid #444; text-align: center; } /* Reduce gap between StoryCaption and Save/Restart menu */ #story-caption { margin-bottom: 0 !important; padding-bottom: 0 !important; } #menu-story { margin-top: 8px !important; } .patreon-link { display: inline-flex; align-items: center; gap: 6px; padding: 8px 12px; background: #f96854; color: #fff !important; border-radius: 4px; text-decoration: none; font-size: 0.9em; font-weight: bold; } .patreon-link:hover { background: #e85a47; text-decoration: none !important; } .patreon-icon { flex-shrink: 0; } .stats-card { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 12px; margin-bottom: 12px; } .stats-name { font-weight: bold; font-size: 1.2em; color: #212529; margin-bottom: 8px; border-bottom: 1px solid #dee2e6; padding-bottom: 6px; } .stats-traits { margin-top: 8px; } .stats-trait-item { display: flex; justify-content: space-between; padding: 4px 8px; border-bottom: 1px dashed #e2e3e5; color: #343a40; } .stats-trait-value { font-weight: bold; color: #0d6efd; } /* Story Arc Help Page Styles */ .chapter-context { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center; } .chapter-name { font-size: 1.1em; color: #e0e0e0; font-weight: bold; } .chapter-mood { font-size: 0.9em; color: #a0a0a0; font-style: italic; } .available-section h3 { font-size: 1em; color: #495057; margin: 0 0 10px 0; border-bottom: 1px solid #dee2e6; padding-bottom: 6px; } .help-card { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 12px 16px; margin-bottom: 10px; } .node-name { font-weight: bold; color: #212529; } .node-hint { color: #6c757d; font-size: 0.9em; margin-top: 4px; } .narrative-hint { background: #fff3cd; border: 1px solid #ffc107; border-radius: 6px; padding: 12px; margin: 16px 0; } .narrative-hint p { margin: 0; color: #856404; } .progress-summary { text-align: center; color: #6c757d; font-size: 0.9em; margin-top: 16px; padding-top: 12px; border-top: 1px solid #dee2e6; } .no-quests-msg { font-style: italic; color: #6c757d; padding: 10px; } /* Quest Page - Simplified One Activity Per NPC */ .npc-section { margin-bottom: 20px; padding: 16px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 10px; border: 1px solid #dee2e6; } .npc-name { font-size: 1.2em; color: #2c3e50; margin: 0 0 12px 0; border-bottom: 2px solid #3498db; padding-bottom: 8px; font-weight: 600; } .quest-available { color: #155724; font-weight: 500; padding: 10px 14px; background: rgba(40, 167, 69, 0.1); border-radius: 6px; border-left: 4px solid #28a745; } .quest-locked { color: #856404; font-weight: 500; padding: 10px 14px; background: rgba(255, 193, 7, 0.15); border-radius: 6px; border-left: 4px solid #ffc107; } .quest-conditions { color: #721c24; font-weight: 500; padding: 10px 14px; background: rgba(220, 53, 69, 0.1); border-radius: 6px; border-left: 4px solid #dc3545; } .quest-waiting { color: #856404; font-weight: 500; padding: 10px 14px; background: rgba(255, 193, 7, 0.15); border-radius: 6px; border-left: 4px solid #ffc107; font-style: italic; } .dev-canvas-info { background: #17a2b8; color: white; padding: 6px 10px; border-radius: 4px; margin-bottom: 10px; font-size: 11px; font-family: 'Courier New', monospace; } .quest-complete { color: #28a745; font-style: italic; padding: 10px 14px; text-align: center; } .no-quests { color: #6c757d; font-style: italic; padding: 12px; text-align: center; } /* Trait Requirement Link */ .trait-requirement-link { color: #721c24; text-decoration: underline; cursor: pointer; } .trait-requirement-link:hover { color: #a71d2a; } /* Trait Activities Modal Overlay */ .trait-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 10000; } /* Modal Container */ .trait-modal { background: #fff; border-radius: 12px; max-width: 400px; width: 90%; max-height: 80vh; overflow: hidden; box-shadow: 0 10px 40px rgba(0,0,0,0.3); } .trait-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; background: #f8f9fa; border-bottom: 1px solid #dee2e6; } .trait-modal-header h3 { margin: 0; font-size: 1.1em; color: #333; } .trait-modal-close { font-size: 24px; cursor: pointer; color: #666; line-height: 1; } .trait-modal-close:hover { color: #333; } .trait-modal-progress { padding: 12px 20px; background: rgba(220, 53, 69, 0.1); color: #721c24; font-weight: 500; text-align: center; } .trait-modal-body { padding: 16px 20px; max-height: 50vh; overflow-y: auto; } /* Activity List */ .trait-activity-list { list-style: none; padding: 0; margin: 0; } .trait-activity-list li { padding: 12px; border-bottom: 1px solid #eee; } .trait-activity-list li:last-child { border-bottom: none; } .activity-name { font-weight: 600; color: #333; } .activity-bonus { float: right; color: #28a745; font-weight: 500; } .activity-hint { margin-top: 4px; color: #666; font-size: 0.9em; clear: both; } /* Effect Toast Notification */ .effect-toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.85); color: #4ade80; padding: 10px 20px; border-radius: 6px; font-size: 14px; z-index: 9999; white-space: nowrap; } /* Game Intro - Top Left Aligned */ .game-intro { text-align: left; } /* Age Gate Section */ .age-gate { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; } .age-warning { font-weight: bold; color: #856404; margin-bottom: 15px; } .age-buttons { display: flex; flex-direction: column; gap: 10px; align-items: center; } /* Developer Intro Section (above age gate) */ .developer-intro { text-align: left; color: #555; margin: 20px 0; } /* Developer Footer Section (below age gate) */ .developer-footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #dee2e6; text-align: center; color: #6c757d; font-size: 0.9em; } .developer-intro a, .developer-footer a { color: #f96854; text-decoration: none; } .developer-intro a:hover, .developer-footer a:hover { text-decoration: underline; } .developer-about { line-height: 1.5; margin: 10px 0; } .support-link { margin-top: 8px; } .developer-footer .support-link { font-weight: bold; } /* Age Blocked Page */ .blocked-page { text-align: center; padding: 50px; color: #721c24; background: #f8d7da; border-radius: 8px; margin: 50px auto; max-width: 400px; } /* Override SugarCube default vertical centering */ #story { align-items: flex-start !important; justify-content: flex-start !important; padding-top: 1em !important; } .passage { margin-top: 0 !important; padding-top: 0 !important; } /* New/Unvisited Canvas Highlight */ .new-canvas a { color: #28a745 !important; font-weight: 600; } .new-canvas a::before { content: "✨ "; } /* Unlocked conditional choice highlight (in passages) */ .unlocked-choice a, .unlocked-choice .link-internal { color: #f59e0b !important; font-weight: 600; } .unlocked-choice a::before, .unlocked-choice .link-internal::before { content: "🔓 "; } /* Unlocked choice canvas link highlight (in location activity list) */ .unlocked-choice-canvas a { color: #f59e0b !important; font-weight: 600; } .unlocked-choice-canvas a::before { content: "🔓 "; } /* Navigation New Content Indicator */ .nav-new { color: #ffc107; font-weight: bold; font-size: 1.1em; } /* Navigation Unlocked Choice Indicators */ .nav-unlocked { color: #f59e0b; font-weight: bold; font-size: 1.0em; } .nav-unlocked-badge { font-size: 0.65em; padding: 2px 5px; } /* Navigation NPC Portrait Indicator */ .nav-npc-portrait { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; vertical-align: middle; margin-left: 4px; border: 2px solid #ccc; } /* =============================================== VISUAL NAVIGATION GRID =============================================== */ .location-nav-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin: 8px 0; } @media (min-width: 768px) { .location-nav-grid { grid-template-columns: repeat(3, 1fr); } } @media (min-width: 1024px) { .location-nav-grid { grid-template-columns: repeat(4, 1fr); } } /* Location Card */ .location-card { display: flex; flex-direction: column; background: #2a2a2a; border-radius: 8px; overflow: hidden; text-decoration: none !important; color: inherit !important; transition: transform 0.2s ease, box-shadow 0.2s ease; cursor: pointer; } .location-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } /* Card Image Area (5:2 aspect ratio - more compact) */ .location-card-image { width: 100%; padding-bottom: 40%; /* 5:2 ratio */ background-size: cover; background-position: center; position: relative; background-color: #1a1a1a; } /* Placeholder silhouette for locations without images */ .location-card-placeholder { display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #2d2d3a 0%, #1a1a2a 100%); } .location-card-placeholder svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 60%; height: 60%; opacity: 0.5; } /* Card Content Area */ .location-card-content { padding: 6px 8px; display: flex; flex-direction: column; gap: 2px; } .location-card-name { font-weight: 600; font-size: 0.9em; line-height: 1.2; color: #e0e0e0; } /* Indicators Row */ .location-card-indicators { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; } /* NEW badge (replaces ! indicator in visual mode) */ .nav-new-badge { background: #ffc107; color: #000; font-size: 0.65em; font-weight: bold; padding: 2px 5px; border-radius: 3px; text-transform: uppercase; } /* NPC portrait badges (smaller version for cards) */ .nav-npc-badge { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; border: 1px solid #666; } /* Exit Links Section (text-only, below grid) */ .location-nav-exits { margin-top: 12px; padding-top: 12px; border-top: 1px solid #444; } .location-nav-exits a { color: #aaa; } .location-nav-exits a:hover { color: #fff; } /* Dev Mode Controls */ .dev-adj-btn { background: #6c757d; color: white; border: none; border-radius: 3px; width: 20px; height: 20px; font-size: 14px; font-weight: bold; cursor: pointer; padding: 0; line-height: 1; vertical-align: middle; } .dev-adj-btn:hover { background: #5a6268; } .trait-controls { display: flex; align-items: center; gap: 4px; } .trait-controls .trait-value { min-width: 24px; text-align: center; } /* Portrait System */ .portrait { width: 40px; height: 40px; border-radius: 50%; overflow: hidden; flex-shrink: 0; background: #e9ecef; display: flex; align-items: center; justify-content: center; } .portrait img { width: 100%; height: 100%; object-fit: cover; } .portrait-placeholder svg { width: 70%; height: 70%; } /* Dialog Block with Portrait */ .dialog-block { display: flex; align-items: center; gap: 12px; margin: 10px 0; padding: 10px; border-radius: 8px; color: #212529; } .dialog-player { border-left: 4px solid #007bff; background: #e7f3ff; } .dialog-player strong { color: #0056b3; } .dialog-npc { border-left: 4px solid #6c757d; background: #f8f9fa; } .dialog-npc strong { color: #495057; } .dialog-content { flex: 1; } /* Stats Card Portrait (larger) */ .stats-portrait { width: 80px; height: 100px; border-radius: 8px; overflow: hidden; background: #e9ecef; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } .stats-portrait img { width: 100%; height: 100%; object-fit: cover; } .stats-portrait svg { width: 60%; height: 60%; } /* Stats Card with Portrait Layout */ .stats-card-with-portrait { display: flex; gap: 12px; align-items: flex-start; } .stats-info { flex: 1; min-width: 0; } </style>
<<nobr>> <h2>What's Next</h2> <<set _helpData = setup.help_data || {}>> <<set _hasNpcs = _helpData.npcs && Object.keys(_helpData.npcs).length > 0>> <<if _hasNpcs>> <<for _npcId, _npcData range _helpData.npcs>> <div class="npc-section"> <h3 class="npc-name"><<print _npcData.name>></h3> <<set _next = setup.getNextActivity(_npcId)>> <<if _next === null>> <div class="quest-complete">✓ All activities completed!</div> <<elseif _next.traitConditionsNotMet>> <div class="quest-conditions"> 🔒 <<print setup.formatCanvasConditions(_next.canvasConditions)>> </div> <<elseif _next.flagConditionsNotMet>> <div class="quest-available"> → <<print setup.formatFlagHint(_next.flagHint)>> </div> <<elseif _next.daysConditionsNotMet>> <div class="quest-waiting"> ⏳ <<if _next.daysRemaining === 1>>Come back tomorrow<<else>>Wait <<print _next.daysRemaining>> more days<</if>> </div> <<elseif _next.conditionsNotMet>> <div class="quest-conditions"> 🔒 <<print setup.formatCanvasConditions(_next.canvasConditions)>> </div> <<elseif _next.isLocked>> <div class="quest-locked"> 🔒 <<print setup.formatTraitRequirements(_next.missingTraits)>> </div> <<else>> <div class="quest-available"> → <<print setup.formatActivityHint(_next.activity)>> </div> <</if>> </div> <</for>> <<else>> <div class="no-quests">No activities available.</div> <</if>> <</nobr>> <<link "← Back">><<run Engine.backward()>><</link>>
<<set _position = setup.detectStoryPosition()>> <<set _hint = setup.generateNarrativeHint()>> <div class="journal-container"> <div class="journal-header"> <<if _position.current_chapter>> <h2 class="journal-chapter-title"><<print _position.current_chapter.name>></h2> <<if _position.current_chapter.mood>> <div class="journal-chapter-mood <<print _position.current_chapter.mood>>"> <<print _position.current_chapter.description || "">> </div> <</if>> <<else>> <h2 class="journal-chapter-title">Your Story</h2> <</if>> <<if _position.progress_percent > 0>> <div class="journal-progress"> <div class="journal-progress-bar"> <div class="journal-progress-fill" style="width: <<print _position.progress_percent>>%;"></div> </div> </div> <</if>> </div> <!-- Memories (completed story moments) --> <<if _position.completed_nodes.length > 0>> <div class="journal-section"> <div class="journal-section-title">Memories</div> <<for _i, _node range _position.completed_nodes>> <div class="journal-memory <<if _node.is_milestone>>milestone<</if>>"> <div class="journal-memory-title"><<print _node.name>></div> <<if _node.journal_entry>> <<print _node.journal_entry>> <</if>> </div> <</for>> </div> <</if>> <!-- Active story threads (groups in progress) --> <<if _position.active_groups.length > 0>> <<set _incompleteGroups = _position.active_groups.filter(function(g) { return !g.isComplete && g.completed > 0; })>> <<if _incompleteGroups.length > 0>> <div class="journal-section"> <div class="journal-section-title">Unfolding</div> <<for _i, _group range _incompleteGroups>> <div class="journal-group"> <div class="journal-group-name"><<print _group.name>></div> <div class="journal-group-progress"> <<print _group.description>> </div> </div> <</for>> </div> <</if>> <</if>> <!-- Relationship states (NPC emotions) --> <<if Object.keys($npcs).length > 0>> <div class="journal-section"> <div class="journal-section-title">Connections</div> <<for _npcId range Object.keys($npcs)>> <<set _state = setup.interpretNpcState(_npcId)>> <<if _state.npc_name>> <div class="journal-npc"> <div class="journal-npc-name"><<print _state.npc_name>></div> <div class="journal-npc-emotion"><<print _state.description>></div> <<if _state.relationship_summary && _state.relationship_summary !== _state.description>> <div class="journal-npc-summary"><<print _state.relationship_summary>></div> <</if>> </div> <</if>> <</for>> </div> <</if>> <!-- Subtle narrative hint (only when needed) --> <<if _hint.hint_type !== "none" && _hint.text>> <div class="journal-hint <<print _hint.hint_type>>"> <<print _hint.text>> </div> <</if>> <!-- Empty state --> <<if _position.completed_nodes.length === 0 && Object.keys($npcs).length === 0>> <div class="journal-empty"> Your story is just beginning... </div> <</if>> <div class="journal-back-link"> [[Return to your story->Navigation]] </div> </div>
<<nobr>> <h2>All Stats</h2> <!-- Player Stats --> <div class="stats-card stats-card-with-portrait"> <div class="stats-portrait"> <<if $player.portrait>> <img @src="'./videos/' + $player.portrait" @alt="$player.name" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';"> <<else>> <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg> <</if>> </div> <div class="stats-info"> <div class="stats-name">You (<<print $player.name>>)</div> <div class="stats-traits"> <<if $player && $player.core_traits && Object.keys($player.core_traits).length > 0>> <<for _tk, _tv range $player.core_traits>> <div class="stats-trait-item"> <span><<print _tk>></span> <span class="stats-trait-value"><<print _tv>></span> </div> <</for>> <<else>> <em>No traits</em> <</if>> </div> </div> </div> <!-- NPC Stats --> <<if Object.keys($npcs).length > 0>> <<for _npcId, _npc range $npcs>> <div class="stats-card stats-card-with-portrait"> <div class="stats-portrait"> <<if _npc.portrait>> <img @src="'./videos/' + _npc.portrait" @alt="_npc.name" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';"> <<else>> <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg> <</if>> </div> <div class="stats-info"> <div class="stats-name"><<print _npc.name>></div> <div class="stats-traits"> <<if _npc.core_traits && Object.keys(_npc.core_traits).length > 0>> <<for _tk, _tv range _npc.core_traits>> <div class="stats-trait-item"> <span><<print _tk>></span> <span class="stats-trait-value"><<print _tv>></span> </div> <</for>> <<else>> <em>No traits</em> <</if>> </div> </div> </div> <</for>> <<else>> <p><em>No NPCs found.</em></p> <</if>> <</nobr>> <<link "← Back">><<run Engine.backward()>><</link>>
<<nobr>> <<set _currentDay to $game_state.time_state.current_day>> <h2>📅 <<print _currentDay>>'s Schedules</h2> <p class="current-time">Current Time: <<print setup.formatTime($game_state.time_state.current_hour, $game_state.time_state.current_minute)>></p> <<set _npcsWithSchedules to setup.getNpcsWithSchedules()>> <<if _npcsWithSchedules.length === 0>> <p><em>No NPCs have schedules defined.</em></p> <<else>> <<for _npcInfo range _npcsWithSchedules>> <<set _npcId to _npcInfo.id>> <<set _npcName to _npcInfo.name>> <<set _currentLoc to setup.getNpcLocation(_npcId)>> <<set _todaySchedule to setup.getTodayScheduleSorted(_npcId)>> <<set _dayIndex to ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].indexOf(_currentDay)>> <div class="schedule-card"> <div class="schedule-header"> <span class="npc-name"><<print _npcName>></span> <<if _currentLoc>> <<set _locName to (setup.locations[_currentLoc.location] && setup.locations[_currentLoc.location].name) || _currentLoc.location>> <span class="now-badge">NOW: <<print _locName>></span> <</if>> </div> <<if _currentLoc && _currentLoc.activity>> <div class="current-activity">"<<print _currentLoc.activity>>"</div> <</if>> <<if _todaySchedule.length === 0>> <p><em>No schedule entries for <<print _currentDay>>.</em></p> <<else>> <p class="schedule-day-info"><<print _todaySchedule.length>> schedule entries for <<print _currentDay>></p> <table class="schedule-table"> <thead><tr><th>Time</th><th>Location</th><th>Activity</th></tr></thead> <tbody> <<for _sch range _todaySchedule>> <<set _isCurrent to setup.isCurrentTimeSlot(_sch.start_time, _sch.end_time)>> <<set _rowClass to _isCurrent ? "current-slot" : "">> <<set _schLocName to (setup.locations[_sch.location] && setup.locations[_sch.location].name) || _sch.location>> <tr class="<<print _rowClass>>"> <td><<if _isCurrent>>▶ <</if>><<print _sch.start_time>>-<<print _sch.end_time || "?">></td> <td><<print _schLocName>></td> <td><<print _sch.activity>></td> </tr> <</for>> </tbody> </table> <</if>> </div> <</for>> <</if>> <<return "← Back">> <</nobr>> <style> .current-time { color: #334155; font-size: 0.9em; margin: 0 0 10px 0; } .schedule-card { background: #ffffff; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; margin-bottom: 12px; } .schedule-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; } .npc-name { font-size: 1.1em; font-weight: bold; color: #1e293b; } .now-badge { background: #22c55e; color: white; padding: 3px 8px; border-radius: 10px; font-size: 0.8em; font-weight: bold; } .current-activity { color: #475569; font-style: italic; margin-bottom: 6px; font-size: 0.85em; } .schedule-day-info { color: #64748b; font-size: 0.8em; margin: 0 0 6px 0; padding: 2px 6px; background: #f1f5f9; border-radius: 3px; display: inline-block; } .schedule-table { width: 100%; border-collapse: collapse; font-size: 0.85em; color: #1e293b; } .schedule-table th { text-align: left; padding: 4px 8px; background: #f1f5f9; border-bottom: 2px solid #e2e8f0; color: #334155; font-weight: 600; } .schedule-table td { padding: 4px 8px; border-bottom: 1px solid #e2e8f0; color: #334155; } .schedule-table tr.current-slot { background: #fef3c7; font-weight: 500; } .schedule-table tr.current-slot td:first-child { color: #d97706; } </style>
<p>You're Jack. Early twenties. No degree, no steady job, no place of your own. Your father's second marriage fell apart two years ago, but his ex-wife — Angela — stayed in touch. When you needed somewhere to stay, she was the one who picked up the phone.</p><img src="./videos/player.jpg" alt="" loading="lazy" decoding="async" style="max-width:100%;height:auto;border-radius:8px;" /><p>Angela. Your step-mom. Mid-thirties, living alone in a two-bedroom apartment. Long dark hair, warm eyes, a quiet confidence. She offered you the guest room without hesitation.</p><img src="./videos/angela.jpg" alt="" loading="lazy" decoding="async" style="max-width:100%;height:auto;border-radius:8px;" /><p>Now it's just the two of you, under one roof. She makes coffee every morning. You hear her moving through the apartment at night. The arrangement is simple — rent, shared spaces, separate lives. But the apartment is small, and some distances are hard to keep.</p> <<set $game_state.current_canvas = "35660013-92ee-48da-b52a-950c05550e8c">> <<set $game_state.current_node = "707e3a99-7012-4aed-bc43-2d103ab29ddf">> <<script>>setup.markCanvasTriggered("35660013-92ee-48da-b52a-950c05550e8c");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("jack_arrives.intro") === -1) { vn.push("jack_arrives.intro"); }<</script>> <<nobr>> <<link "Continue" "StartingCanvas_jack_arrives_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>You stand at the door with your suitcase. The hallway smells like someone else's life — her laundry detergent, a candle she left burning. You knock, even though she said to just come in.</p><p>This is temporary. That's what you tell yourself. A few months, save some money, figure things out. She offered, you accepted. Simple.</p><p>Your father married her. The marriage didn't last, but Angela offered to let you stay at her apartment while you get on your feet. You met a few times during the marriage — holidays, a dinner or two. Polite. Nothing more.</p><p>Now you're moving into her home.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video> <<set $game_state.current_canvas = "35660013-92ee-48da-b52a-950c05550e8c">> <<set $game_state.current_node = "4a6388c5-8301-49a5-ba66-182c3b940774">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("jack_arrives.arrival") === -1) { vn.push("jack_arrives.arrival"); }<</script>> <<nobr>> <<link "Step inside" "StartingCanvas_jack_arrives_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She opens the door and you see her properly for the first time outside of a holiday dinner. Long dark hair, straight, falling past her shoulders. Fair skin. Full-figured — curves she doesn't hide but doesn't advertise. She's wearing a light satin robe, loosely tied. A delicate necklace. Bare feet on the hardwood floor — she's comfortable in her own home.</p><p>She's taller than you remembered. She smiles — warm but measured, like she's calibrating how much warmth is appropriate.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Jack. Come in. Let me show you around.</div></div><p>She steps aside. No hug — that would be too much for two people who are almost strangers. Her hand touches your shoulder as you pass through the doorway. Brief. Intentional or accidental? You can't tell.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She walks ahead of you through the apartment. You notice the way she carries herself — confident, unhurried. She looks back over her shoulder to make sure you're following. The look lasts half a second longer than it needs to.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video> <<set $game_state.current_canvas = "35660013-92ee-48da-b52a-950c05550e8c">> <<set $game_state.current_node = "48ab6428-f0b1-4a5a-afba-de2bd126affb">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("jack_arrives.meeting") === -1) { vn.push("jack_arrives.meeting"); }<</script>> <<nobr>> <<link "Follow her" "StartingCanvas_jack_arrives_Node_4">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She shows you the guest room. It's small but clean. She's put fresh sheets on the bed. A towel folded on the pillow. She thought about your arrival.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Bathroom's down the hall. Kitchen — you saw it. Help yourself to anything.</div></div><p>She pauses at the door.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Rent is two hundred a week. I know that sounds like a lot, but...</div></div><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> It's fine. I'll pick up shifts at the cafe.</div></div><p>She nods. A moment passes where neither of you moves.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Well. Welcome home, Jack.</div></div><p>She closes the door. You sit on the bed. Home.</p><p>'Welcome home, Jack.' Spoken by a woman who's been living alone, to a young man who's just moved in. Just the two of them. For as long as it takes.</p> <<set $game_state.current_canvas = "35660013-92ee-48da-b52a-950c05550e8c">> <<set $game_state.current_node = "f507d83d-a129-4f57-a055-a7be287aeae3">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("jack_arrives.situation") === -1) { vn.push("jack_arrives.situation"); }<</script>> <<script>>advanceTime(60);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 3.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "game_started");setup.applyAndNotifyFlag("player", null, "jack_arrived_complete");<</script>> [[Settle in->Location_Home]]
<h2>Navigation</h2> <p>Where would you like to go?</p> <div class="location-list"> [[Home->Location_Home]]<br> [[Kitchen->Location_Kitchen]]<br> [[Living Room->Location_Living_Room]]<br> [[Angela's Bedroom->Location_Angela's_Bedroom]]<br> [[Bathroom->Location_Bathroom]]<br> [[Jack's Bedroom->Location_Jack's_Bedroom]]<br> [[Street->Location_Street]]<br> [[Cafe->Location_Cafe]]<br> [[Hotel->Location_Hotel]]<br> [[Hotel Lobby->Location_Hotel_Lobby]]<br> [[Hotel Room->Location_Hotel_Room]]<br> </div> <!-- LOCATION PASSAGES -->
<h2>Home</h2> <p>The central hallway of Angela's apartment. Doors lead to every room. The smell of her laundry detergent and a candle she left burning.</p><<nobr>> <<set $player.current_location = "c4c4487c-40f0-4259-ad0e-2cb8ae06add8">> <<if not $game_state.visited_locations.includes("c4c4487c-40f0-4259-ad0e-2cb8ae06add8")>> <<set $game_state.visited_locations.push("c4c4487c-40f0-4259-ad0e-2cb8ae06add8")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("c4c4487c-40f0-4259-ad0e-2cb8ae06add8")>> <div class="location-navigation"> <<nobr>><div class="location-nav-grid"><a class="location-card link-internal" data-passage="Location_Jack's_Bedroom"><div class="location-card-image" style="background-image: url('./videos/images/locations/jacks_bedroom.jpg')"></div><div class="location-card-content"><span class="location-card-name">Jack's Bedroom</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("6c2f3d40-f029-496a-9760-ff92f9325613")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("6c2f3d40-f029-496a-9760-ff92f9325613")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("6c2f3d40-f029-496a-9760-ff92f9325613")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a><a class="location-card link-internal" data-passage="Location_Kitchen"><div class="location-card-image" style="background-image: url('./videos/images/locations/kitchen.jpg')"></div><div class="location-card-content"><span class="location-card-name">Kitchen</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("9f385aa2-5ef4-4895-b867-bb70a80afb05")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("9f385aa2-5ef4-4895-b867-bb70a80afb05")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("9f385aa2-5ef4-4895-b867-bb70a80afb05")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a><a class="location-card link-internal" data-passage="Location_Living_Room"><div class="location-card-image" style="background-image: url('./videos/images/locations/living_room.jpg')"></div><div class="location-card-content"><span class="location-card-name">Living Room</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("58f8de9e-e4af-45a5-81e6-9d02fc27ae07")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("58f8de9e-e4af-45a5-81e6-9d02fc27ae07")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("58f8de9e-e4af-45a5-81e6-9d02fc27ae07")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a><a class="location-card link-internal" data-passage="Location_Angela's_Bedroom"><div class="location-card-image" style="background-image: url('./videos/images/locations/bedroom.jpg')"></div><div class="location-card-content"><span class="location-card-name">Angela's Bedroom</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("663cf0e9-28d8-4522-a7f2-167f17de3978")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("663cf0e9-28d8-4522-a7f2-167f17de3978")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("663cf0e9-28d8-4522-a7f2-167f17de3978")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a><a class="location-card link-internal" data-passage="Location_Bathroom"><div class="location-card-image" style="background-image: url('./videos/images/locations/bathroom.jpg')"></div><div class="location-card-content"><span class="location-card-name">Bathroom</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("6dbbdc4a-9288-49fe-9d44-8ead45647e75")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("6dbbdc4a-9288-49fe-9d44-8ead45647e75")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("6dbbdc4a-9288-49fe-9d44-8ead45647e75")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a></div><</nobr>> <div class="location-nav-exits"> [[Leave Home->Location_Street]]<br> </div> </div>
<h2>Kitchen</h2> <p>The center of domestic life. Bright, well-lit, modern appliances. Natural light through windows. Counter island in the center. Angela's domain — this is where the day starts.</p><<nobr>> <<set $player.current_location = "9f385aa2-5ef4-4895-b867-bb70a80afb05">> <<if not $game_state.visited_locations.includes("9f385aa2-5ef4-4895-b867-bb70a80afb05")>> <<set $game_state.visited_locations.push("9f385aa2-5ef4-4895-b867-bb70a80afb05")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("9f385aa2-5ef4-4895-b867-bb70a80afb05")>> <div class="location-navigation"> <div class="location-nav-exits"> [[Leave Kitchen->Location_Home]]<br> </div> </div>
<h2>Living Room</h2> <p>Couch facing a TV. Comfortable, lived-in. Throw pillows, a blanket folded over the arm.</p><<nobr>> <<set $player.current_location = "58f8de9e-e4af-45a5-81e6-9d02fc27ae07">> <<if not $game_state.visited_locations.includes("58f8de9e-e4af-45a5-81e6-9d02fc27ae07")>> <<set $game_state.visited_locations.push("58f8de9e-e4af-45a5-81e6-9d02fc27ae07")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("58f8de9e-e4af-45a5-81e6-9d02fc27ae07")>> <div class="location-navigation"> <div class="location-nav-exits"> [[Leave Living Room->Location_Home]]<br> </div> </div>
<h2>Angela's Bedroom</h2> <p>Her private space. Bed with soft pillows, warm lighting. Candles on the nightstand. The room smells like her perfume. Entry feels like crossing a boundary.</p><<nobr>> <<set $player.current_location = "663cf0e9-28d8-4522-a7f2-167f17de3978">> <<if not $game_state.visited_locations.includes("663cf0e9-28d8-4522-a7f2-167f17de3978")>> <<set $game_state.visited_locations.push("663cf0e9-28d8-4522-a7f2-167f17de3978")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("663cf0e9-28d8-4522-a7f2-167f17de3978")>> <div class="location-navigation"> <div class="location-nav-exits"> [[Leave Angela's Bedroom->Location_Home]]<br> </div> </div>
<h2>Bathroom</h2> <p>Bathtub, warm lighting, steam. Tiled floor, a towel rack, bottles of bath oil. The sound of running water carries through the walls.</p><<nobr>> <<set $player.current_location = "6dbbdc4a-9288-49fe-9d44-8ead45647e75">> <<if not $game_state.visited_locations.includes("6dbbdc4a-9288-49fe-9d44-8ead45647e75")>> <<set $game_state.visited_locations.push("6dbbdc4a-9288-49fe-9d44-8ead45647e75")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("6dbbdc4a-9288-49fe-9d44-8ead45647e75")>> <div class="location-navigation"> <div class="location-nav-exits"> [[Leave Bathroom->Location_Home]]<br> </div> </div>
<h2>Jack's Bedroom</h2> <p>A small guest room. Single bed, a desk by the window, half-unpacked bag in the corner. Simple but comfortable.</p><<nobr>> <<set $player.current_location = "6c2f3d40-f029-496a-9760-ff92f9325613">> <<if not $game_state.visited_locations.includes("6c2f3d40-f029-496a-9760-ff92f9325613")>> <<set $game_state.visited_locations.push("6c2f3d40-f029-496a-9760-ff92f9325613")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("6c2f3d40-f029-496a-9760-ff92f9325613")>> <div class="location-navigation"> <div class="location-nav-exits"> [[Leave Jack's Bedroom->Location_Home]]<br> </div> </div>
<h2>Street</h2> <p>The neighborhood outside Angela's apartment. Familiar streets, the sound of traffic, a few shops. The cafe is a short walk away.</p><<nobr>> <<set $player.current_location = "8569b260-35b8-4946-95ef-8a3fa9bb50ba">> <<if not $game_state.visited_locations.includes("8569b260-35b8-4946-95ef-8a3fa9bb50ba")>> <<set $game_state.visited_locations.push("8569b260-35b8-4946-95ef-8a3fa9bb50ba")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("8569b260-35b8-4946-95ef-8a3fa9bb50ba")>> <div class="location-navigation"> <<nobr>><div class="location-nav-grid"><a class="location-card link-internal" data-passage="Location_Home"><div class="location-card-image" style="background-image: url('./videos/images/locations/home.jpg')"></div><div class="location-card-content"><span class="location-card-name">Home</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("c4c4487c-40f0-4259-ad0e-2cb8ae06add8")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("c4c4487c-40f0-4259-ad0e-2cb8ae06add8")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("c4c4487c-40f0-4259-ad0e-2cb8ae06add8")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a><a class="location-card link-internal" data-passage="Location_Cafe"><div class="location-card-image" style="background-image: url('./videos/images/locations/cafe.jpg')"></div><div class="location-card-content"><span class="location-card-name">Cafe</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("5a6f94eb-c917-43d4-95a7-92e322f39f43")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("5a6f94eb-c917-43d4-95a7-92e322f39f43")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("5a6f94eb-c917-43d4-95a7-92e322f39f43")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a><a class="location-card link-internal" data-passage="Location_Hotel"><div class="location-card-image" style="background-image: url('./videos/images/locations/hotel.jpg')"></div><div class="location-card-content"><span class="location-card-name">Hotel</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("5380069a-9ff0-4877-b0b9-74bdbeac1cf3")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("5380069a-9ff0-4877-b0b9-74bdbeac1cf3")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("5380069a-9ff0-4877-b0b9-74bdbeac1cf3")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a></div><</nobr>> </div>
<h2>Cafe</h2> <p>A neighborhood cafe with regulars, worn wooden tables, and the smell of fresh coffee. Warm and familiar.</p><<nobr>> <<set $player.current_location = "5a6f94eb-c917-43d4-95a7-92e322f39f43">> <<if not $game_state.visited_locations.includes("5a6f94eb-c917-43d4-95a7-92e322f39f43")>> <<set $game_state.visited_locations.push("5a6f94eb-c917-43d4-95a7-92e322f39f43")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("5a6f94eb-c917-43d4-95a7-92e322f39f43")>> <div class="location-navigation"> <div class="location-nav-exits"> [[Leave Cafe->Location_Street]]<br> </div> </div>
<!-- Container with default entry: Auto-redirect --> <<goto "Location_Hotel_Lobby">>
<h2>Hotel Lobby</h2> <p>A polished marble lobby with soft jazz playing. The reception desk gleams under warm lighting, elevators to the left.</p><<nobr>> <<set $player.current_location = "6cb9664f-0e77-4974-b0c1-02de30fa8ff4">> <<if not $game_state.visited_locations.includes("6cb9664f-0e77-4974-b0c1-02de30fa8ff4")>> <<set $game_state.visited_locations.push("6cb9664f-0e77-4974-b0c1-02de30fa8ff4")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("6cb9664f-0e77-4974-b0c1-02de30fa8ff4")>> <div class="location-navigation"> <<nobr>><div class="location-nav-grid"><a class="location-card link-internal" data-passage="Location_Hotel_Room"><div class="location-card-image" style="background-image: url('./videos/images/locations/hotel_room.jpg')"></div><div class="location-card-content"><span class="location-card-name">Hotel Room</span><div class="location-card-indicators"><<if setup.locationHasNewCanvases("fbd71126-198b-4a62-8eac-432645348482")>><span class="nav-new-badge">NEW</span><</if>><<if setup.locationHasNewUnlockedChoices("fbd71126-198b-4a62-8eac-432645348482")>><span class="nav-unlocked-badge">🔓</span><</if>><<for _npc range setup.getNpcsWithCanvasesAtLocation("fbd71126-198b-4a62-8eac-432645348482")>><<if _npc.portrait>><img @src="'./videos/' + _npc.portrait" class="nav-npc-badge" @alt="_npc.name"><</if>><</for>></div></div></a></div><</nobr>> <div class="location-nav-exits"> [[Exit Hotel->Location_Street]]<br> </div> </div>
<h2>Hotel Room</h2> <p>A hotel bedroom. Warm lighting, a king bed, champagne on the nightstand. Romantic and private.</p><<nobr>> <<set $player.current_location = "fbd71126-198b-4a62-8eac-432645348482">> <<if not $game_state.visited_locations.includes("fbd71126-198b-4a62-8eac-432645348482")>> <<set $game_state.visited_locations.push("fbd71126-198b-4a62-8eac-432645348482")>> <</if>> <</nobr>> <<= setup.renderLocationCanvases("fbd71126-198b-4a62-8eac-432645348482")>> <div class="location-navigation"> <div class="location-nav-exits"> [[Leave Hotel Room->Location_Hotel_Lobby]]<br> </div> </div> <!-- STORY CANVAS PASSAGES -->
<p>The movie ended ten minutes ago. Neither of you got up to change it. The credits are rolling in the dark.</p><p>Angela is closer than she usually sits. You can smell her shampoo — the same one from the bathroom. From that night.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I keep thinking about the other night. The bathroom.</div></div><p>She says it quietly, not looking at you. Her fingers are playing with the edge of the blanket.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I wasn't embarrassed, Jack. That's what scares me.</div></div><p>She turns to face you. The room is dark except for the TV glow. She's close enough that you can see the pulse in her throat.</p> <<set $game_state.current_canvas = "0250fcc1-bf27-445e-82b2-975bc27ca36f">> <<set $game_state.current_node = "89cecc53-5411-42d6-b8a8-59082d8f25fd">> <<script>>setup.markCanvasTriggered("0250fcc1-bf27-445e-82b2-975bc27ca36f");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("first_kiss.the_moment") === -1) { vn.push("first_kiss.the_moment"); }<</script>> <<nobr>> <<link "Kiss her gently" "Canvas_first_kiss_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <<link "Pull her close" "Canvas_first_kiss_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>You lean in slowly. Giving her time to pull away. She doesn't.</p><img src="./videos/tender_kiss.gif" alt="" loading="lazy" decoding="async" style="max-width:100%;height:auto;border-radius:8px;" /><p>Her lips are warm. The kiss is soft — careful, like you're both afraid of breaking something. Her hand comes up to your jaw, and for a moment, the whole world narrows to this.</p><p>She pulls back first. Her eyes are wide, searching yours.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> We probably shouldn't have done that.</div></div><p>But she's smiling. And she doesn't move away.</p> <<set $game_state.current_canvas = "0250fcc1-bf27-445e-82b2-975bc27ca36f">> <<set $game_state.current_node = "7548d038-970e-4a94-8558-a7c93f1915ac">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("first_kiss.gentle") === -1) { vn.push("first_kiss.gentle"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "first_kiss_complete");<</script>> [[Say goodnight->Location_Living_Room]]
<p>You don't think about it. You reach for her — your hand on the back of her neck — and pull her to you.</p><img src="./videos/passionate_kiss.gif" alt="" loading="lazy" decoding="async" style="max-width:100%;height:auto;border-radius:8px;" /><p>The kiss is not gentle. It's everything you've held back since that first morning, that first glimpse, that towel. She makes a sound against your mouth — surprise, then something deeper. Her fingers grip your shirt.</p><p>When you break apart, you're both breathing hard. She stares at you.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Jack...</div></div><p>She doesn't finish the sentence. She just stands up, touches her lips, and walks to her room. At the door, she pauses.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Goodnight.</div></div><p>But the way she says it — it's not a goodbye. It's a promise.</p> <<set $game_state.current_canvas = "0250fcc1-bf27-445e-82b2-975bc27ca36f">> <<set $game_state.current_node = "04771906-b506-4932-b625-d557683f5b85">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("first_kiss.passionate") === -1) { vn.push("first_kiss.passionate"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 3.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "first_kiss_complete");<</script>> [[Watch her leave->Location_Living_Room]]
<p>You push open the bathroom door without thinking — the sound of running water masked your footsteps, or maybe you just weren't paying attention. Either way, you're standing in the doorway and she's right there.</p><p>Angela is near the bathtub, nude, adjusting the faucet. Water cascades into the tub. Steam fills the room. She hasn't noticed you yet.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Then she looks up.</p> <<set $game_state.current_canvas = "0d7627ce-1680-4f9b-8c90-10d8ea655aaa">> <<set $game_state.current_node = "9de0f1ac-7f6f-4ab3-89a8-a0c2df9c4108">> <<script>>setup.markCanvasTriggered("0d7627ce-1680-4f9b-8c90-10d8ea655aaa");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("towel_encounter.walk_in") === -1) { vn.push("towel_encounter.walk_in"); }<</script>> <<nobr>> <<link "Freeze" "Canvas_towel_encounter_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>For a heartbeat, neither of you moves. Then she grabs a towel from the rack, wrapping it around herself with practiced ease. Not panicked. Not angry.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She's smiling. That's what surprises you most. Not the embarrassed kind of smile — something warmer, more knowing.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Knock next time, Jack.</div></div><p>Her voice is amused, not sharp. She adjusts the towel, tucking it tighter.</p> <<set $game_state.current_canvas = "0d7627ce-1680-4f9b-8c90-10d8ea655aaa">> <<set $game_state.current_node = "555fd16b-90d2-4b29-8949-2d43d4d8bcb9">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("towel_encounter.reaction") === -1) { vn.push("towel_encounter.reaction"); }<</script>> <<nobr>> <<link "Apologize" "Canvas_towel_encounter_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> I'm sorry — I didn't realize you were—</div></div><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> It's fine. Really. We live together — it was bound to happen eventually.</div></div><p>She laughs, and the tension breaks — mostly. But the look lingers. Something shifted between you. The image of her, unguarded, water running, steam in the air — it stays.</p><p>She pushes you gently out the door.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Now get out of here before I get cold.</div></div><p>You close the door. Your heart is still pounding.</p> <<set $game_state.current_canvas = "0d7627ce-1680-4f9b-8c90-10d8ea655aaa">> <<set $game_state.current_node = "cf2232ba-908b-4905-accf-b17e62e8b8ed">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("towel_encounter.aftermath") === -1) { vn.push("towel_encounter.aftermath"); }<</script>> <<script>>advanceTime(15);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 3.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "towel_encounter_complete");<</script>> [[Walk away->Location_Bathroom]]
<p>Angela is on the couch, but she's not relaxing. Bills are spread across the coffee table — envelopes, a laptop open to what looks like a bank statement. She's rubbing her temples with both hands.</p><p>She's been like this for days. Short answers at breakfast. Eyes that slide past yours. The kitchen feels ten degrees colder than it used to.</p><p>You stand in the doorway. She hasn't noticed you yet. Or she has, and she's choosing not to look up.</p><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> Long day?</div></div><p>She glances up. Not the warm look you got used to. Something guarded.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I'm fine.</div></div><p>The kind of 'fine' that means the opposite. She goes back to the bills. Her shoulders are hunched, her neck tight. She rolls it with a wince.</p> <<set $game_state.current_canvas = "1a773236-066c-4ef8-a48b-b01d73d888d8">> <<set $game_state.current_node = "03eb789a-a064-45d3-b87f-4ceff9bf6e7c">> <<script>>setup.markCanvasTriggered("1a773236-066c-4ef8-a48b-b01d73d888d8");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("massage_offer.bad_day") === -1) { vn.push("massage_offer.bad_day"); }<</script>> <<nobr>> <<link "Sit with her" "Canvas_massage_offer_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>You sit down on the couch. Not too close. You don't say anything about the bills. You don't say anything about the kiss.</p><p>You just notice her hand pressing into the base of her neck, and you reach over and press your thumb into the knot.</p><p>She tenses. Not from the pressure — from you. From the proximity. For a second you think she's going to pull away.</p><p>She doesn't. She lets out a breath she's been holding for days.</p><p>You work your way across her shoulders. Neither of you speaks. A minute passes. Maybe two. The tension releases under your hands — not just the muscle. Something deeper.</p><p>She drops her head forward. Her breathing slows.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I've been weird. I know.</div></div><p>You don't stop. You let her talk.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> The other night... I got scared. Not of you. Of me. Of how much I wanted it.</div></div><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I kept thinking — what kind of person does this make me?</div></div><p>Your hands pause. She turns to look at you. Not the guarded look. The real one — the one you haven't seen since that night on the couch.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> And then you just... kept showing up. Breakfast. Work. Rent. You didn't push. You didn't make it weird.</div></div><p>She sits up. Rolls her shoulders. Something has shifted — the wall that's been between you for days is gone.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I have massage oils in the bedroom. Candles, the whole thing. Maybe we could do this properly sometime?</div></div><p>She doesn't say it lightly this time. She says it like she means it. Like she's choosing this.</p><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> Just say when.</div></div><p>She smiles — not the polite one. The real one. The one that says I'm not scared anymore.</p> <<set $game_state.current_canvas = "1a773236-066c-4ef8-a48b-b01d73d888d8">> <<set $game_state.current_node = "5e484e2a-6bb5-467c-be46-783e6a372dc3">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("massage_offer.the_offer") === -1) { vn.push("massage_offer.the_offer"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "kiss_unlocked");setup.applyAndNotifyFlag("player", null, "massage_offered");<</script>> [[Head to your room->Location_Living_Room]]
<p>You can't sleep. The apartment is dark except for the kitchen — a thin line of light under the door.</p><p>Angela is at the counter. Robe, bare feet, a glass of wine that's almost empty. She looks up when you walk in.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Can't sleep either?</div></div><p>You pour a glass of water. She pours more wine. You both lean against the counter, and somehow an hour passes.</p><p>She tells you about the divorce. Not the details — just the shape of it. How it ended with a conversation in this kitchen. How she kept the apartment because she couldn't lose one more thing.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> People leave. That's what I learned.</div></div><p>You don't have an answer for that. You just stay. And maybe that's the answer.</p> <<set $game_state.current_canvas = "86000293-ba47-4430-ba02-0fc2d0303732">> <<set $game_state.current_node = "67dfc0dd-177e-438a-8a99-f2e29fd5b7ba">> <<script>>setup.markCanvasTriggered("86000293-ba47-4430-ba02-0fc2d0303732");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("late_night_kitchen.two_am") === -1) { vn.push("late_night_kitchen.two_am"); }<</script>> <<nobr>> <<link "Stay with her" "Canvas_late_night_kitchen_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>The clock on the stove reads 3:17 AM. She yawns.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I should sleep.</div></div><p>She stands. Rinses her glass. Walks toward the hallway. Stops at the door.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Jack?</div></div><p>You look up.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I'm glad you're here.</div></div><p>Then she's gone. The kitchen is quiet. You stand there for a while, in the place where her marriage ended and something else — something you can't name yet — began.</p> <<set $game_state.current_canvas = "86000293-ba47-4430-ba02-0fc2d0303732">> <<set $game_state.current_node = "6d7eaaf5-79c4-4180-861f-3fecdf77e81b">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("late_night_kitchen.goodnight") === -1) { vn.push("late_night_kitchen.goodnight"); }<</script>> <<script>>advanceTime(15);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "late_night_talk");<</script>> [[Go to bed->Location_Kitchen]]
<p>End of the week. You've worked every shift they'd give you. Two hundred dollars. Exactly what she asked for.</p><p>The bills on the counter. The spreadsheet she tried to hide. Her voice saying 'I know that sounds like a lot.' It wasn't a lot. It was everything.</p> <<set $game_state.current_canvas = "604413f9-8651-4bba-81a3-c1d5fa797110">> <<set $game_state.current_node = "c11de6e6-0b9c-4050-a282-f233395c2dbb">> <<script>>setup.markCanvasTriggered("604413f9-8651-4bba-81a3-c1d5fa797110");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("first_rent_day.the_money") === -1) { vn.push("first_rent_day.the_money"); }<</script>> <<nobr>> <<link "Leave it on the counter" "Canvas_first_rent_day_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <<link "Hand it to her" "Canvas_first_rent_day_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>You leave the money on the kitchen counter. Two hundred dollars, folded neatly. No note — she'll know.</p><p>She doesn't mention it. Not that day, not the next.</p><p>But that evening, she cooks something different. Not her usual quick meals. Something slow. Something she put time into. You recognize it — you mentioned once that your mom used to make it, offhand, weeks ago. She was listening.</p> <<set $game_state.current_canvas = "604413f9-8651-4bba-81a3-c1d5fa797110">> <<set $game_state.current_node = "f9b055b6-d880-4c5f-a997-e94e21382610">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("first_rent_day.counter") === -1) { vn.push("first_rent_day.counter"); }<</script>> <<script>>advanceTime(60);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.applyAndNotifyTrait("player", null, "money", "add", -200.0, false, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "first_rent_paid");setup.applyAndNotifyFlag("player", null, "rent_last_paid");<</script>> [[Eat together->Location_Kitchen]]
<p>You find her in the kitchen and hold out the money. Two hundred dollars.</p><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> First week's rent.</div></div><p>She looks at the money. Then at you. Takes it slowly. Your fingers brush. Neither of you pulls away immediately.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Thank you, Jack.</div></div><p>She says it differently than she's said anything before. Not like a landlord to a tenant. Not like a step-mom to a step-son. Like one person to another, with weight behind it.</p><p>She looks at you differently now. Not like a guest. Like someone who belongs here.</p> <<set $game_state.current_canvas = "604413f9-8651-4bba-81a3-c1d5fa797110">> <<set $game_state.current_node = "810014a0-e4b8-4b6a-962e-6a53492ae2b2">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("first_rent_day.handoff") === -1) { vn.push("first_rent_day.handoff"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, true, null);setup.applyAndNotifyTrait("player", null, "money", "add", -200.0, false, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "first_rent_paid");setup.applyAndNotifyFlag("player", null, "rent_last_paid");<</script>> [[Stay in the kitchen->Location_Kitchen]]
<p>You come home from a shift. The apartment is dim — just the lamp by the couch and the blue glow of a book light.</p><p>Angela is on the couch. Wine glass on the side table — her glass, the one with the chip on the rim she never replaced. A paperback open on her lap. Bare feet tucked under her. Hair down.</p><p>She doesn't hear you come in. You watch for a moment. Not with desire — with recognition. This is her life. Her evening. A glass she always uses. A corner of the couch worn soft from years of sitting there alone.</p><p>She looks up.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> How was work?</div></div><p>Casual. Like you've lived here for years. Then:</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I put a plate in the oven for you.</div></div><p>She didn't have to. She goes back to her book. You eat in the kitchen, and the apartment feels — for the first time — like it has two people in it.</p> <<set $game_state.current_canvas = "a62c89df-8d90-4384-bca2-67fe117f589e">> <<set $game_state.current_node = "fcc8d5b1-b44e-44f0-a05b-16aa8e7b8909">> <<script>>setup.markCanvasTriggered("a62c89df-8d90-4384-bca2-67fe117f589e");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("angelas_evening.the_couch") === -1) { vn.push("angelas_evening.the_couch"); }<</script>> <<script>>advanceTime(60);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "routine_noticed");<</script>> [[Eat and settle in->Location_Living_Room]]
<p>The cafe is small — six tables, a long counter, the smell of ground coffee and fresh bread. A woman behind the counter watches you walk in.</p><div class="dialog-block dialog-npc"><div class="dialog-content"><strong>Maria:</strong> What can I get you?</div></div><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> Actually — I'm looking for work. Anything available?</div></div><p>She sizes you up. Not unkindly.</p><div class="dialog-block dialog-npc"><div class="dialog-content"><strong>Maria:</strong> You Angela's...?</div></div><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> Her stepson. I'm staying with her.</div></div><p>A nod. Something passes across her face — respect, maybe. Or recognition.</p><div class="dialog-block dialog-npc"><div class="dialog-content"><strong>Maria:</strong> She's a good woman. Start Monday. Six-fifty an hour plus tips.</div></div> <<set $game_state.current_canvas = "84ee6921-81dc-4f9f-860e-0c58155ae774">> <<set $game_state.current_node = "c1fb15c5-9b0e-4f91-834d-97b0fdddee40">> <<script>>setup.markCanvasTriggered("84ee6921-81dc-4f9f-860e-0c58155ae774");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("jack_gets_hired.interview") === -1) { vn.push("jack_gets_hired.interview"); }<</script>> <<nobr>> <<link "Tell Angela" "Canvas_jack_gets_hired_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>That evening, Angela is in the kitchen. You lean against the doorframe.</p><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> I got a job. The cafe on the corner.</div></div><p>She pauses mid-cooking. Sets the spoon down. Turns to face you.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> You didn't have to do that.</div></div><div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> I wanted to.</div></div><p>Silence. Not awkward — full. She goes back to cooking, but she's smiling. The kind of smile that means something landed.</p> <<set $game_state.current_canvas = "84ee6921-81dc-4f9f-860e-0c58155ae774">> <<set $game_state.current_node = "e9f3182b-459a-4d8d-a435-512074dbbd4d">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("jack_gets_hired.telling_angela") === -1) { vn.push("jack_gets_hired.telling_angela"); }<</script>> <<script>>advanceTime(45);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "job_started");<</script>> [[Help with dinner->Location_Kitchen]]
<p>You come home to find Angela at the kitchen counter. Laptop open, papers spread out. She doesn't hear you come in.</p><p>You see enough before she notices — overdue notices, a spreadsheet of expenses with red numbers. Bills. A lot of them.</p><p>She closes the laptop quickly.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Just bills. Nothing exciting.</div></div><p>Her voice is light but her eyes aren't. You realize — she's been covering everything alone since the divorce. Your father left her the apartment, but not much else.</p> <<set $game_state.current_canvas = "9701502a-4d5e-484e-aea0-3e0d7d197a21">> <<set $game_state.current_node = "482e2075-13d3-43b8-a846-557b8456922c">> <<script>>setup.markCanvasTriggered("9701502a-4d5e-484e-aea0-3e0d7d197a21");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("angelas_struggle.discovery") === -1) { vn.push("angelas_struggle.discovery"); }<</script>> <<nobr>> <<link "I want to help with rent" "Canvas_angelas_struggle_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <<link "That's a lot to handle alone" "Canvas_angelas_struggle_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> Angela — I want to help. With rent. Seriously.</div></div><p>She shakes her head.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> You just got here, Jack. Get settled first.</div></div><p>But her face says something different. Relief she won't name. Gratitude she doesn't know how to show.</p><p>She goes back to the papers. But something between you has changed. She let you see something real.</p> <<set $game_state.current_canvas = "9701502a-4d5e-484e-aea0-3e0d7d197a21">> <<set $game_state.current_node = "6e595409-f6f3-4335-b3a0-bdf17877ceb5">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("angelas_struggle.offer_rent") === -1) { vn.push("angelas_struggle.offer_rent"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "bills_discovered");<</script>> [[Give her space->Location_Kitchen]]
<div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> That's a lot to handle alone.</div></div><p>She's quiet for a moment. Then:</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> It is.</div></div><p>Two words. The first time she's admitted it to anyone. She doesn't cry. She doesn't need to. The admission itself is the vulnerability.</p><p>She closes the laptop. Pours a second glass of wine — for you. You sit with her at the counter and neither of you says anything for a while. It's enough.</p> <<set $game_state.current_canvas = "9701502a-4d5e-484e-aea0-3e0d7d197a21">> <<set $game_state.current_node = "1a752ba7-5f99-44bf-80ab-c9decaf50642">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("angelas_struggle.offer_empathy") === -1) { vn.push("angelas_struggle.offer_empathy"); }<</script>> <<script>>advanceTime(45);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "bills_discovered");<</script>> [[Sit with her->Location_Kitchen]]
<p>You're heading to the bathroom. The door is cracked open — not enough to see, but enough to hear. Water splashing. A soft hum.</p><p>You should keep walking. You know that. But your feet slow down, and before you can stop yourself, you glance through the gap.</p><video src="./videos/angela_white/clips/480p.h264/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Angela. In the bath. Steam curling around her shoulders. She hasn't noticed you.</p><p>Your breath catches. Something shifts — something you can't take back. She's not your step-mom in this moment. She's a woman. And you're staring.</p> <<set $game_state.current_canvas = "93e51bf3-ad19-4c8d-ab07-bbde172d4857">> <<set $game_state.current_node = "6e1f3d28-94ff-446d-9f32-3ecd116f3e88">> <<script>>setup.markCanvasTriggered("93e51bf3-ad19-4c8d-ab07-bbde172d4857");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("accidental_glimpse.the_door") === -1) { vn.push("accidental_glimpse.the_door"); }<</script>> <<nobr>> <<link "Walk away quickly" "Canvas_accidental_glimpse_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <<link "Linger for a moment" "Canvas_accidental_glimpse_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>You pull back from the door and walk to your room. Your hands are shaking.</p><p>You sit on the edge of your bed and stare at the wall. What the hell was that? She's Angela. She's your step-mom.</p><p>But you can still see her. The curve of her shoulder. The steam. The way the light hit the water.</p><p>You didn't mean to look. But you can't pretend you didn't.</p> <<set $game_state.current_canvas = "93e51bf3-ad19-4c8d-ab07-bbde172d4857">> <<set $game_state.current_node = "153931d6-ddce-47ff-80a1-4a676e6a13c6">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("accidental_glimpse.walk_away") === -1) { vn.push("accidental_glimpse.walk_away"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "peek_unlocked");<</script>> [[Try to clear your head->Location_Bathroom]]
<video src="./videos/angela_white/clips/480p.h264/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You watch for a beat longer than you should. She tilts her head back, eyes closed. Completely unaware.</p><p>Then the floorboard creaks under your foot. You freeze — but she doesn't open her eyes. You step back, heart hammering, and retreat to your room.</p><p>You lie on your bed and stare at the ceiling. You can't stop thinking about it. About her. This isn't what you expected when you moved in.</p> <<set $game_state.current_canvas = "93e51bf3-ad19-4c8d-ab07-bbde172d4857">> <<set $game_state.current_node = "8a82cf50-856f-4369-aff2-5f676f624fe3">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("accidental_glimpse.linger") === -1) { vn.push("accidental_glimpse.linger"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "peek_unlocked");<</script>> [[Lie down and think->Location_Bathroom]]
<p>You're in the hallway when Angela comes out of the kitchen carrying a sponge and a bottle of cleaning spray. She holds them out to you.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Jack. Come here.</div></div><p>There's a look on her face — not angry, but clear. The kind of look that says this isn't a request.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> You're not a guest. If you're living here, you help out. Dishes after meals. Laundry once a week. Keep the bathroom clean. I'm not your maid.</div></div><p>She says it matter-of-factly. No apology, no softening. This is her home, and these are her terms.</p> <<set $game_state.current_canvas = "77af4bc5-05e2-4d6b-af2e-55d41fc29ddf">> <<set $game_state.current_node = "bfad35fc-327e-43bc-a79b-1c6239d6ca28">> <<script>>setup.markCanvasTriggered("77af4bc5-05e2-4d6b-af2e-55d41fc29ddf");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("house_rules.the_list") === -1) { vn.push("house_rules.the_list"); }<</script>> <<nobr>> <<link "I'll pull my weight" "Canvas_house_rules_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <<link "Sure, whatever" "Canvas_house_rules_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<div class="dialog-block dialog-player"><div class="portrait"><img src="./videos/player.jpg" alt="You" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>You:</strong> You're right. I should have asked what I can do.</div></div><p>Something shifts in her expression. Not warmth exactly — respect. Like you just passed a test she didn't tell you about.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Good. Cleaning stuff is under the sink. Laundry's in the bathroom closet.</div></div><p>She walks back to the kitchen. You look down at the sponge in your hand. This is your home now. Time to act like it.</p> <<set $game_state.current_canvas = "77af4bc5-05e2-4d6b-af2e-55d41fc29ddf">> <<set $game_state.current_node = "74d16acb-35fb-44b6-9622-7121ab37a78f">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("house_rules.step_up") === -1) { vn.push("house_rules.step_up"); }<</script>> <<script>>advanceTime(15);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "chores_explained");<</script>> [[Get started->Location_Home]]
<p>You take the sponge. She watches you for a moment, then walks away.</p><p>She doesn't say anything else. But you notice she doesn't save you dinner that night.</p> <<set $game_state.current_canvas = "77af4bc5-05e2-4d6b-af2e-55d41fc29ddf">> <<set $game_state.current_node = "548bb71d-ea9f-4991-b9bc-18dd6b085ae2">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("house_rules.shrug") === -1) { vn.push("house_rules.shrug"); }<</script>> <<script>>advanceTime(15);<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "chores_explained");<</script>> [[Go to your room->Location_Home]]
<p>Angela is kneeling on the bedroom floor. Pink lingerie — lace, straps, nothing she'd wear to bed. There's a collar on the rug beside her. Leather. A leash coiled next to it. She looks up at you with something you've never seen in her eyes before. Not fear. Not shame. Permission.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I want to try something. Only if you want to.</div></div><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She lifts the collar and holds it out to you. Her hands are steady. She's thought about this — maybe for weeks, maybe longer. She's not asking you to dominate her. She's asking you to be trusted with this.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You fasten the collar around her neck. The leather is soft. She nuzzles against your thigh, the leash hanging loose between you. Submissive by choice — and the choice is what makes it powerful. Her breath is warm through your jeans.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She takes you in her mouth on her knees. The leash rests in your hand — not pulled, just held. She looks up at you with the collar against her throat and there's nothing degrading about it. She's giving you everything because she wants to. Her tongue works slowly, deliberately, her hands on your thighs.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She crawls toward the mirror on her hands and knees. The leash trails behind her. She catches her own reflection — collar, lingerie, flushed skin — and smiles. Not embarrassed. Thrilled. She likes who she is right now.</p> <<set $game_state.current_canvas = "2209f645-0991-4f24-b4c9-6349d8b536b9">> <<set $game_state.current_node = "a76c6c34-e4a7-4bb4-9b67-b90d3bf62849">> <<script>>setup.markCanvasTriggered("2209f645-0991-4f24-b4c9-6349d8b536b9");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_exploring_kink_angela.floor_mirror") === -1) { vn.push("activity_exploring_kink_angela.floor_mirror"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_exploring_kink_angela_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She leads you to the bed by the leash — a reversal. She tugs, you follow. She climbs up on her knees and looks back at you over her shoulder. The collar sits against her throat like it belongs there.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>On the bed she's different. She wraps the leash around her hand and pulls you to her — not submitting now, demanding. The power shifts between you like breathing. She's on her back, then her knees, then straddling you, the collar the only constant.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You push inside her and she gasps — loud, unguarded. The collar bobs against her throat with every thrust. She's vocal in a way she's never been. No neighbors to worry about in the back of her mind. Just sensation and the leather around her neck and you.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Doggy — you hold the leash with one hand and her hip with the other. She pushes back into you, hard. Then she flips, straddles you, rides you with the collar swinging between her breasts. Control passes back and forth and neither of you knows who's really in charge. That's the point.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>The leash pulls taut between you. Harder. Faster. Her face inches from yours despite the restraints, her breath hot on your mouth. Everything building — the trust, the tension, the months of wanting each other more than you should. It all crests here.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Babe is All Yours While She Gets Fucked on a Leash/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She unbuckles the collar herself. Sets it on the nightstand like something sacred. Then she wraps her arms around you — no leather, no props, no performance. Just her skin and yours and the quiet that comes after.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Thank you. For trusting me enough to let me trust you.</div></div><p>She means it. Every word. This wasn't about the collar. It was about being seen — completely, without judgment — and choosing to stay.</p> <<set $game_state.current_canvas = "2209f645-0991-4f24-b4c9-6349d8b536b9">> <<set $game_state.current_node = "9d888b21-22b5-4362-9e96-5f185ad80e17">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_exploring_kink_angela.bed_phase") === -1) { vn.push("activity_exploring_kink_angela.bed_phase"); }<</script>> <<script>>advanceTime(90);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 4.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.showEffectNotification();<</script>> [[Hold her->Location_Angela's_Bedroom]]
<p>Angela has transformed her bedroom. Towels laid across the bed, candles on the nightstand, a bottle of massage oil warming in a bowl of hot water. The curtains are drawn. The room smells like eucalyptus and something floral — lavender, maybe. She's in the bathroom doorway.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I thought we could both use some relaxation.</div></div><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She fixes her hair in the mirror, the candlelight catching her face in profile. She's wearing a silk robe, loosely tied. She catches you watching and smiles — not shy, not teasing. Comfortable.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She drops the robe. One smooth motion — silk sliding off her shoulders, pooling at her feet. She lies face-down on the towels, her arms folded under her chin. The line of her back, the curve of her hips, all of it bare. You pour warm oil into your palms.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You start at her shoulders. Long strokes, palms flat, thumbs digging into the knots along her spine. She sighs — a real sigh, the kind that comes from somewhere deep. Weeks of tension loosening under your hands.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your thumbs press circles into her lower back, working the dimples above her hips. Oil glistens on her skin in the candlelight. You move back to her shoulders, watching the oil trail down between her shoulder blades.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She melts into the bed. Her breathing slows. Her eyes are closed, her lips slightly parted. A quiet hum escapes her — not a word, just a sound that says don't stop.</p> <<set $game_state.current_canvas = "c6680908-b444-4e94-8f5e-d7648d09de3c">> <<set $game_state.current_node = "0376cd23-a1ae-457d-9f88-a90020da2f9f">> <<script>>setup.markCanvasTriggered("c6680908-b444-4e94-8f5e-d7648d09de3c");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_spa_massage_angela.massage") === -1) { vn.push("activity_spa_massage_angela.massage"); }<</script>> <<nobr>> <<link "Finish the massage" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(90);<</script>><</link>><br> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 45, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "version": "1.0"})>> <span @class="setup.isChoiceVisited('c6680908-b444-4e94-8f5e-d7648d09de3c:cc0') ? '' : 'unlocked-choice'"> <<link "Let your hands wander" "Canvas_activity_spa_massage_angela_Node_2">><<script>>setup.markChoiceVisited("c6680908-b444-4e94-8f5e-d7648d09de3c:cc0");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('c6680908-b444-4e94-8f5e-d7648d09de3c:cc1') ? '' : 'unlocked-choice'"> <<link "Pull her close" "Canvas_activity_spa_massage_angela_Node_3">><<script>>setup.markChoiceVisited("c6680908-b444-4e94-8f5e-d7648d09de3c:cc1");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('c6680908-b444-4e94-8f5e-d7648d09de3c:cc2') ? '' : 'unlocked-choice'"> <<link "Give her everything" "Canvas_activity_spa_massage_angela_Node_4">><<script>>setup.markChoiceVisited("c6680908-b444-4e94-8f5e-d7648d09de3c:cc2");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <</nobr>>
<p>Your hands slow. They drift lower — past her waist, across her hips. She doesn't tense. She doesn't stop you. The massage has become something else and you both know it.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She turns over. Onto her back, looking up at you. Her breasts rise and fall with each breath, skin shining with oil. Her eyes find yours and hold them. A question and an answer at the same time.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You pour oil across her chest and your hands find her breasts. She arches into your palms — nipples hard against your fingers, her breath catching as you knead slowly. This isn't a massage anymore. Her hand covers yours and presses it harder against her.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Oil glistens between your fingers and her skin. You trace the curve of her ribs, her stomach, the soft skin below her navel. Both of you are breathing harder. The room is warm and close.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your hand slides between her thighs. She opens her legs and your oiled fingers find her — warm, wet, swollen. She gasps and her hips lift off the towels. You stroke her slowly, watching her face.</p><p>She reaches for your hand and holds it against her chest. Her heart is hammering. Her skin is flushed from her neck to her stomach.</p> <<set $game_state.current_canvas = "c6680908-b444-4e94-8f5e-d7648d09de3c">> <<set $game_state.current_node = "d5fb9e46-146a-4a51-b107-f99a98508738">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_spa_massage_angela.sensual") === -1) { vn.push("activity_spa_massage_angela.sensual"); }<</script>> <<nobr>> <<link "Hold her close" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 3.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, false, null);<</script>><<script>>setup.applyAndNotifyFlag("player", null, "groping_unlocked");<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(90);<</script>><</link>><br> <</nobr>>
<p>She rolls over and reaches for you. The pretense of a massage is gone. Her oiled hands pull at your shirt, your belt — she wants skin on skin. She pulls you onto the bed.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_016.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She straddles you, her thighs slick with oil, grinding down. The friction is different with oil — everything slides, everything is warm. Her hands press flat on your chest as she rocks against you, her eyes locked on yours.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_015.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_020.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She slides down your body, oil trailing from her breasts across your stomach. She takes you in her mouth — slow at first, deliberate, her tongue working the head. Then deeper, her hands gripping your hips, pulling you into her throat. Everything is slick and warm and relentless.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_018.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She turns and gets on all fours. Oil glistens on the curve of her back, her ass, the insides of her thighs. She looks over her shoulder at you. No words needed.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_017.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She pushes you onto your back and climbs on top. She sinks down onto you and rides — slow, deep, her oiled body catching the candlelight with every movement. Her hands find yours and she pins them above your head. She controls the rhythm, the depth, everything.</p><p>She collapses against you, slick and breathless, her cheek pressed to your chest. Both of you are covered in oil. The towels are ruined. Neither of you cares.</p> <<set $game_state.current_canvas = "c6680908-b444-4e94-8f5e-d7648d09de3c">> <<set $game_state.current_node = "2fd69e3a-ad14-4bed-ad1f-ec18096d55ea">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_spa_massage_angela.sexual") === -1) { vn.push("activity_spa_massage_angela.sexual"); }<</script>> <<nobr>> <<link "Collapse together" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 5.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(120);<</script>><</link>><br> <</nobr>>
<p>She sits up and reaches for the oil. Her fingers are gentle on your shoulder, turning you over. She pushes you onto your stomach and straddles your lower back. Her weight is warm and grounding.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] NewSensations - Angela White XXX BIg Tits Rubdown/clip_019.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her hands work your shoulders, your spine, the muscles along your ribs. She's not as strong as you, but her touch is careful — she finds every knot, every tight spot. Oil-warm palms pressing into your skin. This is the part she wanted all along. Not the sex. This.</p><p>She rests her cheek against your back, her oiled hands still on your shoulders. You can feel her heartbeat between your shoulder blades.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> We should do this every week.</div></div><p>She says it into your skin. You feel the words more than hear them.</p> <<set $game_state.current_canvas = "c6680908-b444-4e94-8f5e-d7648d09de3c">> <<set $game_state.current_node = "8a3c3333-9bdf-4841-b8f5-ff144cb198d7">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_spa_massage_angela.aftercare") === -1) { vn.push("activity_spa_massage_angela.aftercare"); }<</script>> <<script>>advanceTime(120);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 6.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 4.0, true, null);setup.showEffectNotification();<</script>> [[Clean up together->Location_Angela's_Bedroom]]
<p>You've never seen Angela like this. She's been getting ready for two hours — longer than you've ever waited for anything.</p><p>When she finally steps out of the bedroom, you understand why.</p><p>Black dress. Hair down. She looks like someone you'd see in a movie and think 'she's not real.'</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Well?</div></div><p>You forget every word you've ever learned.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Let's go before I change my mind.</div></div><p>She takes your arm. For the first time, you leave the apartment together.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "0c751e37-b019-4a6e-a8b6-38f1bcece8ff">> <<script>>setup.markCanvasTriggered("d0929ed7-f634-4c19-b048-29d4ea77ef9c");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.getting_ready") === -1) { vn.push("activity_date_night_hotel_angela.getting_ready"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>The restaurant is small and dimly lit. The kind of place where the tables are close together and nobody looks at anyone else.</p><p>The waiter calls you 'sir' and Angela 'madame.' She squeezes your hand under the table.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I keep expecting someone to stop us. To say 'excuse me, you two aren't allowed to be happy.'</div></div><p>Two glasses of wine. Then a third. She tells you about her twenties, her first apartment, the life she had before your father. You tell her about growing up without a home that felt permanent.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> You have one now. You know that, right?</div></div><p>Outside, the city is loud and anonymous. She kisses you on the sidewalk and nobody cares.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Take me to the hotel.</div></div> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "024d6544-7381-478e-bc5b-60c21f65c739">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.dinner") === -1) { vn.push("activity_date_night_hotel_angela.dinner"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>The hotel room is nicer than anywhere Jack has ever stayed. White sheets, city lights through floor-to-ceiling windows, a bottle of champagne sweating on the nightstand. Angela stands by the window, her back to you, looking out at the skyline.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Somewhere nobody knows us.</div></div><p>She turns. Her coat slides off her shoulders and underneath — black lingerie. Lace and straps and skin. She planned this. Every detail. The champagne, the room, what she's wearing underneath. She's been thinking about tonight for days.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You cross the room. Your hands find her waist and she inhales — sharp, nervous. Not the Angela who runs a household. The Angela who wore lingerie under her coat and booked a hotel room and kissed you on a sidewalk. She's shaking. So are you.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She pulls you onto the bed and straddles you. Kissing — deep, urgent, her hands in your hair. No penetration, not yet. Just mouths and skin and months of wanting crashing together.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She pulls back just far enough to look at you. Her lipstick is smeared. Her eyes are bright.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "9fd67a76-3267-4058-93a2-6507334d8897">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.arrival") === -1) { vn.push("activity_date_night_hotel_angela.arrival"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_4">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She takes your hands and places them on the straps of her lingerie. Her fingers guide yours. A permission. An invitation. Her breath catches when the lace starts to move.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You slide the straps down her shoulders. Slowly — like unwrapping something precious. The lingerie falls and she's bare from the waist up. Her breasts in the light from the window. She watches your face, looking for something. She finds it.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You kiss her stomach. The soft curve below her ribs. Her hip bone. The inside of her wrist. You're learning her body like a language you've only ever read.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She pulls you up and wraps herself around you. Grinding — slow, no rush. Skin on skin for the first time without walls or interruptions. The hotel room gives you permission to take your time.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Both of you naked. Foreheads together. Breathing the same air. Neither of you moves. The city hums outside and inside this room there is nothing but the two of you and the thing you've been building since the day you arrived.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "a6868e87-06ef-46c2-b330-f41e20f36718">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.undressing") === -1) { vn.push("activity_date_night_hotel_angela.undressing"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_5">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>The room narrows to just the two of you. Nothing exists outside this bed.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She rides you with her hands on your chest, grinding deep. Then your hands are everywhere — her breasts, her neck, the small of her back. Her mouth finds your shoulder, your jaw, your ear. Mouths and fingers learning what the other one needs.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She slides down and takes you in her mouth. Not teasing — giving. Her eyes close and she gives herself to it completely. Your hand finds the back of her head and she pulls you deeper.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She guides you inside her. The first time. She gasps and her nails dig into your back and for a moment neither of you breathes. Then you move — together, finding a rhythm, her legs wrapped around you, her mouth against your neck making sounds that rewrite everything you thought you knew about her. The first peak hits and you hold each other through it, gasping, clutching, the sheets twisted around your legs.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "30b9596a-b537-4c44-a999-f40f4ab1cbc7">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.first_wave") === -1) { vn.push("activity_date_night_hotel_angela.first_wave"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_6">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>You stop. Both of you. The urgency fades and something quieter takes its place. She's lying beside you, her leg hooked over yours, her hand on your chest.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You look at each other. No performance, no next move. Just looking. Her hair is a mess. Her makeup is smeared. She's never been more beautiful.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her fingertips trace your jaw. Your lips. The line of your collarbone. You do the same — the curve of her shoulder, the hollow of her throat. Mapping each other in silence.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I didn't know it could be like this. With someone. With you.</div></div><p>She says it without looking away. For a moment, neither of you is a step-mom or a step-son. You're just two people who found each other in a hotel room where nobody knows your names.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "80eb4aaa-97a7-4a5f-ac68-1f9cd1f83f3d">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.intimate_pause") === -1) { vn.push("activity_date_night_hotel_angela.intimate_pause"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_7">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>The pause ends slowly. Her hand finds yours. Then her lips find your neck. Then her teeth. The second wave doesn't crash — it rises.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_015.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_016.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She takes you in her mouth again — slower this time, deliberate, savoring. Then she pulls up and kisses you, her hand still working, and you're both smiling between kisses. The desperation is gone. This round is play.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_017.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your hand slides between her legs and she opens for you. You watch her face — learning what makes her breath catch, what makes her hips tilt. Her hand grips your wrist, not stopping you. Holding on.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_018.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_019.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You slide inside her and this time it's different — slower, faces inches apart, whispering things that don't quite form sentences. Between thrusts, her hands trace your back, your arms. Tenderness and hunger braided together.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_020.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>The rhythm finds you again. Deeper. Faster. Her legs wrap around you and she pulls you in with her heels. Both of you chasing the same thing. Both of you finding it.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "4b3bb14e-6a97-47f3-984e-a7c555bbd0f9">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.second_wave") === -1) { vn.push("activity_date_night_hotel_angela.second_wave"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_8">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_021.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your fingers intertwine on the pillow. She squeezes. You squeeze back. The room is quiet except for your breathing and the distant sound of the city.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_022.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She rolls onto her stomach and you trace the line of her spine with your fingertips. She shivers. She reaches back and rubs your shoulder. Giving and taking at the same time.</p><p>One last look between you. Her eyes say what her mouth doesn't — that this isn't enough. That she's not ready for this night to end.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_023.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>The last time is the most honest. No technique, no performance. Just her body and yours and the desperate need to be as close as two people can be. She comes with your name on her lips and you follow her over the edge.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "52fc6e05-d498-4b2f-a422-8fa760c6857f">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.afterglow") === -1) { vn.push("activity_date_night_hotel_angela.afterglow"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_date_night_hotel_angela_Node_9">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>Morning. The champagne is flat. The candles burned out hours ago. She's dressed — the black dress from dinner, her hair pulled back. She sits on the edge of the bed and looks at you.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_024.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She doesn't say goodbye. She traces your jawline with her thumb. Her eyes memorize your face like she's afraid this room will disappear when she walks out.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ADULT TIME - Naturally Stacked Angela White Has Romantic Sex With Her Husband At The Hotel/clip_025.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>The door clicks shut. You lie there. The hotel room smells like her perfume and champagne and sex and something you can't name.</p><p>Something has changed. Not just between you and Angela. Something inside you. You walked into this hotel as a kid crashing at his step-mom's place. You're leaving as something else entirely.</p> <<set $game_state.current_canvas = "d0929ed7-f634-4c19-b048-29d4ea77ef9c">> <<set $game_state.current_node = "a1b1a0d9-c7c5-4cda-9b2b-4ce44084ed75">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_date_night_hotel_angela.departure") === -1) { vn.push("activity_date_night_hotel_angela.departure"); }<</script>> <<script>>advanceTime(120);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 8.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 5.0, true, null);setup.applyAndNotifyTrait("player", null, "money", "add", -300.0, false, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "date_night_complete");setup.applyAndNotifyFlag("player", null, "sex_unlocked");<</script>> [[Return home->Location_Hotel_Room]]
<p>You're doing the dishes. Angela is at the table, her wine glass almost empty. She's been quiet tonight — not distant, just thoughtful.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Jack?</div></div><p>You turn off the faucet. She's looking at you with something you haven't seen before. Not desire. Not tenderness. Something closer to courage.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I want to go somewhere with you. Somewhere that isn't... here.</div></div><p>She gestures at the apartment — at the walls that know who you are to each other.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Somewhere nobody knows us.</div></div> <<set $game_state.current_canvas = "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2">> <<set $game_state.current_node = "9d39f4be-92c8-4c9b-8632-3d828550e00d">> <<script>>setup.markCanvasTriggered("c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("event_date_proposal.suggestion") === -1) { vn.push("event_date_proposal.suggestion"); }<</script>> <<nobr>> <<link "Continue" "Canvas_event_date_proposal_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She pulls out her phone and shows you a hotel — downtown, nice but not flashy. A place where no one asks questions.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Dinner first. A real dinner, at a real restaurant. Then...</div></div><p>She doesn't finish the sentence. She doesn't need to.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> It's expensive. Three hundred, maybe. But I want one night where we're not... whatever we are in this apartment.</div></div><p>She looks down at her wine. Then back at you.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Save up. When you're ready, I'll be ready.</div></div> <<set $game_state.current_canvas = "c2292eb7-9ba0-45f4-95b1-5a0b1fd08dd2">> <<set $game_state.current_node = "7af5c8fb-1237-4c59-9ab8-12a96c14a558">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("event_date_proposal.the_plan") === -1) { vn.push("event_date_proposal.the_plan"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 3.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "date_proposed");<</script>> [[Promise her->Location_Kitchen]]
<p>Your room. The bed Angela made up for you on that first day. A desk by the window, your bag half-unpacked in the corner. It's starting to feel like yours.</p> <<set $game_state.current_canvas = "5d6a739c-bc13-4c0d-876f-2776a73fb52a">> <<set $game_state.current_node = "37d960b0-6acb-4ec2-8fba-b399009f33b5">> <<script>>setup.markCanvasTriggered("5d6a739c-bc13-4c0d-876f-2776a73fb52a");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_jacks_room.room") === -1) { vn.push("activity_jacks_room.room"); }<</script>> <<nobr>> <<link "Rest for a bit" "Location_Jack's_Bedroom">><<script>>advanceTime(120);<</script>><</link>><br> <<link "Go to sleep" "Location_Jack's_Bedroom">><<script>>advanceTime(540);<</script>><</link>><br> <</nobr>>
<p>The fridge is getting bare. You grab your wallet and head to the corner store. Coffee, milk, eggs, bread. The basics. You add a bottle of the wine she likes — the one with the green label.</p><p>Back at the apartment, you stock the fridge. She'll open it tomorrow morning and see it full. That's the kind of thing that matters more than words.</p> <<set $game_state.current_canvas = "810f0d67-837a-4a58-9eda-bd895f2b821a">> <<set $game_state.current_node = "7646904a-d91a-4ceb-957f-3d4aa1cb50b7">> <<script>>setup.markCanvasTriggered("810f0d67-837a-4a58-9eda-bd895f2b821a");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_grocery_shopping.shopping") === -1) { vn.push("activity_grocery_shopping.shopping"); }<</script>> <<script>>advanceTime(60);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, true, null);setup.applyAndNotifyTrait("player", null, "money", "add", -20.0, false, null);setup.showEffectNotification();<</script>> [[Head home->Location_Street]]
<p>The laundry basket is overflowing. You sort through it — your clothes, her clothes. You try not to think too much about the latter.</p><p>Load the machine, add detergent, start the cycle. While it runs, you fold what's already dry. Her shirts, your jeans. Domestic, ordinary, and strangely intimate.</p> <<set $game_state.current_canvas = "6f5b0f28-b244-4b13-ac82-95fba4cee208">> <<set $game_state.current_node = "5573bb8b-ef57-4476-beed-1ff982861ffd">> <<script>>setup.markCanvasTriggered("6f5b0f28-b244-4b13-ac82-95fba4cee208");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_do_laundry.laundry") === -1) { vn.push("activity_do_laundry.laundry"); }<</script>> <<script>>advanceTime(45);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, true, null);setup.showEffectNotification();<</script>> [[Fold and put away->Location_Bathroom]]
<p>The living room needs work. Cushions out of place, dust on the shelves, her wine glass from last night still on the side table. You find the vacuum in the closet and get to work.</p><p>Forty-five minutes later, the room looks different. Not just clean — cared for. You put her glass in the dishwasher and straighten the blanket on the couch.</p> <<set $game_state.current_canvas = "369cdcd8-2094-4c08-8535-727c194a297b">> <<set $game_state.current_node = "91479b69-7a06-4f5c-8f60-24f32f6d3d0d">> <<script>>setup.markCanvasTriggered("369cdcd8-2094-4c08-8535-727c194a297b");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_clean_apartment.cleaning") === -1) { vn.push("activity_clean_apartment.cleaning"); }<</script>> <<script>>advanceTime(45);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, true, null);setup.showEffectNotification();<</script>> [[Done->Location_Living_Room]]
<p>You open the fridge and figure out what's there. Nothing fancy — pasta, vegetables, a jar of sauce. You start cooking.</p><p>The apartment fills with the smell of food. It's the first time you've cooked here. You set two plates at the counter.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> You cooked?</div></div><p>She doesn't say anything else. She sits down and eats. But she eats slowly, like she's tasting it carefully. Like it matters.</p> <<set $game_state.current_canvas = "0af73f66-ab63-4be8-a6d7-16567c83c7ee">> <<set $game_state.current_node = "014fb280-9bbe-4c49-84eb-b1310f12c663">> <<script>>setup.markCanvasTriggered("0af73f66-ab63-4be8-a6d7-16567c83c7ee");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_cook_dinner.cooking") === -1) { vn.push("activity_cook_dinner.cooking"); }<</script>> <<script>>advanceTime(60);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.showEffectNotification();<</script>> [[Clean up->Location_Kitchen]]
<p>The sink is full from breakfast. You roll up your sleeves and start scrubbing. Plates, mugs, the pan she used. The hot water is almost meditative.</p><p>When you're done, the kitchen looks like it did when you moved in. Clean counters. Drying rack full. She'll notice.</p> <<set $game_state.current_canvas = "25b75401-e63e-49aa-9293-9c5e8c6ebabf">> <<set $game_state.current_node = "0422dfa0-9ec8-4c2e-ab61-6503cd5f7d69">> <<script>>setup.markCanvasTriggered("25b75401-e63e-49aa-9293-9c5e8c6ebabf");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_wash_dishes.dishes") === -1) { vn.push("activity_wash_dishes.dishes"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, true, null);setup.showEffectNotification();<</script>> [[Done->Location_Kitchen]]
<p>Another week. You count out two hundred dollars and leave it on the kitchen counter.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> You don't have to keep doing this, you know.</div></div><p>But she takes it. And that evening, dinner is a little better than usual.</p> <<set $game_state.current_canvas = "5dff160b-08c7-45c8-9954-44d5f86becdf">> <<set $game_state.current_node = "eb1892be-06cf-4a3f-b2c0-c7ab86af1d23">> <<script>>setup.markCanvasTriggered("5dff160b-08c7-45c8-9954-44d5f86becdf");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_weekly_rent.n1") === -1) { vn.push("activity_weekly_rent.n1"); }<</script>> <<script>>advanceTime(15);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("player", null, "money", "add", -200.0, false, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "rent_last_paid");<</script>> [[Done->Location_Kitchen]]
<p>The afternoon crowd is lighter — students with laptops, a couple sharing a slice of cake. You clean tables between orders.</p><p>You think about what Angela might be doing right now. The couch. A book. Her bath. You shake it off and finish your shift.</p> <<set $game_state.current_canvas = "31a70088-6fd9-4fb3-b412-0c471b8896e1">> <<set $game_state.current_node = "f50635aa-a001-4667-8644-a0df897d4e1b">> <<script>>setup.markCanvasTriggered("31a70088-6fd9-4fb3-b412-0c471b8896e1");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_cafe_shift_afternoon.n1") === -1) { vn.push("activity_cafe_shift_afternoon.n1"); }<</script>> <<script>>advanceTime(180);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("player", null, "money", "add", 70.0, false, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, true, null);setup.showEffectNotification();<</script>> [[Finish shift->Location_Cafe]]
<p>The cafe smells like ground coffee and fresh pastries. You tie on your apron and get to work. Regulars come and go. Tips are decent.</p><p>Your phone buzzes once — a message from Angela. 'Missed you at breakfast.' You pocket it and keep working.</p> <<set $game_state.current_canvas = "24f8a779-39d5-4312-be02-81f6510c4788">> <<set $game_state.current_node = "b645f699-9019-4959-9ee3-3ec24da39fe2">> <<script>>setup.markCanvasTriggered("24f8a779-39d5-4312-be02-81f6510c4788");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_cafe_shift_morning.n1") === -1) { vn.push("activity_cafe_shift_morning.n1"); }<</script>> <<script>>advanceTime(180);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("player", null, "money", "add", 70.0, false, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, true, null);setup.showEffectNotification();<</script>> [[Finish shift->Location_Cafe]]
<p>The TV is off tonight. The living room is lit by the floor lamp in the corner — warm, low. Angela is on the couch with a glass of wine, her feet tucked under her. There's a second glass on the coffee table. She poured it before you got here.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I was hoping you'd come sit with me.</div></div><p>You sit. Not at the far end — close. She shifts so her knee touches yours. The apartment is quiet except for the city outside and the faint hum of the fridge in the kitchen. She takes a sip of wine and looks at you over the rim of the glass.</p><p>Some nights are like this. The two of you end up on this couch and the conversation goes somewhere real — past the safe topics, past the daily logistics of rent and groceries and who's cooking tomorrow. She asks questions that don't have easy answers. She listens like what you say matters.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Can I ask you something? I keep thinking about this. About us. Not in a bad way — I just... I want to understand it.</div></div><p>She tucks a strand of hair behind her ear. It's the thing she does when she's nervous. Her eyes find yours and stay there. The wine has loosened something in her, or maybe it's the hour, or maybe she's just tired of not saying what she's thinking.</p><p>The space between you feels charged. Not with desire — with honesty. The kind of conversation where everything you say reveals more than you intended.</p> <<set $game_state.current_canvas = "e4698ed6-1afc-440b-aa47-039c91a7f904">> <<set $game_state.current_node = "70c648bf-6fb6-44b0-9a42-05b0910c6712">> <<script>>setup.markCanvasTriggered("e4698ed6-1afc-440b-aa47-039c91a7f904");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_deep_conversation_angela.n1") === -1) { vn.push("activity_deep_conversation_angela.n1"); }<</script>> <<nobr>> <<link "Listen and ask questions" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(90);<</script>><</link>><br> <<link "Share something personal" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(90);<</script>><</link>><br> <<link "Deflect with humor" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>The apartment is quiet. Angela's bedroom door is open — not an accident, not anymore. She's under the covers, the bedside lamp casting warm light across the pillows. She looks up when you appear in the doorway.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Come to bed.</div></div><p>Two words. No question mark. You undress in the dark and slide in beside her. The sheets are warm from her body.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She tucks herself against you — her head on your shoulder, her hand flat on your chest. Her breathing slows. Her leg hooks over yours. For a while, neither of you speaks. The room smells like her shampoo and the candle she blew out an hour ago. You don't need words for this.</p> <<set $game_state.current_canvas = "0eb588c2-f2de-4d84-a239-c279ea4d475d">> <<set $game_state.current_node = "14b8c0ce-5b97-4e4b-a780-35b4cb18f2b0">> <<script>>setup.markCanvasTriggered("0eb588c2-f2de-4d84-a239-c279ea4d475d");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_together_angela.sleep") === -1) { vn.push("activity_morning_together_angela.sleep"); }<</script>> <<nobr>> <<link "Fall asleep with her" "Canvas_activity_morning_together_angela_Node_2">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(420);<</script>><</link>><br> <</nobr>>
<p>You wake up first. She's still pressed against you, one arm draped across your chest. Morning light edges through the curtains. You can feel her heartbeat against your ribs.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She stirs. Her fingers curl against your shoulder. Her eyes open — unfocused, then finding you. The smile that comes is unguarded. The kind of smile people only make before they're fully awake.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She pulls you into a slow, sleepy kiss. Morning breath and warm skin and neither of you caring. Her fingers trace the line of your jaw.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Morning.</div></div><p>She says it like it's the most natural thing in the world. Like you've always been here. Like this is how every morning should start.</p> <<set $game_state.current_canvas = "0eb588c2-f2de-4d84-a239-c279ea4d475d">> <<set $game_state.current_node = "075e5381-c65b-40fb-a39b-83be62c50384">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_together_angela.wakeup") === -1) { vn.push("activity_morning_together_angela.wakeup"); }<</script>> <<nobr>> <<link "Cuddle and talk" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 72, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('0eb588c2-f2de-4d84-a239-c279ea4d475d:cc0') ? '' : 'unlocked-choice'"> <<link "Touch her" "Canvas_activity_morning_together_angela_Node_3">><<script>>setup.markChoiceVisited("0eb588c2-f2de-4d84-a239-c279ea4d475d:cc0");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('0eb588c2-f2de-4d84-a239-c279ea4d475d:cc1') ? '' : 'unlocked-choice'"> <<link "Pull her close" "Canvas_activity_morning_together_angela_Node_4">><<script>>setup.markChoiceVisited("0eb588c2-f2de-4d84-a239-c279ea4d475d:cc1");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 92, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('0eb588c2-f2de-4d84-a239-c279ea4d475d:cc2') ? '' : 'unlocked-choice'"> <<link "Take her breath away" "Canvas_activity_morning_together_angela_Node_5">><<script>>setup.markChoiceVisited("0eb588c2-f2de-4d84-a239-c279ea4d475d:cc2");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <</nobr>>
<p>She turns toward you under the sheets. Her leg slides over yours, her thigh pressing against you. She can feel you're hard already. Her hand slides down your stomach.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You push the sheets aside and your mouth finds her thigh — the soft inner skin, the place that makes her gasp. She opens her legs wider, her fingers gripping the pillow.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She straddles you, grinding down slowly. Her forehead presses against yours. You can feel how wet she is through the thin fabric — then there's no fabric, just skin on skin, and she rocks against you with her eyes closed.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (2)/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (2)/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She slides down your body and takes you in her mouth. Playful at first — her tongue teasing, her eyes looking up at you with a smile. Then deeper, her hand gripping the base, her head bobbing with a rhythm that makes your hips lift off the bed. She pulls you to the edge and holds you there.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She sits up and presses you between her breasts, her hands squeezing them together around you. She spits on the head and works you slowly, watching your face, enjoying the control.</p><p>She traces a finger along your collarbone. Her chest is flushed. Her hair is wrecked.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I didn't know it could feel like this.</div></div><p>She says it quietly. Not about the sex. About waking up next to someone who stays.</p> <<set $game_state.current_canvas = "0eb588c2-f2de-4d84-a239-c279ea4d475d">> <<set $game_state.current_node = "ab77662f-1c9c-4d22-93ed-c7edcfde3520">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_together_angela.foreplay") === -1) { vn.push("activity_morning_together_angela.foreplay"); }<</script>> <<nobr>> <<link "Cuddle and talk" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <<link "Initiate intimacy" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>She rolls toward you. Her hand traces down your chest, past your navel. She doesn't ask. She doesn't need to. Her fingers wrap around you and she guides you inside her.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (2)/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She's warm from sleep. You move together slowly — deep, rhythmic, unhurried. Morning sex, no performance. She pulls you down and kisses you mid-thrust, swallowing the sound you make.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (2)/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You shift positions like you've done this a hundred times — her on top, then you, then side by side. The rhythm changes but doesn't break. Her back arches and she grabs your hand, pressing it against her breast.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_019.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (2)/clip_020.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>The pace builds. She wraps her legs around you, pulling you deep, her heels digging into your back. Both of you are breathing hard. The sheets are tangled around your hips.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She finds your hand and laces her fingers through yours. You can feel her tightening around you. Her mouth opens and she says your name — not a moan, not a whisper. Just your name, like it means everything.</p><p>She buries her face in your neck and holds on. You can feel her heartbeat everywhere.</p> <<set $game_state.current_canvas = "0eb588c2-f2de-4d84-a239-c279ea4d475d">> <<set $game_state.current_node = "fe947c53-60ca-4c89-affb-25da7bc6f864">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_together_angela.sex") === -1) { vn.push("activity_morning_together_angela.sex"); }<</script>> <<nobr>> <<link "Cuddle and talk" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <<link "Initiate again" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>The bedroom is dark except for a single candle she lit. You've been here before, but tonight feels different. Weightier. More real. She's already naked, already waiting, and the look on her face isn't lust. It's need.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_015.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She turns over and you take her from behind. Not gentle — she doesn't want gentle tonight. She pushes back against you, her fists in the sheets, each thrust landing hard. She makes sounds she's never made with you before.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_016.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>The angle changes. She reaches back to guide you and you feel the tightness, the resistance giving way. She exhales slowly, adjusting, then starts to move against you. Deliberate. Connected. Both of you breathing hard, the candlelight flickering on the wall.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_017.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You turn her over. Face to face in the dark. Her legs wrap around you and she whispers between thrusts — your name, broken sentences, things you'll both remember and never repeat.</p><video src="./videos/angela_white/clips/480p.h264 (2)/clip_018.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Everything slows. You move inside her with your eyes locked and there's nothing left — no irony, no distance, no step-mother, no step-son. Just two people who chose each other in the dark.</p><p>Afterward, she curls into you. Her hand finds yours and she holds it against her chest, over her heart. The candle has burned down to nothing.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Stay. Please.</div></div><p>You're not sure if she means tonight or forever. You don't ask.</p> <<set $game_state.current_canvas = "0eb588c2-f2de-4d84-a239-c279ea4d475d">> <<set $game_state.current_node = "a6c5191d-f4c5-40a6-a9e7-63c64dd31de5">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_together_angela.intense") === -1) { vn.push("activity_morning_together_angela.intense"); }<</script>> <<nobr>> <<link "Cuddle and talk" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <<link "Initiate again" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>Angela's bedroom door is open. You glance in as you pass — you always glance now. She's still in bed, morning light pooling across the white sheets. Her nightgown has ridden up past her thighs.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (1)/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She stirs, stretches, pushes the covers aside. You catch a flash of bare legs, the curve of her hip under cotton. She hasn't seen you. You should keep walking.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "e732efed-c838-4e85-a1e0-d5417e800502">> <<script>>setup.markCanvasTriggered("66aa2625-2586-4275-852b-9cb50879f43d");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.approach") === -1) { vn.push("activity_morning_angela.approach"); }<</script>> <<nobr>> <<link "Keep walking" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(30);<</script>><</link>><br> <<link "Watch from the door" "Canvas_activity_morning_angela_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <<if setup.triggerConditionsSatisfied({"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "towel_encounter_complete", "operator": "is_true"}, {"type": "flag", "subject": "player", "flag_key": "massage_offered", "operator": "is_true"}, {"type": "days_since_flag", "value": 2, "subject": "player", "flag_key": "towel_encounter_complete", "operator": "gte"}, {"type": "trait", "value": 60, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "trait", "value": 22, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "trust"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('66aa2625-2586-4275-852b-9cb50879f43d:cc0') ? '' : 'unlocked-choice'"> <<link "Step inside" "Canvas_activity_morning_angela_Node_6">><<script>>setup.markChoiceVisited("66aa2625-2586-4275-852b-9cb50879f43d:cc0");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <</nobr>>
<p>You don't keep walking. You stop just past the doorframe, your shoulder against the wall. Through the gap you can see her.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She shrugs off her robe and it pools at her feet. Underneath — just a bra and underwear. She stretches, arms over her head, her back arching. Her breasts lift against the thin fabric. Morning light traces the line of her waist, the soft dip of her stomach.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She crawls across the bed to reach something on the nightstand — her phone, maybe. On all fours, her back curved, her ass in the air. She has no idea what that looks like from the doorway.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "6b8bfcc4-1ce1-4e7c-8218-82f0d685a91a">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.peek_routine") === -1) { vn.push("activity_morning_angela.peek_routine"); }<</script>> <<nobr>> <<link "Continue watching" "Canvas_activity_morning_angela_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She's changing. The shirt comes open, button by button, and she lets it fall.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her breasts are heavy, full, the nipples tightening in the cool morning air. She doesn't rush to cover them. She sits on the edge of the bed, nude, and looks out the window like she has all the time in the world.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_016.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (1)/clip_017.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (1)/clip_018.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She lies face-down on the sheets, her body a long curve of warm skin in the light. When she rolls over, everything is visible — her breasts falling to the sides, the plane of her stomach, the dark strip between her thighs. She's not posing. She's just comfortable. That's what makes it devastating.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_019.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (1)/clip_020.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She sits up and runs her fingers through her hair, her back arched, her shoulders rolling. Then she stands — one final stretch, arms above her head, her whole body taut — and for a moment she's the most beautiful thing you've ever seen. She reaches for a towel and the spell breaks.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "21bf669d-dce6-43e9-8132-68a6489be26c">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.peek_nude") === -1) { vn.push("activity_morning_angela.peek_nude"); }<</script>> <<nobr>> <<link "Continue watching" "Canvas_activity_morning_angela_Node_4">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She's not getting dressed. She's lying on the bed, still in her underwear, and her hands are moving — not with purpose, but with curiosity. Like she's rediscovering herself.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (1)/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her fingers trail down her stomach, over her hipbone. She hooks a thumb under her underwear and pulls them aside. Her fingers brush against herself — light, testing. Her breath hitches. She does it again, less gently.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264 (1)/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her hand settles between her legs. Slow circles, her hips rocking slightly against her own fingers. She's not rushing — she's finding what works, adjusting pressure, her lips parting each time she hits the right spot.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her fingers push deeper. She bites her lip and her eyes flutter shut. A small sound escapes her — involuntary, private. You're watching your step-mother touch herself in her bedroom at seven in the morning, and you can't make yourself look away.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "030283cc-fa6c-45f5-8794-88c11a577ccd">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.peek_exploration") === -1) { vn.push("activity_morning_angela.peek_exploration"); }<</script>> <<nobr>> <<link "Continue watching" "Canvas_activity_morning_angela_Node_5">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>Her underwear is off now. Her knees are apart and her hand is between her legs — no hesitation, no curiosity anymore. She knows what she wants. Her fingers move in quick, tight circles, and her breathing has changed. Faster. Deliberate.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her hips lift off the mattress. Her free hand grips the sheet, twisting it. She's close — you can tell from the way her thighs are trembling, the way her jaw is clenched.</p><video src="./videos/angela_white/clips/480p.h264 (1)/clip_015.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her back arches off the bed. A moan — low, guttural, nothing like her careful speaking voice. Her whole body shakes and then goes limp. She lies there panting, one hand still between her legs, the other thrown above her head. The sheets are twisted around her.</p><p>You step back. Your heart is in your throat. Your body is telling you things your mind doesn't want to hear.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "c4d086b2-24e6-4899-928c-04dca5821a0c">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.peek_private") === -1) { vn.push("activity_morning_angela.peek_private"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, true, null);setup.showEffectNotification();<</script>> [[Keep walking->Location_Angela's_Bedroom]]
<p>You push the door open. Morning light fills the room — warm, golden, filtering through the curtains. She's lying on the bed in her nightgown, one strap fallen off her shoulder. Not sleeping. Not getting up. Waiting for something she hasn't named yet.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She looks up and sees you. Her hand goes to the fallen strap — not to fix it. To pull the other one down.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Jack.</div></div><p>Just your name. But the way she says it — soft, deliberate, like she's been rehearsing it — is an invitation.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "266b9b89-dd3a-4569-8f1e-0fdbbae25b35">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.encounter_discovery") === -1) { vn.push("activity_morning_angela.encounter_discovery"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_morning_angela_Node_7">><<script>>advanceTime(3);<</script>><</link>><br> <<link "Leave quietly" "Canvas_activity_morning_angela_Node_9">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>You step inside and close the door behind you. The click of the latch is the loudest sound in the room.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She watches you from the bed — her eyes moving down your body, lingering. She's not shy about it. She sits up slowly, her nightgown falling open, and reaches for the waistband of your shorts.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She takes you in her mouth — warm, slow, her tongue tracing the underside as she sinks down. Her eyes stay on yours. She's unhurried, like this is exactly where she planned to be this morning. Her hand grips the base and her head bobs slowly, each stroke deliberate, pulling sounds out of you that you can't control.</p><p>She pulls back, her lips wet, and looks up at you. Her thumb traces a lazy circle on your hip.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "3894ad6a-bcde-48ae-a0ed-0146971c876e">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.encounter_oral") === -1) { vn.push("activity_morning_angela.encounter_oral"); }<</script>> <<nobr>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "trait", "value": 80, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('66aa2625-2586-4275-852b-9cb50879f43d:cc1') ? '' : 'unlocked-choice'"> <<link "Continue" "Canvas_activity_morning_angela_Node_8">><<script>>setup.markChoiceVisited("66aa2625-2586-4275-852b-9cb50879f43d:cc1");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<link "Hold her close" "Location_Angela's_Bedroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 5.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(120);<</script>><</link>><br> <</nobr>>
<p>Something breaks open between you. The careful distance you've maintained, the polite silences, the measured smiles — all of it falls away.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She takes you deeper, her confidence growing. Her hand grips your thigh and she moans around you — a vibration that runs up your spine. Then she pulls back, lips swollen, eyes dark.</p><p>Without a word she lies back and draws you down on top of her. Her nightgown is gone — you're not sure when it came off.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You push inside her and she gasps — a sharp intake that turns into something low and continuous. She's tight and warm and she wraps her legs around you, pulling you deeper. Her nails rake down your back. You'll find the marks tomorrow.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>The rhythm builds. Her breasts move with each thrust and you cup one, your thumb brushing her nipple. She arches into your hand. Her hips meet yours stroke for stroke — urgent now, both of you chasing something.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_015.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She turns over and you take her from behind. She fists the sheets and pushes back against you. Each thrust drives a sound out of her — small, desperate, building. The headboard taps the wall.</p><p>She turns to face you. Hair undone, skin flushed, eyes wide. She wants to see your eyes.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_016.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Face to face again. She pulls you down until your foreheads touch. You can feel her breath on your lips, every sound she makes.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I'm yours, Jack. Not because I should be. Because I want to be.</div></div><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - Busty Slut Fuck Her Step Bro/clip_017.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She pushes you onto your back and finishes you with her mouth — slow, tender, her hand pressed flat against your chest like she's feeling your heartbeat. When she's done, she rests her cheek on your hip.</p><p>The morning light has shifted across the room. The sheets are ruined. Neither of you cares.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "ac8b32c9-8113-4112-a9cd-d42715c60846">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.encounter_sex") === -1) { vn.push("activity_morning_angela.encounter_sex"); }<</script>> <<script>>advanceTime(120);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 8.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 5.0, true, null);setup.showEffectNotification();<</script>> [[Stay with her->Location_Angela's_Bedroom]]
<p>You step back from the doorway. She doesn't see you leave. Or maybe she does, and she lets you go.</p><p>In the kitchen, you make coffee and try not to think about what you almost did.</p> <<set $game_state.current_canvas = "66aa2625-2586-4275-852b-9cb50879f43d">> <<set $game_state.current_node = "896b72ae-455f-4126-ac4a-1b5769fcdbad">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_morning_angela.encounter_leave") === -1) { vn.push("activity_morning_angela.encounter_leave"); }<</script>> <<script>>advanceTime(30);<</script>> [[Go to the kitchen->Location_Angela's_Bedroom]]
<p>You're at the bathroom door again. The gap in the door, the sound of water, the steam curling into the hallway. By now you know the routine.</p><video src="./videos/angela_white/clips/480p.h264/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>But tonight, something is different. She shifts in the water — and her eyes find yours through the gap.</p><p>You freeze. She doesn't.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> How long have you been standing there?</div></div><p>Her voice is calm. Not angry. Not surprised. Like she's been waiting for this moment.</p> <<set $game_state.current_canvas = "cdd88fe2-180f-4acd-9c09-fc688a78f760">> <<set $game_state.current_node = "e693401f-5e18-4f37-9603-3a66ac80cc91">> <<script>>setup.markCanvasTriggered("cdd88fe2-180f-4acd-9c09-fc688a78f760");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("event_bath_invitation.caught") === -1) { vn.push("event_bath_invitation.caught"); }<</script>> <<nobr>> <<link "Continue" "Canvas_event_bath_invitation_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She holds your gaze. Then she smiles — slow, knowing, a little dangerous.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Come here.</div></div><p>You step into the bathroom. The steam is thick and warm. She doesn't cover herself. She watches you approach — naked in the water, completely at ease.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She cups water in her hands and lets it run down her chest. Washing — but not like she's alone anymore. Every movement is unhurried, deliberate. She knows your eyes are on her skin.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She reaches behind to scrub her back, arching forward. Her breasts lift with the motion. Skin glistening, water trailing down her spine.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She pours oil into her palms and slides her hands over her arms, her stomach, the tops of her thighs. The water clouds around her. She takes your hand and pulls it into the water. Her fingers lace through yours.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I knew you were watching. I've known for a while.</div></div><p>She presses your hand against her chest. You can feel her heartbeat — fast, steady, certain.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I didn't mind.</div></div> <<set $game_state.current_canvas = "cdd88fe2-180f-4acd-9c09-fc688a78f760">> <<set $game_state.current_node = "2ce4cb31-d555-4318-9f01-b882db914544">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("event_bath_invitation.invitation") === -1) { vn.push("event_bath_invitation.invitation"); }<</script>> <<nobr>> <<link "Continue" "Canvas_event_bath_invitation_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She knows you're watching now — really watching, not hiding behind a door. And she likes it. She leans back in the water and her hand moves to her breast.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She cups her breast, thumb circling her nipple. Her other hand drifts below the surface. You can see the motion of her wrist through the water — slow circles, her fingers finding herself. Her eyes stay on yours. She wants you to watch this.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She bites her lip. Her hand moves faster under the water. Her hips shift, her thighs parting wider. She's not bathing, not performing — she's touching herself for you, and the look on her face says she's been imagining this for longer than tonight.</p> <<set $game_state.current_canvas = "cdd88fe2-180f-4acd-9c09-fc688a78f760">> <<set $game_state.current_node = "1de4b8af-5734-4d63-a9f8-28cd8effb102">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("event_bath_invitation.showing_off") === -1) { vn.push("event_bath_invitation.showing_off"); }<</script>> <<nobr>> <<link "Continue" "Canvas_event_bath_invitation_Node_4">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She reaches for you — not your hand this time. Her fingers find your belt, your zipper. She pulls you to the edge of the tub and her mouth is on you before you can think.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She takes you in her mouth from the water. Warm, wet, her tongue pressing flat against the underside. Her hand grips the edge of the tub for balance, the other wrapped around the base. Water sloshes with every movement of her head. She looks up at you — hair wet, eyes bright, mouth full — and the sight of her is more than you can handle.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Water splashes over the edge of the tub. She doesn't care. Her rhythm builds — deeper, faster, her hand working in time with her mouth. Both of you are lost in it. The bathroom echoes with the sound of water and the sounds she pulls out of you.</p><p>Afterward, the bathroom is quiet. Just the sound of water dripping and her breathing against your skin. She rests her cheek on your thigh, her hand still on you, and the steam settles around you both.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Next time, get in the water with me.</div></div><p>She says it like a promise.</p> <<set $game_state.current_canvas = "cdd88fe2-180f-4acd-9c09-fc688a78f760">> <<set $game_state.current_node = "75083a51-65f1-490b-b7dc-fb05c5351f90">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("event_bath_invitation.oral") === -1) { vn.push("event_bath_invitation.oral"); }<</script>> <<script>>advanceTime(90);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 5.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.showEffectNotification();<</script>> <<script>>setup.applyAndNotifyFlag("player", null, "oral_unlocked");setup.applyAndNotifyFlag("player", null, "bath_invitation_complete");<</script>> [[Stay with her->Location_Bathroom]]
<p>The bathroom door is slightly ajar. Steam curls through the gap and you hear water — the slow slosh of someone settling in. You should keep walking.</p><video src="./videos/angela_white/clips/480p.h264/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Through the steam you catch a glimpse — her shoulders, the curve of her neck, the way she sighs as the hot water takes her weight. Your pulse is doing something it shouldn't. You know you should walk away.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "95ed6bd6-06c5-469e-a2ff-69fb94758a41">> <<script>>setup.markCanvasTriggered("87e07146-10fa-4562-8bcc-a74410148af3");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.approach") === -1) { vn.push("activity_bath_angela.approach"); }<</script>> <<nobr>> <<link "Leave quietly" "Location_Bathroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(30);<</script>><</link>><br> <<link "Watch from the door" "Canvas_activity_bath_angela_Node_2">><<script>>advanceTime(3);<</script>><</link>><br> <<if setup.triggerConditionsSatisfied({"items": [{"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}, {"type": "days_since_flag", "value": 1, "subject": "player", "flag_key": "bath_invitation_complete", "operator": "gte"}, {"type": "trait", "value": 65, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('87e07146-10fa-4562-8bcc-a74410148af3:cc0') ? '' : 'unlocked-choice'"> <<link "Join her" "Canvas_activity_bath_angela_Node_6">><<script>>setup.markChoiceVisited("87e07146-10fa-4562-8bcc-a74410148af3:cc0");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <</nobr>>
<p>You don't walk away. You lean against the wall beside the door, watching through the gap. She has no idea you're there.</p><video src="./videos/angela_white/clips/480p.h264/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She washes with the absent-minded ease of someone who thinks she's alone. Soap suds slide down her shoulders, along the swell of her breasts, disappearing into the water. She hums something you can't quite make out.</p><video src="./videos/angela_white/clips/480p.h264/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She lifts one leg out of the water to scrub it, the skin glistening. When she rinses, water cascades over her — her collarbones, her stomach, the dark patch between her thighs. She's not performing. She's just existing in her body. And you can't look away.</p><video src="./videos/angela_white/clips/480p.h264/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She tips her head back and closes her eyes. The water laps at her collarbone. She stretches — arms above her head, back arching — and for a moment her breasts break the surface, wet and gleaming in the bathroom light. She has no idea how beautiful she looks right now.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "aaaf03d6-6939-4bf7-b200-3f9be023c406">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.peek_routine") === -1) { vn.push("activity_bath_angela.peek_routine"); }<</script>> <<nobr>> <<link "Continue watching" "Canvas_activity_bath_angela_Node_3">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>Her eyes are half-closed. The washing is over. What she's doing now isn't about getting clean.</p><video src="./videos/angela_white/clips/480p.h264/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her hands cup her breasts, thumbs tracing slow circles over her nipples. She isn't rushing. Her lips part and she exhales — a sound that's more than a sigh. Less than a moan.</p><video src="./videos/angela_white/clips/480p.h264/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/480p.h264/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her hands drift lower — along her ribs, over the soft curve of her stomach, fingertips trailing through the water toward her hips. She's touching herself the way you've imagined touching her. Your mouth is dry.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "ba18c736-171f-4806-a6aa-b88ea4554a98">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.peek_intimate") === -1) { vn.push("activity_bath_angela.peek_intimate"); }<</script>> <<nobr>> <<link "Continue watching" "Canvas_activity_bath_angela_Node_4">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She picks up the loofah again. It moves across her stomach, her hip, her inner thigh. Then it slows. She presses it between her legs — not washing anymore. Her eyes flutter shut.</p><video src="./videos/angela_white/clips/480p.h264/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She rocks against it gently, the water rippling around her. Her free hand grips the rim of the tub. Her head tilts back and her breath comes in short, deliberate pulls. She's not bathing. She's using the loofah to get herself off, and she thinks no one can see.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "de4c8409-225e-47eb-854a-5280dafec2dd">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.peek_selftouch") === -1) { vn.push("activity_bath_angela.peek_selftouch"); }<</script>> <<nobr>> <<link "Continue watching" "Canvas_activity_bath_angela_Node_5">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>The loofah is gone. Her hand is between her legs now — no pretense, no prop. Just her fingers, moving in tight circles. Her breathing fills the bathroom — soft, rhythmic, getting faster.</p><video src="./videos/angela_white/clips/480p.h264/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her hips lift off the bottom of the tub. Her thighs clamp together around her hand. She bites her lower lip and a sound escapes — quiet, involuntary, the kind of sound she'd never make if she knew you were listening. The water sloshes and goes still. She lies there, chest heaving, eyes closed.</p><p>You step back from the door. Your heart is hammering. You're hard. And you're not sure if the guilt or the wanting is worse.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "0671f043-73e9-4904-aec1-8e4d44dac731">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.peek_private") === -1) { vn.push("activity_bath_angela.peek_private"); }<</script>> <<script>>advanceTime(30);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, true, null);setup.showEffectNotification();<</script>> [[Leave quietly->Location_Bathroom]]
<p>You push the door open. Angela looks up from the water — and smiles. Not startled. Not embarrassed. Like she was waiting for you to make up your mind.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Get in. The water's perfect.</div></div><p>You undress. She watches — not looking away, not pretending not to. When you step into the tub, the water rises and she shifts to make room, her legs sliding against yours under the surface.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She goes back to washing like this is normal. Her hands glide over her breasts, soap suds running down between them. She's completely unguarded — the way people are only when they trust someone.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She turns to scrub her back and you see the curve of her spine, the dimples above her hips. She pours oil into her palm and her hands slide over her skin — shoulders, stomach, thighs. The water goes cloudy and warm. Your knees are touching under the surface.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "e45b1b09-ec95-4095-b1ad-c43d249d048e">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.bath_self_care") === -1) { vn.push("activity_bath_angela.bath_self_care"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_bath_angela_Node_7">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>The washing stops. Her hand doesn't come back to the soap. It drifts to her breast, cupping it, her thumb brushing her nipple. She's looking right at you.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her other hand slips below the water. You can't see it, but you can see what it's doing to her — the way her lips part, the way her breathing changes. She's touching herself and she wants you to watch.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her fingers move faster. She bites her lip and her eyes stay locked on yours — not shy, not performing. Daring you. The water ripples with the motion of her hand. Her chest is flushed.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "2557e630-c374-4601-a624-6edee30df246">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.bath_self_pleasure") === -1) { vn.push("activity_bath_angela.bath_self_pleasure"); }<</script>> <<nobr>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}, {"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('87e07146-10fa-4562-8bcc-a74410148af3:cc1') ? '' : 'unlocked-choice'"> <<link "Continue" "Canvas_activity_bath_angela_Node_8">><<script>>setup.markChoiceVisited("87e07146-10fa-4562-8bcc-a74410148af3:cc1");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<link "Hold her close" "Location_Bathroom">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 3.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(90);<</script>><</link>><br> <</nobr>>
<p>She stands up in the tub. Water runs down her body in rivulets — between her breasts, down her stomach, off the curve of her hips. She turns slowly. She knows exactly what she's doing.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She faces the window, the light catching the water on her skin. Her back, the swell of her ass, the line of her thighs — she holds there, letting you look.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She looks over her shoulder at you. Then her hand reaches behind her — her fingers tracing down, finding the cleft of her ass. She presses one finger inside, slowly, and her eyes don't leave yours.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She works her finger deeper, her mouth falling open. She's watching your reaction the way a scientist watches an experiment — curious, pleased with the result. She pulls her hand away and turns to face you.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Your turn.</div></div> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "1d9427a7-c0c9-4df5-a3a1-27a6a084eef2">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.bath_for_you") === -1) { vn.push("activity_bath_angela.bath_for_you"); }<</script>> <<nobr>> <<link "Continue" "Canvas_activity_bath_angela_Node_9">><<script>>advanceTime(3);<</script>><</link>><br> <</nobr>>
<p>She kneels in the tub in front of you. Water laps at her waist. Her hands find your thighs under the surface, sliding upward. She wraps her fingers around you and looks up.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her mouth takes you in — warm, wet, unhurried. The water sloshes around you both as she finds her rhythm. Her tongue works the underside and she hums against you, the vibration running through your whole body. Her hand grips what her mouth can't reach.</p><video src="./videos/angela_white/clips/[PornhubFans 480p] ANGELA WHITE - POV Blowjob and Fucking in the Bath/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Water splashes over the edge of the tub and she laughs around you — the absurdity of it, the mess, the two of them in this tiny bathtub. She takes you deeper and the laugh becomes a moan. Your hand finds the back of her wet hair.</p><p>The water is lukewarm by the time she's done. She leans against you, skin to skin, her head on your chest. The bathroom is quiet except for the drip of the faucet and your breathing.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> This is my favorite room in the apartment now.</div></div><p>She says it with her eyes closed, tracing a line through the water on your chest.</p> <<set $game_state.current_canvas = "87e07146-10fa-4562-8bcc-a74410148af3">> <<set $game_state.current_node = "e6ee0f8c-5870-4db3-a8d5-182d1bd10451">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_bath_angela.bath_together") === -1) { vn.push("activity_bath_angela.bath_together"); }<</script>> <<script>>advanceTime(90);<</script>> <<script>>setup.pendingEffects = [];setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 5.0, true, null);setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 3.0, true, null);setup.showEffectNotification();<</script>> [[Dry off together->Location_Bathroom]]
<p>The living room is dim, the TV casting soft blue light across the ceiling. Angela is curled on the couch with a blanket over her legs, hair still damp from her evening shower. She's in an oversized t-shirt and nothing else — or nothing you can see.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Want to watch something? I was just flipping channels.</div></div><p>You sit down. Not too close, not too far. She picks a movie — something light, something neither of you will remember. Halfway through, her leg shifts under the blanket and presses against yours. Warm through the fabric. She doesn't move it. You don't move yours. The movie keeps playing.</p> <<set $game_state.current_canvas = "56f91831-8fbc-45d9-9b8a-b5cfb86cec81">> <<set $game_state.current_node = "14c4c5ed-020d-4462-b34a-a9894ddbc84d">> <<script>>setup.markCanvasTriggered("56f91831-8fbc-45d9-9b8a-b5cfb86cec81");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_movie_night_angela.evening") === -1) { vn.push("activity_movie_night_angela.evening"); }<</script>> <<nobr>> <<link "Watch together quietly" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(90);<</script>><</link>><br> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 50, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc0') ? '' : 'unlocked-choice'"> <<link "Move closer to her" "Canvas_activity_movie_night_angela_Node_2">><<script>>setup.markChoiceVisited("56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc0");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 65, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc1') ? '' : 'unlocked-choice'"> <<link "Slide your hand under the blanket" "Canvas_activity_movie_night_angela_Node_3">><<script>>setup.markChoiceVisited("56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc1");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "sex_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc2') ? '' : 'unlocked-choice'"> <<link "Pull her onto your lap" "Canvas_activity_movie_night_angela_Node_4">><<script>>setup.markChoiceVisited("56f91831-8fbc-45d9-9b8a-b5cfb86cec81:cc2");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <</nobr>>
<p>You shift closer. Your thigh presses against hers. She doesn't look at you, but her hand moves under the blanket and finds your leg. Her fingertips trace up your inner thigh — slow, deliberate, like she's been thinking about this since you sat down.</p><video src="./videos/angela_white/clips/angela_white_4/clip_013.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her hand wraps around you through your shorts, then slips inside. Her grip is warm, confident — she knows what she's doing. You turn to her and she's already looking at you. She kisses you hard, her free hand grabbing the back of your neck, pulling you into her.</p><video src="./videos/angela_white/clips/angela_white_4/clip_014.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your mouth moves down — her jaw, her neck, the inside of her thigh where the blanket has fallen away. She sighs and her fingers tangle in your hair. The TV flickers. Neither of you is watching.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I've been wanting to do that all week.</div></div><p>She says it quietly, almost to herself. Her hand is still on you. The movie plays on, unwatched, and her thumb traces a lazy circle on your hip.</p> <<set $game_state.current_canvas = "56f91831-8fbc-45d9-9b8a-b5cfb86cec81">> <<set $game_state.current_node = "c6262562-8ab3-4602-a24c-65343a9108c5">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_movie_night_angela.foreplay") === -1) { vn.push("activity_movie_night_angela.foreplay"); }<</script>> <<nobr>> <<link "Tell her you wanted this too" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <<link "Just hold her hand" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>Her hand finds your thigh under the blanket. She squeezes once, then guides your hand between her legs. She's already wet through the fabric. She looks at you — not a question. A request.</p><p>She pushes the blanket aside and pulls you down. You slide off the couch, settling between her knees.</p><video src="./videos/angela_white/clips/angela_white_4/clip_015.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your mouth presses against her inner thigh. She inhales sharply. You take your time — kissing higher, slower than she wants, until her hips shift toward you and her fingers grip the cushion.</p><video src="./videos/angela_white/clips/angela_white_4/clip_018.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your tongue finds her and she makes a sound that has nothing to do with the movie. Her head falls back against the cushion. Her thighs tighten around you. She tastes like the shower she just took — clean, warm, hers.</p><p>When she comes, it's quiet — a shudder, her hand pressing your head closer, then releasing. She lies there breathing. The screen casts flickering blue light across her face. She looks wrecked.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Get up here.</div></div><p>She pulls you up by your shirt and tucks herself against your chest. Her heartbeat is still fast against your ribs.</p> <<set $game_state.current_canvas = "56f91831-8fbc-45d9-9b8a-b5cfb86cec81">> <<set $game_state.current_node = "5f8697d5-dbd0-4f32-a6be-e15023bcc196">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_movie_night_angela.passionate") === -1) { vn.push("activity_movie_night_angela.passionate"); }<</script>> <<nobr>> <<link "Stay close and watch the ending" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <<link "Kiss her neck and say nothing" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>She pulls you close before the opening credits finish. Her mouth is on yours, her hands already under your shirt. There's no pretense of watching anything tonight.</p><p>She pulls your shirt over your head. You pull hers off. The blanket ends up on the floor. She pushes you back against the armrest and climbs onto you.</p><video src="./videos/angela_white/clips/angela_white_4/clip_017.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She reaches between you and guides you inside her. The sound she makes is low, almost private — like she didn't mean to let it out. Her nails dig into your shoulders.</p><video src="./videos/angela_white/clips/angela_white_4/clip_019.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She rocks against you slowly, her forehead pressed to yours. Her breath is hot on your lips. Every time she rolls her hips, she watches your face — checking, savoring, taking her time.</p><video src="./videos/angela_white/clips/angela_white_4/clip_020.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She sits up and rides you harder. Her hands flat on your chest, her back arched, her eyes half-closed. She sets the pace and you let her. The couch creaks under you.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> I want all of you tonight.</div></div><video src="./videos/angela_white/clips/angela_white_4/clip_016.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She shifts, reaching back to guide you differently. The angle changes. She exhales through her teeth and her whole body tightens, then slowly opens. You hold still until she pushes back against you.</p><video src="./videos/angela_white/clips/angela_white_4/clip_021.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She turns to face you again, sinking down slowly. Her hands find your face. She holds you there — eye to eye — while she moves. The TV is just noise now.</p><video src="./videos/angela_white/clips/angela_white_4/clip_022.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She bends over the armrest. You grip her hips and push into her from behind. She reaches back and grabs your wrist — not to stop you. To hold on.</p><video src="./videos/angela_white/clips/angela_white_4/clip_023.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You flip her onto her back. She wraps her legs around you and pulls you deep. Her mouth opens but no sound comes out. Then she whispers your name.</p><video src="./videos/angela_white/clips/angela_white_4/clip_024.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She turns over again, pressing her face into the cushion. You enter her slowly and she moans into the fabric, her hand reaching back to grip your hip, pulling you closer.</p><video src="./videos/angela_white/clips/angela_white_4/clip_025.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/angela_white_4/clip_026.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She lies flat, her face turned to the side, eyes glazed. You thrust into her steadily and her fingers claw at the cushion. She's past words now — just sounds, just breath, just the two of you on this couch in the dark.</p><video src="./videos/angela_white/clips/angela_white_4/clip_027.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She rolls over and pulls you toward her mouth. She takes you deep, tasting herself on you, her eyes locked on yours. Her hand works what her mouth can't reach.</p><video src="./videos/angela_white/clips/angela_white_4/clip_028.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She looks up at you when you finish. Wipes her chin with two fingers and licks them clean, smiling. Not the smile from those first awkward dinners. This one is triumphant.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> We never did pick a movie.</div></div><p>She pulls the blanket off the floor and drapes it over you both. Her head finds your chest. Your heartbeat is still hammering. The TV has gone to the menu screen, looping the same three seconds of music. Neither of you reaches for the remote.</p> <<set $game_state.current_canvas = "56f91831-8fbc-45d9-9b8a-b5cfb86cec81">> <<set $game_state.current_node = "de96f4ac-ec61-449d-9b58-25ac6f70d6b8">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_movie_night_angela.intense") === -1) { vn.push("activity_movie_night_angela.intense"); }<</script>> <<nobr>> <<link "Hold her close" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <<link "Tell her how you feel" "Location_Living_Room">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>Morning light fills the kitchen. Angela is already up — coffee maker gurgling, her bare feet on the tile. She hasn't noticed you in the doorway.</p><video src="./videos/angela_white/clips/angela_white_4/clip_001.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/angela_white_4/clip_002.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/angela_white_4/clip_003.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><video src="./videos/angela_white/clips/angela_white_4/clip_004.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She picks a strawberry off the plate and bites into it. Juice on her lower lip. She licks it off without thinking — the kind of small, private pleasure she doesn't know anyone is watching.</p><video src="./videos/angela_white/clips/angela_white_4/clip_005.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She sees you and the smile shifts — still warm, but aware now. She's been caught being herself.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Morning. Coffee's ready.</div></div><p>She slides a mug across the counter. Her fingers brush the ceramic like she's done this a thousand times. You take the seat across from her and the kitchen settles into something that's starting to feel like routine.</p> <<set $game_state.current_canvas = "8519e25e-acbb-4414-a56c-57e5225ab01b">> <<set $game_state.current_node = "87be7279-c944-4648-88f6-ad905f95de47">> <<script>>setup.markCanvasTriggered("8519e25e-acbb-4414-a56c-57e5225ab01b");<</script>> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_breakfast_angela.morning") === -1) { vn.push("activity_breakfast_angela.morning"); }<</script>> <<nobr>> <<link "Eat together" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(45);<</script>><</link>><br> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 22, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}], "version": "1.0"})>> <span @class="setup.isChoiceVisited('8519e25e-acbb-4414-a56c-57e5225ab01b:cc0') ? '' : 'unlocked-choice'"> <<link "Stand closer to her" "Canvas_activity_breakfast_angela_Node_2">><<script>>setup.markChoiceVisited("8519e25e-acbb-4414-a56c-57e5225ab01b:cc0");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 42, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "kiss_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('8519e25e-acbb-4414-a56c-57e5225ab01b:cc1') ? '' : 'unlocked-choice'"> <<link "Kiss her" "Canvas_activity_breakfast_angela_Node_3">><<script>>setup.markChoiceVisited("8519e25e-acbb-4414-a56c-57e5225ab01b:cc1");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 62, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "groping_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('8519e25e-acbb-4414-a56c-57e5225ab01b:cc2') ? '' : 'unlocked-choice'"> <<link "Get closer to her" "Canvas_activity_breakfast_angela_Node_4">><<script>>setup.markChoiceVisited("8519e25e-acbb-4414-a56c-57e5225ab01b:cc2");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <<if setup.triggerConditionsSatisfied({"items": [{"type": "trait", "value": 82, "npc_id": "npc_angela", "subject": "npc", "operator": "gte", "trait_key": "love"}, {"type": "flag", "subject": "player", "flag_key": "oral_unlocked", "operator": "is_true"}], "logic": "AND", "version": "1.0"})>> <span @class="setup.isChoiceVisited('8519e25e-acbb-4414-a56c-57e5225ab01b:cc3') ? '' : 'unlocked-choice'"> <<link "Pull her close" "Canvas_activity_breakfast_angela_Node_5">><<script>>setup.markChoiceVisited("8519e25e-acbb-4414-a56c-57e5225ab01b:cc3");<</script>><<script>>advanceTime(3);<</script>><</link>><br> </span> <</if>> <</nobr>>
<p>You come into the kitchen half-dressed — shorts, no shirt. She's at the counter slicing fruit. She doesn't look up, but her shoulders shift. She knows you're there.</p><video src="./videos/angela_white/clips/angela_white_4/clip_006.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your arms wrap around her from behind. She tenses for half a second — your chest against her back, your skin still warm from sleep — then she exhales and leans into you. Her head tilts back against your shoulder.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> You're up early. Couldn't sleep... or couldn't stay away?</div></div><p>She says it lightly, teasing, but she doesn't move. Her hand comes up to rest on your forearm. Her thumb traces a circle on your wrist. The coffee maker clicks off. Neither of you reaches for it.</p> <<set $game_state.current_canvas = "8519e25e-acbb-4414-a56c-57e5225ab01b">> <<set $game_state.current_node = "4a9ae306-57f9-412d-b4bc-8ff9ce8cfa8e">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_breakfast_angela.warm") === -1) { vn.push("activity_breakfast_angela.warm"); }<</script>> <<nobr>> <<link "Help her cook" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(45);<</script>><</link>><br> <<link "Just hold her" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(45);<</script>><</link>><br> <</nobr>>
<p>She's at the sink, rinsing fruit. You come up beside her to grab a mug and your hip brushes hers. She doesn't move away. You don't either.</p><p>The mug stays empty. Your hand finds the small of her back instead. She turns — not surprised. Waiting.</p><video src="./videos/angela_white/clips/angela_white_4/clip_007.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Your mouth finds her neck. The soft spot below her ear, then lower — her collarbone, the hollow of her throat. She gasps and her fingers thread through your hair, pulling you closer. The fruit goes untouched.</p><video src="./videos/angela_white/clips/angela_white_4/clip_008.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She kisses you like she's been thinking about it since she woke up. Deep, unhurried, her body pressed against yours. When you finally break apart, your foreheads stay touching. She's breathing hard.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> We should probably eat something.</div></div><p>She says it with her hands still fisted in your shirt.</p> <<set $game_state.current_canvas = "8519e25e-acbb-4414-a56c-57e5225ab01b">> <<set $game_state.current_node = "e4dd551b-3342-4862-a2b1-f338d7d66f5f">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_breakfast_angela.kiss") === -1) { vn.push("activity_breakfast_angela.kiss"); }<</script>> <<nobr>> <<link "Help her cook" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(45);<</script>><</link>><br> <<link "Cook for her" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>She's leaning against the counter, eating a strawberry, watching you over the rim of her coffee mug. The lace top rides up at her hip. She doesn't pull it down.</p><p>You cross the kitchen. Your hands find her waist. She sets the mug down — deliberately, like she's been waiting for you to do exactly this.</p><video src="./videos/angela_white/clips/angela_white_4/clip_009.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>You slide her shorts down over her hips. She leans forward against the counter, and you drop to your knees behind her. Your lips press against her lower back, the curve of her hip, the soft skin of her inner thigh. She inhales sharply and grips the counter edge.</p><video src="./videos/angela_white/clips/angela_white_4/clip_010.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She laughs — breathless, disbelieving, like she can't quite accept this is her life now. Your tongue traces a line up the back of her thigh and the laugh turns into something else. Her knuckles are white on the countertop.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> The coffee's getting cold.</div></div><p>She says it without turning around. Her voice is unsteady. She doesn't care about the coffee.</p> <<set $game_state.current_canvas = "8519e25e-acbb-4414-a56c-57e5225ab01b">> <<set $game_state.current_node = "240ac273-b098-45bd-ad6b-3d5c373e26e9">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_breakfast_angela.foreplay") === -1) { vn.push("activity_breakfast_angela.foreplay"); }<</script>> <<nobr>> <<link "Take your time with her" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(45);<</script>><</link>><br> <<link "Cook for her after" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<p>She's already made coffee. Two mugs on the counter, steam rising. She looks at you the way she does now — not the measured look from those first mornings. This one is direct. Hungry.</p><p>She comes around the counter. Her hand presses flat against your chest, pushing you back against the kitchen island. Then she drops to her knees on the tile floor.</p><video src="./videos/angela_white/clips/angela_white_4/clip_011.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>Her mouth is warm. She takes you in slowly, her eyes locked on yours the entire time — not shy, not performative. Present. Her hands grip your hips as she finds a rhythm, unhurried, like they have all the time in the world.</p><video src="./videos/angela_white/clips/angela_white_4/clip_012.mp4" controls preload="metadata" style="max-width:100%;height:auto;border-radius:8px;"></video><p>She doesn't break eye contact. Her tongue works slowly, deliberately, and when she takes you deeper she hums against you — a sound of satisfaction, like this is something she wanted. Your hand finds the back of her head. Her hair is still damp from her morning shower.</p><p>When she stands, she wipes her mouth with the back of her hand and smiles. Not the careful smile from month one. Something proprietary.</p><div class="dialog-block dialog-npc"><div class="portrait"><img src="./videos/angela.jpg" alt="Angela" onerror="this.style.display='none';this.parentElement.innerHTML='<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="35" r="20" fill="#6c757d"/><ellipse cx="50" cy="85" rx="35" ry="25" fill="#6c757d"/></svg>';" /></div><div class="dialog-content"><strong>Angela:</strong> Now we can have breakfast.</div></div><p>She picks up her mug like nothing happened. But her hand is shaking slightly, and the look she gives you over the rim says everything.</p> <<set $game_state.current_canvas = "8519e25e-acbb-4414-a56c-57e5225ab01b">> <<set $game_state.current_node = "3cc0859d-927b-421c-abdc-b343e35e7e34">> <<script>>var vn = State.variables.game_state.visited_nodes;if (vn.indexOf("activity_breakfast_angela.intimate") === -1) { vn.push("activity_breakfast_angela.intimate"); }<</script>> <<nobr>> <<link "Hold her close afterward" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 2.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(45);<</script>><</link>><br> <<link "Make her breakfast after" "Location_Kitchen">><<script>>setup.pendingEffects = [];<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "love", "add", 1.0, false, null);<</script>><<script>>setup.applyAndNotifyTrait("npc", "npc_angela", "trust", "add", 1.0, false, null);<</script>><<script>>setup.showEffectNotification();<</script>><<script>>advanceTime(60);<</script>><</link>><br> <</nobr>>
<h2>⚠️ Missing Media Files</h2> <p>Found <strong>1</strong> missing media file.</p> <h3>Locations (1 missing)</h3> <div class="missing-media-section" style="margin-bottom:20px;"> <div class="missing-item" style="border:1px solid #ddd;padding:12px;margin:8px 0;border-radius:6px;background:#fafafa;"> <p style="margin:0;font-weight:bold;color:#333;">[LOCATION_IMAGE] images/locations/hotel_lobby</p> <p style="margin:4px 0 0;color:#666;font-style:italic;font-size:14px;">Navigation image for Hotel Lobby</p> <div style="margin-top:8px;"> <a href="https://www.google.com/search?q=luxury+hotel+lobby+marble&_tmc_game=output&_tmc_scene=images%2Flocations%2Fhotel_lobby" target="_blank" style="display:inline-block;margin:4px 8px 4px 0;padding:6px 12px;background:#3b82f6;color:white;text-decoration:none;border-radius:4px;font-size:13px;">🔍 luxury hotel lobby marble</a> <a href="https://www.google.com/search?q=upscale+hotel+reception+desk&_tmc_game=output&_tmc_scene=images%2Flocations%2Fhotel_lobby" target="_blank" style="display:inline-block;margin:4px 8px 4px 0;padding:6px 12px;background:#3b82f6;color:white;text-decoration:none;border-radius:4px;font-size:13px;">🔍 upscale hotel reception desk</a> </div> </div> </div> <<back "← Back">>