City of Secrets

A Murder Mystery RPG
Detective's Name
💡 Tip: press Ctrl + 0 to reset browser zoom for the best fit.
v0.3 — City of Secrets
🛏️ Bedroom
☀️ Monday Day1 · Morning
$50
📝 Dev Notes 0 notes
No notes yet
🔍
▼ Press Enter or click to continue

Shop

$0

🎁 Give a gift to

Choose carefully — they'll remember.

🎒 Your Bag

Items you've bought. Open a chat with someone and tap 🎁 to give one.
🟦 GPT (gpt-4.1-mini)

📋 Active Quests

📋 Evidence Board

🗺️

Welcome to Ashford City

This is the city map. You can travel to any location by clicking on a building.

Your address 🏠 Harbour View · Your Apartment

The 🏠 Home button in the top bar takes you directly to your room from anywhere in the city.

Loading map…
, document.body may be null — defer to DOMContentLoaded. if (document.body) _updateMobileFlag(); else document.addEventListener('DOMContentLoaded', _updateMobileFlag, { once: true }); window.addEventListener('resize', _updateMobileFlag); window.addEventListener('orientationchange', _updateMobileFlag); // Clear any leftover inline zoom styles from the previous detector. (function _clearLegacyZoom() { const ids = [ 'hud', 'azure-banner', 'dialogue-box', 'suggestion-chips', 'chat-back-btn', 'rel-toast', 'debug-scene-label', 'debug-mode-badge', 'voicemail-overlay', 'chat-popup-overlay', 'portrait-popup', 'cutscene-overlay', 'quest-notification', '_zoom-debug', ]; for (const id of ids) { const el = document.getElementById(id); if (el) el.style.zoom = ''; } const debug = document.getElementById('_zoom-debug'); if (debug) debug.remove(); })(); let gameState = null; const SAVE_KEY = 'city-of-secrets-save'; const DEBUG_SKIP_SPLASH = false; // Skip splash + instant text for debugging // ─── Content Mode (hidden — not visible in game UI) ───────────── // Set to true for family-friendly mode: no Groq, // locked bathroom door, no nightgown, stricter chat guardrails. const TAME_MODE = true; // Scene background images — paths relative to client/ // These are placeholder gradient colors until real images are generated const SCENE_IMAGES = { // Home — room scenes bedroom: '../locations/home/bedroom/scene.png', hallway: '../locations/home/hallway/scene.png', kitchen: '../locations/home/kitchen/scene.png', 'utility-room': '../locations/home/utility-room/scene.png', 'neighbor-view': '../locations/home/neighbor-view/scene.png', outside: '../locations/home/outside-front/scene.png', garage: '../locations/home/garage/scene.png', // City building interior scenes (shown when player enters) hospital: '../locations/hospital/scene-interior.png', 'hospital-exterior': '../locations/hospital/scene-exterior.png', 'police-station': '../locations/police-station/scene-bullpen-final.png', 'city-jail': '../locations/holding-cells/scene.png', 'city-jail-bianca':'../locations/holding-cells/scene-bianca.png', 'police-station-exterior': '../locations/police-station/scene-exterior.png', 'school-exterior': '../locations/school/scene-exterior.png', 'harbour-view': '../locations/harbour-view/scene-interior.png', 'harbour-view-exterior': '../locations/harbour-view/scene-exterior.png', 'shopping-mall': '../locations/shopping-mall/scene-interior-v2.png', 'shopping-mall-exterior': '../locations/shopping-mall/scene-exterior.png', 'bloom-co-florist':'../locations/bloom-co-florist/scene.png', 'ink-vinyl': '../locations/ink-vinyl/scene.png', 'the-cellar': '../locations/the-cellar/scene.png', 'news-outlet': '../locations/news-outlet/scene-v2.png', 'grand-hotel': '../locations/grand-hotel/scene-interior-v2.png', 'grand-hotel-hallway': '../locations/grand-hotel/scene-interior-v3.png', 'sofia-hotel-suite': '../locations/grand-hotel/scene-lena-suite.png', 'grand-hotel-exterior': '../locations/grand-hotel/scene-exterior.png', restaurant: '../locations/restaurant/scene-interior.png', 'restaurant-exterior': '../locations/restaurant/scene-exterior.png', gym: '../locations/gym/scene-interior.png', 'gym-exterior': '../locations/gym/scene-exterior.png', nightclub: '../locations/nightclub/scene-interior.png', 'nightclub-exterior': '../locations/nightclub/scene-exterior.png', 'day-spa': '../locations/day-spa/scene-interior.png', 'day-spa-exterior': '../locations/day-spa/scene-exterior.png', 'lookout-diner': '../locations/lookout-diner/scene-interior.png', 'lookout-diner-bianca-date': '../locations/lookout-diner/scene-bianca-date.png', 'lookout-diner-exterior': '../locations/lookout-diner/scene-exterior.png', bar: '../locations/bar/scene-interior.png', 'bar-exterior': '../locations/bar/scene-exterior.png', 'ashford-estate': '../locations/ashford-estate/scene-interior.png', 'ashford-estate-exterior': '../locations/ashford-estate/scene.png', 'ashford-pool': '../locations/ashford-estate/scene-pool.png', 'forest-cliff': '../locations/forest-cliff/scene-interior-new.png', 'forest-cliff-climbing': '../locations/forest-cliff/scene-climbing.png', 'forest-cliff-bianca-date': '../locations/forest-cliff/scene-bianca-date.png', 'forest-cliff-exterior': '../locations/forest-cliff/scene.png', 'beach': '../locations/beach/scene-v3-3.png', 'beach-bianca-date': '../locations/beach/scene-bianca-date.png', 'boat-cabin': '../locations/beach/scene-boat-cabin.png', 'scrap-yard': '../locations/scrap-yard/scene-interior.png', 'scrap-yard-office':'../locations/scrap-yard/scene-office.png', 'scrap-yard-exterior': '../locations/scrap-yard/scene.png', 'beach-hotel': '../locations/beach-hotel/scene-interior-v3.png', 'beach-hotel-exterior': '../locations/beach-hotel/scene-exterior.png', university: '../locations/university/scene-interior.png', 'university-exterior': '../locations/university/scene-exterior.png', 'bowling-alley': '../locations/bowling-alley/scene-interior.png', 'bowling-backroom': '../locations/bowling-alley/scene-bowling-backroom.png', 'bowling-alley-bianca-date': '../locations/bowling-alley/scene-bianca-date.png', 'bowling-alley-exterior': '../locations/bowling-alley/scene-exterior.png', bank: '../locations/bank/scene-v3-3.png', 'bank-exterior': '../locations/bank/scene-exterior.png', 'bus-station': '../locations/bus-station/scene-interior.png', 'bus-station-exterior': '../locations/bus-station/scene-exterior.png', supermarket: '../locations/supermarket/scene-interior.png', 'supermarket-exterior': '../locations/supermarket/scene-exterior.png', // Residential buildings 'ashford-towers': '../locations/ashford-towers/scene-lobby-v2.png', 'ashford-towers-exterior': '../locations/ashford-towers/scene-exterior.png', 'house-for-sale': '../locations/house-for-sale/scene.png', 'backyard-pool': '../locations/backyard-pool/scene.png', 'harbour-view': '../locations/harbour-view/scene-lobby.png', 'harbour-view-floor-2': '../locations/harbour-view/scene-hallway-floor2.png', 'houses-block-a': '../locations/houses-block-a/scene-exterior.png', 'houses-block-c/willow-street': '../locations/houses-block-c/willow-street.png', 'houses-block-c-exterior': '../locations/houses-block-c/scene-exterior.png', // Additional city buildings manufacturing: '../locations/manufacturing/scene-interior.png', 'manufacturing-exterior': '../locations/manufacturing/scene-exterior.png', 'corner-deli': '../locations/corner-deli/scene-interior.png', 'corner-deli-exterior': '../locations/corner-deli/scene-exterior.png', 'deli-backroom': '../locations/corner-deli/scene-deli-backroom.png', 'church-cemetery': '../locations/church-cemetery/scene-interior.png', 'church-cemetery-exterior': '../locations/church-cemetery/scene-exterior.png', 'fire-station': '../locations/fire-station/scene-interior.png', 'fire-station-exterior': '../locations/fire-station/scene-exterior.png', 'mechanic-shop': '../locations/mechanic-shop/scene-interior.png', 'mechanic-shop-exterior': '../locations/mechanic-shop/scene-exterior.png', 'riverside-flats': '../locations/riverside-flats/scene-interior.png', 'riverside-flats-exterior': '../locations/riverside-flats/scene-exterior.png', 'city-hall': '../locations/city-hall/scene-interior.png', 'courtroom': '../locations/city-hall/scene-courtroom.png', 'courtroom-verdict': '../locations/city-hall/scene-courtroom-verdict.png', // Endgame cinematic backgrounds (used directly as scene bg, no portrait popup) 'cinematic-day-24-trial': '../locations/city-hall/scene-courtroom.png', 'cinematic-day-25-holloway': '../images/cinematics/day-25-holloway.png', 'cinematic-day-26-verdict': '../locations/city-hall/scene-courtroom-verdict.png', 'cinematic-day-26-verdict-convicted': '../locations/city-hall/scene-courtroom-verdict.png', 'cinematic-bianca-hug': '../images/cinematics/bianca-hug.png', 'cinematic-hale-arrest': '../images/cinematics/hale-arrest.png', 'cinematic-bianca-sorority': '../images/cinematics/bianca-sorority.png', 'city-hall-exterior': '../locations/city-hall/scene-exterior.png', 'art-museum': '../locations/art-museum/scene-interior.png', 'art-museum-exterior': '../locations/art-museum/scene-exterior.png', 'outdoor-pool': '../locations/outdoor-pool/scene-interior.png', 'outdoor-pool-exterior': '../locations/outdoor-pool/scene-exterior.png', 'skate-park-exterior': '../locations/skate-park/scene-exterior.png', playground: '../locations/playground/scene-interior.png', 'fountain-exterior': '../locations/fountain/scene-exterior.png', 'morano-estate-exterior': '../locations/morano-estate/scene-exterior.png', 'morano-pool': '../locations/morano-estate/scene-pool.png', // 'harbour-gardens': '../locations/harbour-gardens/scene-interior.png', // archived — reserved for future tenant // 'harbour-gardens-exterior': '../locations/harbour-gardens/scene-exterior.png', // archived marina: '../locations/marina/scene-interior.png', arcade: '../locations/arcade/scene-interior.png', 'arcade-bianca-date': '../locations/arcade/scene-bianca-date.png', 'arcade-exterior': '../locations/arcade/scene-exterior.png', // Greek row 'greek-row': '../locations/greek-row/scene.png', 'sorority-common': '../locations/sorority-house/scene-common.png', 'fraternity-house':'../locations/fraternity-house/scene.png', 'fraternity-common':'../locations/fraternity-house/scene-common.png', 'noah-frat-room':'../locations/fraternity-house/scene-interior.png', // Police station sub-rooms 'captain-office': '../locations/police-station/captain-office.png', 'case-room': '../locations/police-station/case-room.png', 'player-desk': '../locations/police-station/player-desk.png', 'margot-apartment': '../locations/ashford-towers/scene-margot-apartment.png', // Player apartment (at Harbour View Apartments) 'mercer-apartment':'../locations/mercer-apartment/scene.png', // Character homes 'harbour-view-floor-7': '../locations/harbour-view/scene-apartments-hallway.png', 'ashford-towers-hallway': '../locations/ashford-towers/scene-apartments-hallway.png', 'ashford-towers-floor-2': '../locations/ashford-towers/scene-floor-2.png', 'reyes-apartment': '../locations/harbour-view/scene-reyes-apartment.png', 'sofia-condo': '../locations/maple-condos/scene-sofia-condo.png', 'marco-penthouse': '../locations/ashford-towers/scene-marco-penthouse.png', 'chen-apartment': '../locations/harbour-view/scene-chen-apartment.png', 'jade-studio': '../locations/riverside-flats/scene-jade-studio.png', 'tommy-apartment': '../locations/bar/scene-tommy-flat.png', 'briggs-house': '../locations/elm-townhouses/scene-briggs-house.png', 'duke-house': '../locations/houses-block-a/scene-duke-house.png', 'grant-house': '../locations/houses-block-a/scene-interior.png', 'walt-house': '../locations/houses-block-b/scene-interior.png', 'pastor-house': '../locations/houses-block-a/scene-pastor-house.png', 'herb-apartment': '../locations/harbour-view/scene-herb-apartment.png', 'drake-townhouse': '../locations/elm-townhouses/scene-drake-townhouse.png', 'bianca-sorority-room':'../locations/sorority-house/scene-interior.png', 'lena-suite': '../locations/grand-hotel/scene-lena-suite.png?v=2', 'daisy-suite': '../locations/grand-hotel/scene-daisy-suite.png', 'kaia-apartment': '../locations/harbour-view/scene-kaia-apartment.png', 'mina-apartment': '../locations/harbour-view/scene-mina-apartment.png', 'don-suite': '../locations/ashford-towers/scene-don-suite.png', 'lopez-apartment': '../locations/harbour-view/scene-lopez-apartment.png', 'papadopoulos-apartment': '../locations/riverside-flats/scene-papadopoulos-apartment.png', // Story scene locations 'ashford-penthouse':'../locations/ashford-estate/scene-penthouse.png', 'docks-night': '../locations/docks/scene-night.png', // University sub-rooms 'uni-library': '../locations/university/scene-library.png', 'uni-lecture-hall': '../locations/university/scene-lecture-hall.png', 'uni-student-lounge':'../locations/university/scene-student-lounge.png', // Character homes — batch 2 'richard-hale-suite':'../locations/ashford-towers/scene-richard-hale-suite.png', 'yuki-apartment': '../locations/day-spa/scene-yuki-apartment.png', 'art-grady-cottage':'../locations/ashford-estate/scene-art-grady-cottage.png', 'sam-apartment': '../locations/shopping-mall/scene-sam-apartment.png', 'tess-apartment': '../locations/bank/scene-tess-apartment.png', 'eddie-apartment': '../locations/manufacturing/scene-eddie-apartment.png', 'gears-apartment': '../locations/mechanic-shop/scene-gears-apartment.png', 'charlie-apartment':'../locations/fire-station/scene-charlie-apartment.png', 'reggie-apartment': '../locations/ashford-towers/scene-reggie-apartment.png', 'rosa-apartment': '../locations/church-cemetery/scene-rosa-apartment.png', 'coop-apartment': '../locations/supermarket/scene-coop-apartment-v2.png', 'dex-apartment': '../locations/bus-station/scene-dex-apartment.png', 'max-apartment': '../locations/ashford-towers/scene-max-apartment.png', // Bedroom scenes removed — items moved to parent home rooms // Morano Estate 'morano-estate': '../locations/morano-estate/scene-interior.png', // Beach hotel hallway 'beach-hotel-hallway': '../locations/beach-hotel/scene-hallway.png', }; // Night filter overlays per scene — each is an array of CSS gradient layers // Applied when gameState.timeOfDay is 'evening' const SCENE_NIGHT_FILTERS = { bedroom: [ 'radial-gradient(ellipse 30% 45% at 14% 36%, rgba(5,8,28,0.78) 0%, rgba(5,8,28,0.4) 55%, transparent 100%)', 'radial-gradient(ellipse 42% 30% at 30% 83%, rgba(5,8,28,0.6) 0%, rgba(5,8,28,0.22) 55%, transparent 100%)', 'linear-gradient(rgba(8,12,35,0.05), rgba(8,12,35,0.05))', 'radial-gradient(ellipse 50% 45% at 50% 25%, rgba(255,200,100,0.07) 0%, transparent 70%)', ], hallway: [ 'radial-gradient(ellipse 50% 40% at 70% 50%, rgba(5,8,28,0.5) 0%, rgba(5,8,28,0.2) 60%, transparent 100%)', 'linear-gradient(rgba(8,12,35,0.08), rgba(8,12,35,0.08))', ], kitchen: [ 'radial-gradient(ellipse 35% 40% at 60% 35%, rgba(5,8,28,0.6) 0%, rgba(5,8,28,0.25) 55%, transparent 100%)', 'linear-gradient(rgba(8,12,35,0.06), rgba(8,12,35,0.06))', 'radial-gradient(ellipse 55% 50% at 45% 40%, rgba(255,200,100,0.06) 0%, transparent 70%)', ], outside: [ 'linear-gradient(rgba(8,12,35,0.35), rgba(8,12,35,0.35))', 'radial-gradient(ellipse 30% 40% at 50% 70%, rgba(255,200,100,0.08) 0%, transparent 60%)', ], }; // ─── Character Data (portraits, conversation caps, schedules) ── // schedule: maps timeOfDay to room ID (for city NPCs with fixed locations) // wanderWeights: maps timeOfDay to { roomId: weight } for random house wandering // Characters with wanderWeights use rollNpcLocations() instead of fixed schedule const CHARACTER_DATA = { 'detective-reyes': { name: 'Detective Reyes', color: '#5b8def', portrait: '../images/characters/detective-reyes/portrait.png', convCaps: { friendship: 90, trust: 85 }, suggestions: ['Any leads on the Ashford case?', 'What do you think about Hale?', 'Tell me about the department'], defaultSuggestions: ['Any leads on the Ashford case?', 'What do you think about Hale?', 'Tell me about the department'], schedule: { day: 'police-station', evening: 'police-station', }, greetings: { 'police-station': { text: '*Detective Reyes is hunched over a desk covered in case files. He barely looks up.* Kid. You again. Make it quick.', suggestions: ['Any updates on the Ashford case?', 'I found something you should see', 'Who are your suspects?'], }, 'lookout-diner:evening': { text: '*Reyes is nursing a black coffee at a corner booth, tie loosened. He nods when he sees you.* Off duty. Mostly. Sit down.', suggestions: ['Rough day?', 'Can I ask you something off the record?', 'You look like you need sleep'], }, '_default': { text: '*Reyes gives you a sharp look.* What do you need?', suggestions: ['About the case...', 'I have information', 'Just passing through'], }, }, }, 'captain-briggs': { name: 'Captain Briggs', color: '#c0392b', portrait: '../images/characters/captain-briggs/portrait.png', convCaps: { friendship: 30, trust: 25 }, suggestions: ['About the Ashford case...', 'What\'s Hale\'s connection to all this?', 'Do you believe the natural-causes ruling?'], defaultSuggestions: ['About the Ashford case...', 'What\'s Hale\'s connection to all this?', 'Do you believe the natural-causes ruling?'], // Briggs does NOT wander — he stays at his desk during the day and goes // home in the evening. Time-gating is handled by `present` on the rooms. greetings: { 'captain-office': { text: () => `*Briggs is at his desk, not looking up from his paperwork.* "You need something, ${(gameState.lastName || gameState.name || 'Detective').trim()}?"`, suggestions: ['About the Ashford case...', 'Any new orders for me?', 'Why was the case closed so fast?'], }, 'police-station': { text: () => `*Captain Briggs looks up from his desk, expression unreadable.* "${(gameState.lastName || gameState.name || 'Detective').trim()}. What is it?"`, suggestions: ['About the Ashford case...', 'Any new orders?', 'I\'ve been going over the Ashford file.'], }, 'grand-hotel:evening': { text: () => `*Briggs is at the bar nursing a scotch — Hale just excused himself when you walked in. His jaw tightens.* '${(gameState.lastName || gameState.name || 'Detective').trim()}. What the hell are you doing here.'`, suggestions: ["Didn't know you knew Hale", 'Something you want to tell me, Captain?', "I'll pretend I didn't see this"], }, '_default': { text: '*Briggs gives you a curt nod.* "Make it quick."', suggestions: ['It\'s about the Ashford case', 'Do you have a minute?', 'Just checking in'], }, }, }, 'dr-ellis-chen': { name: 'Dr. Ellis Chen', color: '#4ecdc4', convCaps: { friendship: 80, trust: 75 }, suggestions: ['How was the victim found?', 'What was his condition?', 'Did anything seem off?'], wanderWeights: { day: { 'hospital': 5 }, evening: { 'hospital': 1, 'chen-apartment': 4 }, }, greetings: { 'hospital': { text: '*Dr. Chen is reviewing a chart near the nurses\' station. She adjusts her glasses when she notices you.* Ah. Can I help you with something?', suggestions: ['I wanted to ask about Victor Ashford', 'How are you holding up?', 'What can you tell me about the case?'], returnText: () => `*Dr. Chen glances up from a patient chart. There's a flicker of something — recognition, maybe worry — before her expression smooths into professional calm.* Detective ${(gameState.lastName || gameState.name || '').trim()}. Back again?`, returnSuggestions: ['Still looking into the Ashford case', 'Anything new on your end?', 'I have some follow-up questions'], }, 'chen-apartment': { text: '*Dr. Chen answers the door in casual clothes, clearly off-duty. She looks tired.* Detective. How did you know where I live?', suggestions: ['I had to find you outside work', 'This couldn\'t wait', 'Is now a bad time?'], returnText: '*Dr. Chen opens the door before you finish knocking. She looks like she\'s been waiting.* You again. Come in.', returnSuggestions: ['Something came up about the case', 'I found something I think you should know', 'Are you okay?'], }, '_default': { text: '*Dr. Chen looks up with a polite but guarded expression.* Yes?', suggestions: ['About Ashford...', 'How are you?', 'I have some questions'], returnText: '*Dr. Chen\'s expression shifts almost imperceptibly when she sees you. She takes a breath.* Detective. What is it this time?', returnSuggestions: ['About the Ashford case', 'Something new came up', 'Just checking in'], }, }, }, 'daisy': { name: 'Daisy', color: '#ffb6c1', portrait: '../images/characters/daisy/sprite-faceswap-transparent.png', convCaps: { friendship: 45, trust: 35 }, suggestions: ['What are you studying?', 'How do you know about the Ashford case?', 'Want to grab coffee?'], defaultSuggestions: ['What are you studying?', 'How do you know about the Ashford case?', 'Want to grab coffee?'], wanderWeights: { day: { 'university': 4, 'uni-library': 2, 'uni-student-lounge': 3 }, evening: { 'lookout-diner': 3, 'daisy-suite': 2 }, }, greetings: { 'university': { text: '*You spot Daisy walking across the quad with a bag of books. She sees you and gives a bright smile.* Hey! I was just heading to class. What\'s up?', suggestions: ['Mind if I walk with you?', 'Can I ask you something?', 'Want to grab coffee later?'], }, 'uni-student-lounge': { text: '*Daisy is curled up on one of the couches with a laptop, sipping on a cold drink. She looks up when you approach.* Hey there! Procrastinating on homework too?', suggestions: ['Mind if I sit?', 'What are you working on?', 'Want to take a break?'], }, 'uni-library': { text: '*Daisy is studying at a table surrounded by textbooks. She puts a finger to her lips when she sees you, gesturing to move to a quieter corner.* Hey, what\'s going on?', suggestions: ['Need help with anything?', 'Want to study together?', 'Can I ask you something?'], }, 'lookout-diner:evening': { text: '*Daisy is at the counter with a milkshake, chatting with some friends. She notices you and waves you over with a friendly smile.* Hey! Come join us!', suggestions: ['Mind if I sit?', 'How\'s your week been?', 'Can I buy you another milkshake?'], }, 'coffee-shop:evening': { text: '*Daisy is working on her laptop at a small table by the window, nursing a latte. She smiles when she sees you.* Oh hey! Want to join me?', suggestions: ['Mind if I sit?', 'Working on something interesting?', 'Want to talk?'], }, '_default': { text: '*Daisy smiles warmly when she sees you.* Hey! What\'s up?', suggestions: ['How have you been?', 'Want to hang out?', 'Can I ask you something?'], returnText: '*Daisy greets you with a friendly wave.* Oh hey, again! What\'s happening?', returnSuggestions: ['Just wanted to see you', 'How\'s it going?', 'Got time to talk?'], }, }, }, 'jade-hartley': { name: 'Jade Hartley', color: '#f7b731', portrait: '../images/characters/jade-hartley/portrait.png', convCaps: { friendship: 50, trust: 35 }, suggestions: ['What was Ashford like?', 'Do you know about the will?', 'Are you okay?'], schedule: { day: 'ashford-estate', evening: 'restaurant', }, greetings: { 'ashford-estate': { text: '*Jade is sorting through boxes in the foyer, looking overwhelmed. She brushes hair from her face.* Oh — hi. Sorry, it\'s a bit chaotic. Mr. Ashford\'s affairs and all that...', suggestions: ['Need any help?', 'What was Ashford really like?', 'Have you found anything unusual?'], }, 'restaurant:evening': { text: '*Jade is sitting alone at a corner table, staring into her wine glass. She forces a small smile.* Hey. Needed to get out of that house for a bit.', suggestions: ['Mind if I join you?', 'Are you doing okay?', 'Have you heard anything new?'], }, '_default': { text: '*Jade gives a tired but genuine smile.* Hi there.', suggestions: ['How are you holding up?', 'About the Ashford estate...', 'Can I ask you something?'], }, }, }, 'tommy-vance': { name: 'Tommy Vance', color: '#a29bfe', portrait: '../images/characters/tommy-vance/portrait.png', convCaps: { friendship: 85, trust: 85 }, suggestions: ['Hear any good rumors?', 'Who comes in here?', 'Tell me about the regulars'], wanderWeights: { day: { 'bar': 5 }, evening: { 'bar': 3, 'tommy-apartment': 2 }, }, greetings: { 'bar': { text: '*Tommy is polishing a glass behind the bar. He gives you a knowing grin.* Well, well. My favorite detective. What\'ll it be?', suggestions: ['What\'s the word on the street?', 'Anyone interesting been in lately?', 'Tell me about Ashford\'s crowd'], }, '_default': { text: '*Tommy nods in your direction.* Hey, kid. Pull up a stool.', suggestions: ['Hear any rumors?', 'Who\'s been coming in?', 'Just hanging out'], }, }, }, 'margot-vance': { name: 'Margot Vance', color: '#d35400', portrait: '../images/characters/margot-vance/portrait.png', convCaps: { friendship: 80, trust: 75 }, suggestions: ['Trade gossip?', "What are you working on?", "Heard anything about the Ashford case?"], wanderWeights: { day: { 'news-outlet': 5 }, evening: { 'margot-apartment': 4, 'bar': 1 }, }, greetings: { 'news-outlet': { text: "*Margot Vance looks up from her typewriter, one auburn strand falling across her forehead. She lets the silence sit for a beat too long, then smiles like she's been waiting for you.* Well. The detective walks into the press office. I should be writing this down. What've you got for me?", suggestions: ["Trade gossip?", "What are you working on?", "Anyone been asking about me?"], }, 'bar': { text: "*Margot is at the end of the bar with a notebook open beside a glass of whiskey. She slides the notebook shut as you approach.* Off the record, then. Sit. Buy me the next one and I might be persuaded to talk.", suggestions: ["Trade gossip?", "What's the latest?", "What are you doing here?"], }, 'margot-apartment': { text: () => `*Margot opens the door a crack, recognizes you, and steps back to let you in. Newspaper clippings and red string cover one wall. A coffee mug steams on the table beside a stack of files.* ${(gameState.lastName || gameState.name || 'Detective').trim()}. Kind of late. *She locks the door behind you.* Want a drink, or are we straight to business?`, suggestions: ["Trade gossip?", "Tell me about Kovacs", "Just checking in"], }, '_default': { text: "*Margot raises an eyebrow.* Off-territory for both of us. What do you need?", suggestions: ["Trade gossip?", "Just saying hi", "I have a question"], }, }, }, 'marco-morano': { name: 'Marco Morano', color: '#e74c3c', convCaps: { friendship: 30, trust: 20 }, suggestions: ['What do you want?', 'What do you know about Ashford?', 'Who do you work for?'], wanderWeights: { morning: { 'morano-estate-exterior': 5 }, afternoon: { 'ashford-towers': 5 }, evening: { 'nightclub': 5 }, }, greetings: { 'nightclub': { text: '*Marco is leaning against the VIP railing, drink in hand. His bodyguard shifts as you approach. Marco studies you with cold amusement.* You\'ve got guts coming here. What do you want?', suggestions: ['I need to talk to you', 'What do you know about Ashford?', 'I\'m not looking for trouble'], }, '_default': { text: '*Marco\'s eyes narrow.* You again. Make it worth my time.', suggestions: ['I have information', 'About the Ashford deal...', 'I need a favor'], }, }, }, 'sofia-ashford': { name: 'Sofia Ashford', color: '#dfe6e9', portrait: '../images/characters/sofia-ashford/portrait.png', convCaps: { friendship: 85, trust: 80 }, suggestions: ['I\'m sorry about your father', 'What was he like?', 'Do you know who did this?'], wanderWeights: { day: { 'ashford-estate': 5 }, evening: { 'sofia-hotel-suite': 4, 'grand-hotel-hallway': 1 }, }, greetings: { 'ashford-estate': { text: '*Sofia is standing by the window, arms crossed. She turns slowly, eyes red-rimmed.* You\'re the one asking questions around town, aren\'t you?', suggestions: ['I\'m sorry about your father', 'I want to help find out what happened', 'What can you tell me?'], }, 'grand-hotel:evening': { text: '*Sofia is in the hotel bar, looking elegant but exhausted. She gestures to the seat across from her.* I could use the company, honestly.', suggestions: ['How are you holding up?', 'Have the police told you anything?', 'What was your father like?'], }, '_default': { text: '*Sofia regards you with cautious curiosity.* What brings you here?', suggestions: ['About your father...', 'I found something', 'How are you?'], }, }, }, 'richard-hale': { name: 'Richard Hale', color: '#cd6763', convCaps: { friendship: 30, trust: 20 }, suggestions: ['How did you know Ashford?', 'What were you doing at the docks?', 'Nice hotel'], wanderWeights: { day: { 'grand-hotel': 2, 'manufacturing': 3 }, evening: { 'richard-hale-suite': 4, 'grand-hotel': 1 }, }, greetings: { 'manufacturing': { text: "*Hale walks the factory floor unhurried, like he's already pricing fixtures. He turns when he hears you.* Detective. Small city, isn't it. Just taking an interest in local industry.", suggestions: ["This is Ashford's factory", 'What are you planning to do with it?', 'You were expecting this place to change hands'], }, '_default': { text: () => `*Hale studies you with the patient amusement of a man who's never lost a negotiation.* Detective ${(gameState.lastName || gameState.name || '').trim()}. What can I do for you?`, suggestions: ['How well did you know Ashford?', 'What were you doing at the docks?', 'I have some questions'], }, }, }, 'grand-hotel-clerk': { name: 'Hotel Receptionist', color: '#d4a574', portrait: '../images/characters/grand-hotel-clerk/portrait.png', convCaps: { friendship: 10, trust: 5 }, suggestions: ['Any rooms available?', 'Who checked in recently?', 'Nice lobby'], greetings: { '_default': { text: '*A poised woman behind the marble front desk looks up with a practiced smile.* "Welcome to The Grand Hotel. I\'m afraid we\'re fully booked at the moment — have been for weeks, actually. Is there something else I can help you with?"', suggestions: ['Any rooms available?', 'Who checked in recently?', 'I\'m investigating a case'], }, }, }, 'driftwood-clerk': { name: 'Hotel Clerk', color: '#c8a882', portrait: '../images/characters/driftwood-clerk/portrait.png', convCaps: { friendship: 10, trust: 5 }, suggestions: ['Any rooms available?', 'Who\'s staying here?', 'Nice place'], greetings: { '_default': { text: '*A sun-kissed woman at the reception desk adjusts a vase of tropical flowers and smiles warmly.* "Welcome to The Driftwood! Unfortunately, we\'re completely booked through the season. But feel free to enjoy the lobby — the ocean view is free."', suggestions: ['Any rooms available?', 'Who\'s been staying here?', 'I\'m a detective — I have some questions'], }, }, }, 'art-grady': { name: 'Art Grady', color: '#e17055', convCaps: { friendship: 40, trust: 30 }, wanderWeights: { day: { 'ashford-estate': 3, 'manufacturing': 2 }, // Manufacturing closes in the evening — Art is at the estate or home. evening: { 'ashford-estate': 1, 'art-grady-cottage': 3 }, }, greetings: { 'ashford-estate': { text: '*Art is inspecting the grounds near the main entrance, hands in his vest pockets. He squints at you.* "You\'re that detective, ain\'t ya. I keep the estate runnin\' — been doin\' it for twenty-five years. What do you need?"', suggestions: ['You keep this whole place running?', 'Did you notice anything strange before Ashford died?', 'What do you think happened?'], }, 'manufacturing': { text: '*Art is checking manifests on the loading dock, pencil tucked behind his ear. He looks up warily.* "This is a workin\' dock, detective. You need somethin\', make it quick."', suggestions: ['What goes through this dock?', 'Notice any unusual shipments?', 'Who authorized the new crates?'], }, 'art-grady-cottage': { text: '*Art opens the cottage door, still in his work boots. A kettle whistles behind him.* "Didn\'t expect company. Come in, I guess."', suggestions: ['Nice place you got here', 'Can we talk off the record?', 'What\'s really going on at the factory?'], }, '_default': { text: '*Art gives you a cautious nod.* "What do you need?"', suggestions: ['About the Ashford case...', 'I have some questions', 'How are you holding up?'], }, }, }, 'sam-okafor': { name: 'Sam Okafor', color: '#e17055', convCaps: { friendship: 40, trust: 25 }, wanderWeights: { day: { 'fire-station': 5 }, evening: { 'sam-apartment': 5 }, }, }, 'tess-walker': { name: 'Tess Walker', color: '#ff7675', convCaps: { friendship: 45, trust: 30 }, wanderWeights: { day: { 'bank': 4 }, evening: { 'tess-apartment': 4, 'bank': 1 }, }, }, 'kaia-reeves': { name: 'Kaia Reeves', color: '#f0932b', convCaps: { friendship: 50, trust: 35 }, wanderWeights: { day: { 'outdoor-pool': 4 }, // Pool closes in the evening — Kaia heads to the beach or home. evening: { 'beach': 3, 'kaia-apartment': 2 }, }, greetings: { '_default': { text: '*A tanned young woman in a red lifeguard tank top twirls a whistle around her finger, scanning the water.* "Hey! Pool\'s open till six. Need a towel or just here to hang?"', suggestions: ['See a lot of people from town here?', 'Know any of the Ashford crowd?', 'Just hanging out'], }, }, }, 'herb-pemberton': { name: 'Herb Pemberton', color: '#b8860b', convCaps: { friendship: 35, trust: 25 }, wanderWeights: { day: { 'riverside-flats': 2, 'harbour-view': 3 }, evening: { 'herb-apartment': 4, 'harbour-view': 1 }, }, }, 'max-sterling': { name: 'Max Sterling', color: '#ffeaa7', convCaps: { friendship: 35, trust: 20 }, wanderWeights: { day: { 'grand-hotel': 5 }, evening: { 'grand-hotel': 2, 'max-apartment': 3 }, }, }, 'reggie-cole': { name: 'Reggie Cole', color: '#e17055', convCaps: { friendship: 35, trust: 25 }, wanderWeights: { day: { 'bowling-alley': 5 }, evening: { 'bowling-alley': 2, 'reggie-apartment': 3 }, }, }, 'noah-keller': { name: 'Noah Keller', color: '#74b9ff', convCaps: { friendship: 45, trust: 35 }, suggestions: ['What have you heard around campus?', 'Did anyone from Greek Row know Ashford?', 'You seem like you know everyone'], defaultSuggestions: ['What have you heard around campus?', 'Did anyone from Greek Row know Ashford?', 'You seem like you know everyone'], wanderWeights: { day: { 'fraternity-common': 5 }, evening: { 'noah-frat-room': 4, 'fraternity-common': 1 }, }, greetings: { 'fraternity-common': { text: '*A guy in a university hoodie looks up from the couch and gives you a friendly nod.* Hey — you lost, or you here for a reason? Either way, grab a seat.', suggestions: ['You live here at Sigma Chi?', 'What have you heard around campus?', 'Did anyone from Greek Row know Ashford?'], }, 'noah-frat-room': { text: '*Noah is at his desk, scribbling on a whiteboard covered in color-coded notes. He spins around.* Oh hey — come in. Just updating my... uh, study notes.', suggestions: ['Those look like more than study notes', 'What are you tracking?', 'Rough day?'], }, 'university': { text: '*Noah jogs over with a backpack slung over one shoulder.* Hey! You\'re that PI, right? I hear things. Want to compare notes?', suggestions: ['What have students been saying?', 'Notice anyone new around campus?', 'Know anything about the Ashford family?'], }, '_default': { text: '*Noah gives you a quick wave.* Yo — what\'s up?', suggestions: ['Heard anything new?', 'Who should I talk to next?', 'How\'s campus life?'], }, '_return': { text: '*Noah grins.* Back again? Must mean you\'re making progress. What do you need?', suggestions: ['I found something interesting', 'Any new rumors?', 'Need your help with something'], }, }, }, 'bianca-morano': { name: 'Bianca Morano', color: '#ff85a2', // Best face reference for the chat-popup 📸 button — Seedream's edit endpoint // conditions the generated scene on this image so the output keeps her face. faceRef: '../images/characters/bianca-morano/expressions/bianca-happy.png', convCaps: { friendship: 75, trust: 70 }, suggestions: ['Are you okay?', 'Tell me about yourself', 'What happened to you?'], defaultSuggestions: ['Are you okay?', 'Tell me about yourself', 'What happened to you?'], wanderWeights: { morning: { 'shopping-mall': 5 }, afternoon: { 'shopping-mall': 5 }, evening: { 'shopping-mall': 3, 'sorority-common': 5 }, }, greetings: { 'shopping-mall': { text: '*She\'s on a bench near the food court, cross-legged, the leather Moleskine balanced on one knee. Her pencil is moving fast — she\'s sketching the silhouette of a woman in line at the coffee kiosk, getting the dress, not the face. She doesn\'t look up as you stop nearby.* *(A beat. Then she registers the shadow on her page and glances over — a stranger\'s polite half-smile, slightly guarded.)* …Hi. You need something, or just watching?', suggestions: ['Sorry — didn\'t mean to hover. Mind if I sit?', 'Just curious what you\'re drawing.', 'Got a minute? I\'m a detective.'], returnText: '*She glances up from the sketchbook and recognizes you this time. A small, slightly embarrassed smile.* Oh — hey. Wasn\'t expecting to see you again so soon. I\'m not creeping, I swear. I just like the silhouettes here.', returnSuggestions: ['Can I see what you\'re drawing?', 'How\'s the day going?', 'What are you sketching now?'], }, 'city-jail': { text: '*Bianca looks up from the bench in her cell. She\'s in jail clothes with her hair worn loose, somehow still composed. She tries to smile and her lip trembles.* You came. I— I didn\'t know if anyone would come.', suggestions: ['Tell me what really happened', 'What do you need?', 'I\'m going to get you out of here'], }, '_default': { text: '*Bianca tucks a strand of blonde hair behind her ear and manages a small smile.* Hey. Back again?', suggestions: ['How are you holding up?', 'Any news about the trial?', 'I\'m going to help you — I promise'], }, '_return': { text: '*Bianca\'s face lights up when she sees you.* Hey — I was hoping you\'d come by.', suggestions: ['I found something about your case', 'How\'s your dad handling this?', 'I missed talking to you'], }, }, }, 'don-morano': { name: 'Don Enzo Morano', color: '#b8860b', convCaps: { friendship: 30, trust: 25 }, suggestions: ['I\'m here about Bianca', 'Tell me about Richard Hale', 'I need your help'], defaultSuggestions: ['I\'m here about Bianca', 'Tell me about Richard Hale', 'I need your help'], wanderWeights: { morning: { 'morano-estate': 5 }, afternoon: { 'don-suite': 5 }, evening: { 'don-suite': 5 }, }, greetings: { 'morano-estate': { text: '*The Don sits behind a massive oak desk, cigar smoke curling upward. Two bodyguards flank the door. He studies you with eyes that have measured a thousand men.* So. You\'re the one my Bianca trusts. Sit.', suggestions: ['Bianca told me you might have information', 'Who is trying to frame your daughter?', 'I want to help — but I need the truth'], }, '_default': { text: '*Don Morano regards you with the patience of a man who controls his world.* Speak.', suggestions: ['About Bianca\'s case...', 'I found evidence', 'I need information'], }, }, }, }; // ─── Character Profiles (loaded from character-profiles.js) ───── // CHARACTER_PROFILES is defined in character-profiles.js (loaded via