/* ===================================================================== GLOBAL THEME โ€” dark base from the user's login screen. The dark deep purple body background stays locked for every user. What CAN change (for premium users only) is the PRIMARY accent color: the glow on buttons, the active-tab highlight, the persona-card gradient. The 8 preset accents below match the 8-color picker from the old app. Non-premium users are locked to the default Purple+Cyan pairing that matches the login screen. Premium users can repaint --accent-primary from JS by writing to document.documentElement.style.setProperty(). ===================================================================== */ :root { --bg-deep: #0a0118; --bg-mid: #1a0b2e; /* The 3 brand accents. --accent-primary is the one premium users can swap; cyan and pink stay constant so the brand identity holds. */ --accent-primary: #a855f7; /* default = Purple */ --accent-primary-dark: var(--accent-primary-dark); --accent-glow: var(--accent-glow); --accent-cyan: #22d3ee; --accent-pink: #ec4899; --accent-gold: #fbbf24; /* Surfaces/borders are derived from --accent-primary alpha. We keep these as static rgba values for performance; JS rewrites them when a premium user picks a new theme. */ --border-soft: rgba(168, 85, 247, 0.2); --border-strong: var(--accent-glow); --surface: rgba(168, 85, 247, 0.06); --surface-hover: rgba(168, 85, 247, 0.12); /* Aliases kept for existing markup that already used --accent-purple */ --accent-purple: var(--accent-primary); --text-primary: #ffffff; --text-secondary: #c4b5fd; --text-muted: #6b7280; --error: #ef4444; --success: #10b981; } * { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; } body { background: radial-gradient(ellipse at top, var(--bg-mid) 0%, var(--bg-deep) 50%, #000 100%); color: var(--text-primary); font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; min-height: 100vh; position: relative; overflow-x: hidden; } body::before { content: ''; position: fixed; inset: 0; background: radial-gradient(circle at 15% 25%, rgba(168, 85, 247, 0.15) 0%, transparent 35%), radial-gradient(circle at 85% 75%, rgba(34, 211, 238, 0.10) 0%, transparent 35%); pointer-events: none; z-index: 0; } /* ===================================================================== LOGIN SCREEN (kept from user's login HTML, lightly trimmed). It is hidden once the user authenticates and the main app takes over. ===================================================================== */ .screen { position: relative; z-index: 1; } #screen-login { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .auth-card { width: 100%; max-width: 440px; background: rgba(10, 1, 24, 0.85); backdrop-filter: blur(20px); border: 1px solid var(--border-soft); border-radius: 24px; padding: 32px 28px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); } .brand { text-align: center; margin-bottom: 24px; } .brand-logo { font-size: 36px; margin-bottom: 8px; filter: drop-shadow(0 0 12px var(--accent-purple)); } .brand-name { font-size: 14px; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; background: linear-gradient(135deg, var(--accent-purple), var(--accent-cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; } .tab-switcher { display: flex; background: var(--surface); border-radius: 12px; padding: 4px; margin-bottom: 24px; border: 1px solid var(--border-soft); } .tab-btn-login { flex: 1; padding: 10px; background: none; border: none; color: var(--text-secondary); font-family: inherit; font-size: 14px; font-weight: 600; cursor: pointer; border-radius: 8px; transition: all 0.2s; } .tab-btn-login.active { background: linear-gradient(135deg, var(--accent-purple), var(--accent-primary-dark)); color: var(--text-primary); box-shadow: 0 4px 12px rgba(168, 85, 247, 0.3); } .form-panel { display: none; } .form-panel.active { display: block; animation: fadeIn 0.3s ease-out; } @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .form-title { font-size: 22px; font-weight: 700; margin-bottom: 4px; } .form-subtitle { font-size: 13px; color: var(--text-secondary); margin-bottom: 20px; } .form-group { margin-bottom: 14px; } .form-label { display: block; font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.05em; } .form-input, .form-select, .form-textarea { width: 100%; padding: 12px 14px; background: var(--surface); border: 1px solid var(--border-soft); border-radius: 10px; color: var(--text-primary); font-family: inherit; font-size: 14px; outline: none; transition: all 0.2s; } .form-input:focus, .form-select:focus, .form-textarea:focus { border-color: var(--accent-purple); background: var(--surface-hover); box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.15); } .submit-btn { width: 100%; padding: 13px; background: linear-gradient(135deg, var(--accent-purple), var(--accent-primary-dark)); border: none; border-radius: 12px; color: var(--text-primary); font-family: inherit; font-size: 15px; font-weight: 700; cursor: pointer; transition: all 0.2s; margin-top: 8px; } .submit-btn:hover { transform: translateY(-1px); box-shadow: 0 8px 24px var(--accent-glow); } .status-msg { padding: 10px 14px; border-radius: 10px; font-size: 13px; margin-bottom: 16px; display: none; line-height: 1.5; } .status-msg.show { display: block; animation: fadeIn 0.3s ease-out; } .status-msg.error { background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); color: #fca5a5; } .status-msg.success { background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.3); color: #6ee7b7; } .footer-note { text-align: center; margin-top: 20px; font-size: 11px; color: var(--text-muted); } .footer-note a { color: var(--accent-purple); text-decoration: none; } /* ===================================================================== MAIN APP SHELL - Top header with brand + ZodiacZ chip + profile pic - Tab bar (scrollable) along top - Content area - Bottom-right corner: Corey Voice info icon (๐ŸŽ™๏ธโ„น๏ธ) per page ===================================================================== */ #screen-app { display: none; min-height: 100vh; padding-bottom: 40px; } #screen-app.active { display: block; } .app-header { position: sticky; top: 0; z-index: 100; background: linear-gradient(180deg, rgba(10,1,24,0.95) 0%, rgba(26,11,46,0.85) 100%); backdrop-filter: blur(16px); border-bottom: 1px solid var(--border-soft); padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; gap: 10px; } .app-brand { font-size: 16px; font-weight: 700; letter-spacing: 0.05em; background: linear-gradient(135deg, var(--accent-purple), var(--accent-cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; } .app-header-right { display: flex; align-items: center; gap: 10px; } .zodiac-chip { background: var(--surface); padding: 6px 10px; border-radius: 999px; border: 1px solid var(--border-soft); font-size: 11px; font-weight: 600; color: var(--text-secondary); cursor: pointer; transition: all 0.2s; white-space: nowrap; } .zodiac-chip:hover { background: var(--surface-hover); border-color: var(--border-strong); } .balance-chip { background: var(--surface); padding: 6px 10px; border-radius: 8px; border: 1px solid var(--border-soft); font-size: 11px; font-weight: 700; cursor: pointer; } .profile-pic { width: 36px; height: 36px; border-radius: 50%; border: 2px solid var(--accent-purple); overflow: hidden; display: flex; align-items: center; justify-content: center; font-size: 18px; background: var(--surface); cursor: pointer; flex-shrink: 0; } .profile-pic img { width: 100%; height: 100%; object-fit: cover; } /* Tab bar โ€” wraps onto multiple rows so every tab is visible without horizontal scrolling. Pills shrink slightly to fit 19 tabs comfortably on a phone. */ .tab-bar { background: rgba(10,1,24,0.7); border-bottom: 1px solid var(--border-soft); display: flex; flex-wrap: wrap; gap: 4px; padding: 6px; position: sticky; top: 61px; z-index: 99; } .tab-link { flex: 0 1 auto; padding: 5px 9px; background: var(--surface); border: 1px solid var(--border-soft); border-radius: 999px; color: var(--text-secondary); font-size: 11px; font-weight: 600; cursor: pointer; text-decoration: none; white-space: nowrap; display: inline-flex; align-items: center; gap: 4px; transition: all 0.2s; line-height: 1.2; } .tab-link:hover { background: var(--surface-hover); color: var(--text-primary); } .tab-link.active { background: linear-gradient(135deg, var(--accent-purple), var(--accent-purple-dark)); color: var(--text-primary); border-color: transparent; box-shadow: 0 4px 12px var(--accent-glow); } /* Content */ .app-content { padding: 18px 16px 80px; max-width: 920px; margin: 0 auto; } .page-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 16px; } .page-title { font-size: 22px; font-weight: 700; background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); -webkit-background-clip: text; background-clip: text; color: transparent; } .page-subtitle { font-size: 13px; color: var(--text-secondary); margin-top: 4px; line-height: 1.5; } .corey-btn { background: var(--surface); border: 1px solid var(--border-soft); border-radius: 999px; padding: 6px 10px; color: var(--text-primary); font-size: 14px; cursor: pointer; flex-shrink: 0; transition: all 0.2s; } .corey-btn:hover { background: var(--surface-hover); border-color: var(--accent-cyan); box-shadow: 0 0 12px rgba(34,211,238,0.3); } /* Cards */ .card { background: rgba(10, 1, 24, 0.7); backdrop-filter: blur(12px); border: 1px solid var(--border-soft); border-radius: 16px; padding: 18px; margin-bottom: 14px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); } .card-title { font-size: 14px; font-weight: 700; color: var(--text-primary); margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between; gap: 8px; border-bottom: 1px solid var(--border-soft); padding-bottom: 8px; } /* Sub-app icon grid (the +tab style entries) */ .subapp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 14px; } .subapp-card { background: var(--surface); border: 1px solid var(--border-soft); border-radius: 14px; padding: 12px; cursor: pointer; transition: all 0.2s; display: flex; flex-direction: column; gap: 8px; text-align: center; } .subapp-card:hover { transform: translateY(-2px); border-color: var(--accent-cyan); box-shadow: 0 8px 20px rgba(34,211,238,0.25); } .subapp-icon { width: 100%; aspect-ratio: 1; border-radius: 10px; background: #000; background-size: cover; background-position: center; display: flex; align-items: center; justify-content: center; font-size: 36px; overflow: hidden; } .subapp-name { font-size: 13px; font-weight: 700; color: var(--text-primary); } .subapp-desc { font-size: 11px; color: var(--text-secondary); line-height: 1.4; } /* Generic buttons */ .btn { padding: 10px 16px; border: none; border-radius: 10px; font-weight: 600; cursor: pointer; background: linear-gradient(135deg, var(--accent-purple), var(--accent-primary-dark)); color: white; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; transition: all 0.2s; } .btn:hover { transform: translateY(-1px); box-shadow: 0 8px 24px var(--accent-glow); } .btn-secondary { background: var(--surface); color: var(--text-primary); border: 1px solid var(--border-soft); } .btn-secondary:hover { background: var(--surface-hover); } .btn-danger { background: linear-gradient(135deg, #ef4444, #b91c1c); } .btn-cyan { background: linear-gradient(135deg, var(--accent-cyan), #0891b2); } .btn-small { padding: 6px 10px; font-size: 11px; } /* Modals */ .modal-backdrop { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.75); backdrop-filter: blur(6px); z-index: 1000; align-items: center; justify-content: center; padding: 16px; overflow-y: auto; } .modal-backdrop.active { display: flex; } .modal { background: linear-gradient(180deg, rgba(26,11,46,0.95), rgba(10,1,24,0.95)); border: 1px solid var(--border-strong); border-radius: 18px; width: 100%; max-width: 560px; max-height: 90vh; overflow-y: auto; padding: 22px; box-shadow: 0 20px 60px rgba(0,0,0,0.7), 0 0 40px rgba(168,85,247,0.2); animation: fadeIn 0.25s ease-out; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; gap: 12px; } .modal-title { font-size: 16px; font-weight: 700; background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); -webkit-background-clip: text; background-clip: text; color: transparent; } .modal-close { background: var(--surface); border: 1px solid var(--border-soft); color: var(--text-primary); font-size: 18px; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; } .modal-body { font-size: 13px; line-height: 1.6; color: var(--text-secondary); } /* Corey Voice modal styling */ .corey-text { background: var(--surface); border-left: 3px solid var(--accent-cyan); padding: 12px 14px; border-radius: 8px; margin-bottom: 12px; font-size: 13px; line-height: 1.6; color: var(--text-primary); font-style: italic; } /* Nested tabs inside an app modal (Lilith, SingZ, RapZ, BodieZ) */ .nested-tabs { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 8px; margin-bottom: 12px; border-bottom: 1px solid var(--border-soft); scrollbar-width: none; } .nested-tabs::-webkit-scrollbar { display: none; } .nested-tab { flex-shrink: 0; padding: 6px 10px; background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px; font-size: 11px; font-weight: 600; color: var(--text-secondary); cursor: pointer; white-space: nowrap; } .nested-tab.active { background: linear-gradient(135deg, var(--accent-purple), var(--accent-primary-dark)); color: white; border-color: transparent; } .nested-panel { background: var(--surface); border-radius: 10px; padding: 12px; font-size: 12px; line-height: 1.6; color: var(--text-secondary); } /* Skill / persona buttons (kept from existing app, restyled) */ .skill-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; } .skill-btn { padding: 8px 10px; background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px; color: var(--text-primary); font-size: 11px; font-weight: 600; cursor: pointer; text-align: center; transition: all 0.2s; } .skill-btn:hover { background: var(--surface-hover); } .skill-btn.sel { background: linear-gradient(135deg, var(--accent-cyan), #0891b2); color: white; border-color: var(--accent-cyan); box-shadow: 0 0 12px rgba(34,211,238,0.4); } .skill-btn.any { border-style: dashed; } .skill-btn.any.sel { background: linear-gradient(135deg, var(--accent-gold), #f59e0b); box-shadow: 0 0 12px rgba(251,191,36,0.4); } .tag { display: inline-block; background: var(--surface); border: 1px solid var(--border-soft); padding: 3px 8px; border-radius: 999px; font-size: 10px; margin: 2px; color: var(--text-secondary); } /* Persona cards */ .persona-card { background: linear-gradient(135deg, rgba(168,85,247,0.15), rgba(34,211,238,0.08)); border: 1px solid var(--border-soft); border-radius: 12px; padding: 14px; margin-bottom: 10px; } .persona-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 8px; } .persona-name { font-weight: 700; font-size: 14px; } /* Empty state */ .empty { text-align: center; padding: 28px 14px; color: var(--text-muted); font-size: 13px; } /* Grids for forms */ .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } @media (max-width: 480px) { .grid-2 { grid-template-columns: 1fr; } } /* Posts feed (for PostZ, CollabZ, BugZ etc.) */ .post-card { background: var(--surface); border: 1px solid var(--border-soft); border-radius: 12px; padding: 14px; margin-bottom: 10px; } .post-head { display: flex; gap: 10px; margin-bottom: 8px; align-items: center; } .post-avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--accent-purple); color: white; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; } .post-user { font-weight: 700; font-size: 13px; } .post-meta { font-size: 10px; color: var(--text-muted); } .post-body { font-size: 13px; line-height: 1.5; color: var(--text-primary); margin-bottom: 8px; } .post-actions { display: flex; gap: 6px; flex-wrap: wrap; } /* Hide helper */ .hidden { display: none !important; } /* Floating "+" action button for tabs that have a primary create action */ .fab { position: fixed; bottom: 20px; right: 20px; width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(135deg, var(--accent-pink), var(--accent-purple)); color: white; font-size: 28px; border: none; box-shadow: 0 10px 30px rgba(236,72,153,0.4); cursor: pointer; z-index: 50; display: none; align-items: center; justify-content: center; } .fab.show { display: flex; } ๐ŸŽต Music ConnectZ Sign In Register Welcome back Sign in to your Music ConnectZ account Email or username Password Sign In Create your account Join Music ConnectZ in seconds Email Username Birthday (for ZodiacZ) Password Create Account By continuing, you agree to our Terms and Privacy Policy. ๐ŸŽต Music ConnectZ โ™“ Add Birthday ๐Ÿ’ฐ $0.00 ๐Ÿ‘ค + Title โœ• /* ======================================================================== APP STATE & PERSISTENCE ======================================================================== */ /* Default state shape. Anything saved to localStorage is merged on load so adding new fields here doesn't break existing users. */ const defaultState = { user: { username: '', email: '', phone: '', birthday: '', gender: '', location: '', bio: '', profilePic: null, // ZodiacZ derived fields zodiacSign: '', zodiacSymbol: '', zodiacEmoji: '', zodiacDateRange: '', zodiacAiSummary: '' }, auth: { loggedIn: false }, personas: [], // [{ key, name, skills:[{name, startDate}] }] posts: [], // PostZ posts (audio/image/text) examples: [], // legacy: kept for compatibility with collab feed collabs: [], bugs: [], groups: { Friends: [], Fans: [], Partners: [], Blocked: [], Custom: [] }, labels: [], messages: { inbox: [], outbox: [] }, wallet: { balance: 0, earned: 0 }, paymentHistory: [], settings: { themePalette: 'purple', notifications: true, soundEffects: true, shareLocation: true }, /* Premium gate โ€” toggled true once the user upgrades. The 8-color theme picker only appears for premium users. For demo/dev convenience, flip this in localStorage or via the dev toggle in SettingZ. */ isPremium: false }; let state = JSON.parse(JSON.stringify(defaultState)); function loadState() { try { const saved = localStorage.getItem('mcz_state_v2'); if (saved) { const parsed = JSON.parse(saved); // Shallow merge top-level keys; deep merge user object so new ZodiacZ // fields don't disappear for returning users. state = { ...defaultState, ...parsed, user: { ...defaultState.user, ...(parsed.user || {}) } }; } } catch (e) { console.warn('State load failed', e); } } function saveState() { try { localStorage.setItem('mcz_state_v2', JSON.stringify(state)); } catch (e) {} } /* ======================================================================== THEME PALETTES โ€” Dark base never changes. What changes is the PRIMARY accent (the glow color used on buttons, the active tab pill, the persona-card gradient, surface tints). Premium users can pick from the 8 colors below. Non-premium users are locked to 'purple', which matches the login screen the rest of the app inherited. Each palette specifies: primary โ€” the main accent hex primaryDark โ€” the darker shade used as the second stop in gradients rgb โ€” comma-separated RGB triplet used for rgba() surfaces ======================================================================== */ const THEME_PALETTES = { purple: { name: 'Purple', emoji: '๐ŸŸฃ', primary: '#a855f7', primaryDark: '#7c3aed', rgb: '168, 85, 247' }, blue: { name: 'Blue', emoji: '๐Ÿ”ต', primary: '#2196F3', primaryDark: '#1565C0', rgb: '33, 150, 243' }, red: { name: 'Red', emoji: '๐Ÿ”ด', primary: '#F44336', primaryDark: '#D32F2F', rgb: '244, 67, 54' }, green: { name: 'Green', emoji: '๐ŸŸข', primary: '#4CAF50', primaryDark: '#388E3C', rgb: '76, 175, 80' }, orange: { name: 'Orange', emoji: '๐ŸŸ ', primary: '#FF9800', primaryDark: '#F57C00', rgb: '255, 152, 0' }, pink: { name: 'Pink', emoji: '๐ŸŒธ', primary: '#E91E63', primaryDark: '#C2185B', rgb: '233, 30, 99' }, teal: { name: 'Teal', emoji: '๐Ÿ”ท', primary: '#00BCD4', primaryDark: '#0097A7', rgb: '0, 188, 212' }, indigo: { name: 'Indigo', emoji: '๐Ÿ’™', primary: '#3F51B5', primaryDark: '#303F9F', rgb: '63, 81, 181' } }; function applyThemePalette(key) { const p = THEME_PALETTES[key] || THEME_PALETTES.purple; const r = document.documentElement.style; r.setProperty('--accent-primary', p.primary); r.setProperty('--accent-primary-dark', p.primaryDark); r.setProperty('--accent-glow', `rgba(${p.rgb}, 0.4)`); r.setProperty('--border-soft', `rgba(${p.rgb}, 0.2)`); r.setProperty('--border-strong', `rgba(${p.rgb}, 0.4)`); r.setProperty('--surface', `rgba(${p.rgb}, 0.06)`); r.setProperty('--surface-hover', `rgba(${p.rgb}, 0.12)`); state.settings.themePalette = key; saveState(); } /* ======================================================================== ZODIACZ - birthday-driven sign auto-detection ======================================================================== */ const ZODIAC_TABLE = [ // [start_month, start_day, end_month, end_day, sign, symbol, emoji, range, summary] [3, 21, 4, 19, 'Aries', 'โ™ˆ', '๐Ÿ', '3/21-4/19', 'Bold, energetic, a natural starter โ€” perfect for kicking off new collabs.'], [4, 20, 5, 20, 'Taurus', 'โ™‰', '๐Ÿ‚', '4/20-5/20', 'Grounded, consistent, loves a tight pocket and a deep groove.'], [5, 21, 6, 20, 'Gemini', 'โ™Š', '๐Ÿ‘ฏ', '5/21-6/20', 'Quick-tongued and adaptive โ€” your VibeZ go in every direction.'], [6, 21, 7, 22, 'Cancer', 'โ™‹', '๐Ÿฆ€', '6/21-7/22', 'Emotional depth that fuels melodies people actually feel.'], [7, 23, 8, 22, 'Leo', 'โ™Œ', '๐Ÿฆ', '7/23-8/22', 'Star energy โ€” you were built for BattleZ and the spotlight.'], [8, 23, 9, 22, 'Virgo', 'โ™', '๐Ÿ‘ฉ', '8/23-9/22', 'Detail-perfect mixer brain โ€” your tracks are surgically clean.'], [9, 23, 10, 22, 'Libra', 'โ™Ž', 'โš–๏ธ', '9/23-10/22', 'Balance and aesthetics โ€” design and arrangement come naturally.'], [10, 23, 11, 21, 'Scorpio', 'โ™', '๐Ÿฆ‚', '10/23-11/21','Intense, magnetic, drawn to dark melodic depth.'], [11, 22, 12, 21, 'Sagittarius','โ™', '๐Ÿน', '11/22-12/21','Boundary-pushing collaborator with a global ear.'], [12, 22, 1, 19, 'Capricorn', 'โ™‘', '๐Ÿ', '12/22-1/19', 'Career-mode artist โ€” every session is a brick in the empire.'], [1, 20, 2, 18, 'Aquarius', 'โ™’', '๐ŸŒŠ', '1/20-2/18', 'Future-facing weirdo (compliment) โ€” your sound is ahead of trend.'], [2, 19, 3, 20, 'Pisces', 'โ™“', '๐ŸŸ', '2/19-3/20', 'Dreamy and intuitive โ€” you write what other people only feel.'] ]; function detectZodiac(birthdayISO) { if (!birthdayISO) return null; const d = new Date(birthdayISO + 'T00:00:00'); const m = d.getMonth() + 1; const day = d.getDate(); for (const z of ZODIAC_TABLE) { const [sm, sd, em, ed, sign, symbol, emoji, range, summary] = z; // Standard range if (sm === em) { if (m === sm && day >= sd && day <= ed) return { sign, symbol, emoji, range, summary }; } else if (sm < em) { if ((m === sm && day >= sd) || (m === em && day <= ed)) return { sign, symbol, emoji, range, summary }; } else { // Capricorn wraps Decโ†’Jan if ((m === sm && day >= sd) || (m === em && day <= ed)) return { sign, symbol, emoji, range, summary }; } } return null; } function applyZodiacFromBirthday() { const z = detectZodiac(state.user.birthday); if (z) { state.user.zodiacSign = z.sign; state.user.zodiacSymbol = z.symbol; state.user.zodiacEmoji = z.emoji; state.user.zodiacDateRange = z.range; state.user.zodiacAiSummary = `AI DetectZ: Based on your birthday, your ZodiacZ is ${z.symbol} ${z.sign} ${z.emoji}.`; saveState(); } renderZodiacChip(); } function renderZodiacChip() { const el = document.getElementById('zodiacChip'); if (!el) return; if (state.user.zodiacSign) { el.textContent = `${state.user.zodiacSymbol} ${state.user.zodiacSign} ${state.user.zodiacEmoji}`; } else { el.textContent = 'โ™“ Add Birthday'; } } function openZodiacModal() { const z = state.user.zodiacSign ? `
${state.user.zodiacAiSummary}
Sign: ${state.user.zodiacSymbol} ${state.user.zodiacSign} ${state.user.zodiacEmoji}
Date range: ${state.user.zodiacDateRange}
In-app vibe: ${detectZodiac(state.user.birthday)?.summary || ''}
ZodiacZ flavors your profile, your VibeZ matches, and your BattleZ commentary.
` : `
Add your birthday to unlock ZodiacZ. It auto-detects your sign and uses it across matching, profile flavor, and BattleZ commentary โ€” no manual selection needed.
`; showModal('ZodiacZ', z); } function saveZodiacBday() { const v = document.getElementById('zodiacBdayInput').value; if (!v) return; state.user.birthday = v; applyZodiacFromBirthday(); closeModal(); // Refresh current page so any zodiac-aware content updates route(currentRoute()); } /* ======================================================================== TAB / ROUTE DEFINITIONS This is the single source of truth for the top-level tabs and their sub-apps. URL hash is /tabkey or /tabkey/subkey. ======================================================================== */ const ICON = (file, fallbackEmoji) => ({ // 'file' is relative path to icon image; we let CSS background-image take it // and fall back to an emoji if the file is missing in deployment. file, fallback: fallbackEmoji }); const TABS = [ { key: 'home', label: 'Home', emoji: '๐Ÿ ', title: '๐Ÿ  Home', corey: "Yo โ€” this is your home base. Personas, skills, profile pic, ZodiacZ. Set this up first and the whole app gets smarter about you.", subapps: [] }, { key: 'collabz', label: 'CollabZ', emoji: '๐Ÿค', title: '๐Ÿค CollabZ โ€” Collaborate with other users', corey: "CollabZ is where you actually work with people. Covers, remixes, projects. You make it, somebody else flips it. Or you flip theirs.", subapps: [ { key: 'coverz', name: 'CoverZ', emoji: '๐Ÿซด๐Ÿผ', desc: "Covers of songs, redraws of existing art, performing existing song covers. Tribute mode.", icon: ICON(null, '๐Ÿซด๐Ÿผ') }, { key: 'remixez', name: 'RemixeZ', emoji: '๐Ÿ”„', desc: "Remix posts and songz. Take someone's PostZ and flip it into your version.", icon: ICON(null, '๐Ÿ”„') } ] }, { key: 'battlez', label: 'BattleZ', emoji: '๐Ÿช–', title: '๐Ÿช– BattleZ โ€” Post vs Post', corey: "One post vs another. Contestants verified 18+ can bet real money on themselves. Everyone else throws SpinaZ. Winner takes the pot, loser takes the lesson.", subapps: [ { key: 'freestyle', name: 'Freestyle', emoji: '๐Ÿ†“', desc: "Live, sporadic battles. Open mic, no notice, prove it in the moment.", icon: ICON(null, '๐Ÿ†“') }, { key: '1v1', name: '1v1', emoji: '1โƒฃ', desc: "One artist vs one artist. Other personas helping doesn't change the count โ€” it's still 1v1.", icon: ICON(null, '1โƒฃ') }, { key: 'cypher', name: 'Battle Cypher', emoji: '๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘', desc: "More than one artist vs more than one artist. Personas helping doesn't change the count.", icon: ICON(null, '๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘') } ] }, { key: 'social-connectz', label: 'Social', emoji: '๐Ÿ’“', title: '๐Ÿ’“ Social ConnectZ', corey: "Match for romance or collab โ€” and we make you say which. No more 'oh I thought this was professional.' Pick a lane.", subapps: [ { key: 'vibez', name: 'VibeZ', emoji: 'โ™ฅ๏ธ', desc: "Plenty-of-Fish style. Advanced match for romance or collab โ€” you mark which up front.", icon: ICON('assets/icons/vibez.jpg', 'โ™ฅ๏ธ') }, { key: 'inferno', name: 'Inferno', emoji: 'โค๏ธโ€๐Ÿ”ฅ', desc: "Tinder style. Fast swipe match for romance or quick collab. Intent flagged on every profile.", icon: ICON('assets/icons/inferno.jpg', 'โค๏ธโ€๐Ÿ”ฅ') }, { key: 'boardz', name: 'BoardZ', emoji: '๐Ÿชง', desc: "Old-school message boards. Threaded talk, no algorithm.", icon: ICON(null, '๐Ÿชง') }, { key: 'personalitiez', name: 'PersonalitieZ', emoji: '๐Ÿ˜ถ', desc: "MBTI tests, simple and complex. Feeds into VibeZ + Inferno matching.", icon: ICON(null, '๐Ÿ˜ถ') }, { key: 'preferencez', name: 'PreferenceZ', emoji: '๐Ÿšป', desc: "Partner genderZ + compatibility filters. Powers who shows up in VibeZ and Inferno.", icon: ICON('assets/icons/preferencez.jpg', '๐Ÿšป') } ] }, { key: 'messagez', label: 'MessageZ', emoji: '๐Ÿ“จ', title: '๐Ÿ“จ MessageZ', corey: "Inbox + outbox. That's it. No clever feed, no algorithmic 'priority' โ€” your messages, in order, sent and received.", subapps: [ { key: 'inbox', name: 'Inbox', emoji: '๐Ÿ“ฅ', desc: "Messages people sent you.", icon: ICON(null, '๐Ÿ“ฅ') }, { key: 'outbox', name: 'Outbox', emoji: '๐Ÿ“ค', desc: "Messages you sent.", icon: ICON(null, '๐Ÿ“ค') } ] }, { key: 'callz', label: 'CallZ', emoji: '๐Ÿ“ž', title: '๐Ÿ“ž CallZ โ€” Voice & AI calls', corey: "Calls cost money. Available to StatZ-tier users. SpinaZ won't cut it here โ€” try, you'll get 'coming soon' and a prompt to load real balance via Stripe/PayPal.", subapps: [ { key: 'ai', name: 'AI CallZ', emoji: '๐Ÿค–', desc: "Talk to AI. Cost depends on the model you pick.", icon: ICON('assets/icons/ai-call.png', '๐Ÿค–') }, { key: 'user', name: 'User CallZ', emoji: '๐Ÿ—ฃ๏ธ', desc: "Talk to other users. Cost = that user's hourly skill rate.", icon: ICON('assets/icons/user-callz.webp', '๐Ÿ—ฃ๏ธ') } ] }, { key: 'bugz', label: 'BugZ', emoji: '๐Ÿž', title: '๐Ÿž BugZ โ€” Report it, get paid for it', corey: "Found a bug? Post it. Admin marks it 'In Progress' or 'Squashed'. Squashed = 200 SpinaZ in your account. Quality bug reports are basically a side hustle.", subapps: [ { key: 'submit', name: 'Submit Bug', emoji: '๐Ÿ“', desc: "Submit a bug as a post. Include steps to reproduce.", icon: ICON('assets/icons/bugz.jpg', '๐Ÿž') }, { key: 'feed', name: 'Bug Feed', emoji: '๐Ÿ“‹', desc: "All posted bugs and their status: ๐Ÿ’ญ In Progress, ๐Ÿชฆ Squashed.", icon: ICON(null, '๐Ÿ“‹') } ] }, { key: 'distributez', label: 'DistributeZ', emoji: '๐ŸŽถ', title: '๐ŸŽถ DistributeZ โ€” Submit for distribution', corey: "Audio becomes the track. Image becomes the cover. Text becomes lyrics. Album posts become album submissions. Free users get 1 a month โ€” Premium and StatZ get unlimited and StatZ can submit for licensing.", subapps: [ { key: 'single', name: 'Submit Single', emoji: '๐ŸŽต', desc: "Submit one PostZ as a single distribution.", icon: ICON(null, '๐ŸŽต') }, { key: 'album', name: 'Submit Album', emoji: '๐Ÿ’ฟ', desc: "Album posts populate as album submissions. Editable before send.", icon: ICON(null, '๐Ÿ’ฟ') }, { key: 'licensing', name: 'Licensing (StatZ)', emoji: 'โš–๏ธ', desc: "StatZ only โ€” submit for sync and licensing deals.", icon: ICON(null, 'โš–๏ธ') } ] }, { key: 'royaltiez', label: 'RoyaltieZ', emoji: '๐Ÿ‘‘', title: '๐Ÿ‘‘ RoyaltieZ โ€” Balance & logz', corey: "Your money. Where it came from. When it landed. Timestamped, no hand-waving.", subapps: [] }, { key: 'groupz', label: 'GroupZ', emoji: '๐Ÿ‘ฅ', title: '๐Ÿ‘ฅ GroupZ โ€” Your private circles', corey: "GroupZ are local to you โ€” you decide who's a friend, partner, fan, blocked. Custom groups too. Nobody else sees your labels.", subapps: [ { key: 'friends', name: 'Friends', emoji: '๐Ÿ™‚', desc: "Mutual benefit relationships.", icon: ICON(null, '๐Ÿ™‚') }, { key: 'fans', name: 'Fans', emoji: '๐Ÿ‘‹๐Ÿฝ', desc: "Less need from you, more from them.", icon: ICON(null, '๐Ÿ‘‹๐Ÿฝ') }, { key: 'partners', name: 'Partners', emoji: '๐Ÿ‘๐Ÿฝ', desc: "People you work with frequently.", icon: ICON(null, '๐Ÿ‘๐Ÿฝ') }, { key: 'blocked', name: 'Blocked', emoji: '๐Ÿคš๐Ÿฝ', desc: "Cannot contact you.", icon: ICON(null, '๐Ÿคš๐Ÿฝ') }, { key: 'custom', name: 'Custom', emoji: 'โ“', desc: "Make your own labels.", icon: ICON(null, 'โ“') } ] }, { key: 'labelz', label: 'LabelZ', emoji: '๐Ÿท๏ธ', title: '๐Ÿท๏ธ LabelZ โ€” Public label-style groups', corey: "Public GroupZ with teeth. Premium A&R Scout or Manager persona required to create or edit. Real label flow: advances, terms, e-signed contracts.", subapps: [] }, { key: 'toolz', label: 'ToolZ', emoji: '๐Ÿ’ก', title: '๐Ÿ’ก ToolZ โ€” Build, write, draw, edit, sing, rap, train', corey: "Everything that opens media also lives here. Every tool has its own URL and its own modal. Text, image, video, plus the Lilith/SingZ/RapZ/BodieZ practice apps.", subapps: [ { key: 'keyconnectz', name: 'Key ConnectZ', emoji: 'โŒจ๏ธ', desc: "System keyboard on mobile, floating keyboard on desktop. Ctrl+Shift+K. Custom bg, transcription for premium.", icon: ICON(null, 'โŒจ๏ธ') }, { key: 'sentencez', name: 'SentenceZ', emoji: '๐Ÿ“ƒ', desc: "Word knockoff. Every text media opens here.", icon: ICON(null, '๐Ÿ“ƒ') }, { key: 'imagez', name: 'ImageZ', emoji: '๐Ÿ–ผ๏ธ', desc: "Photoshop knockoff. Every image media opens here.", icon: ICON(null, '๐Ÿ–ผ๏ธ') }, { key: 'videoz', name: 'VideoZ', emoji: '๐Ÿ“น', desc: "Sony Vegas knockoff. Every video media opens here.", icon: ICON(null, '๐Ÿ“น') }, { key: 'lilith', name: 'Lilith', emoji: '๐Ÿ’ƒ๐Ÿฝ', desc: "Apple Things knockoff โ€” task & life capture. Inbox โ†’ Today โ†’ Upcoming โ†’ Logbook.", icon: ICON(null, '๐Ÿ’ƒ๐Ÿฝ') }, { key: 'singz', name: 'SingZ', emoji: '๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค', desc: "Singing practice app. RoutineZ, ExerciseZ, Session, VoiceMap, Coach.", icon: ICON('assets/icons/singz.jpg', '๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค') }, { key: 'rapz', name: 'RapZ', emoji: '๐Ÿ‘จ๐Ÿผโ€๐ŸŽค', desc: "Rapping practice app. FlowZ, RhymeZ, RapMap, every style on file.", icon: ICON('assets/icons/rapz.jpg', '๐Ÿ‘จ๐Ÿผโ€๐ŸŽค') }, { key: 'bodiez', name: 'BodieZ', emoji: '๐Ÿ’ช๐Ÿฝ', desc: "Jefit-style fitness companion. Routines, Session, BodyMap, Coach.", icon: ICON(null, '๐Ÿ’ช๐Ÿฝ') }, { key: 'dawz', name: 'DawZ', emoji: '๐ŸŽ›๏ธ', desc: "Digital Audio WorkstationZ. The in-app studio.", icon: ICON('assets/icons/dawz.jpg', '๐ŸŽ›๏ธ') } ] }, { key: 'postz', label: 'PostZ', emoji: '๐Ÿ“ฃ', title: '๐Ÿ“ฃ PostZ โ€” Your output', corey: "Every track, image, lyric, idea โ€” it's a PostZ. PostZ feed everything else: BattleZ, DistributeZ, CollabZ all read from your PostZ.", subapps: [ { key: 'create', name: 'Create PostZ', emoji: 'โšก', desc: "Make a new post: audio, image, video, or text.", icon: ICON('assets/icons/energy.jpg', 'โšก') }, { key: 'builder', name: 'PostZ Builder', emoji: '๐Ÿ—๏ธ', desc: "Manage and edit your post library.", icon: ICON('assets/icons/builder.jpg', '๐Ÿ—๏ธ') }, { key: 'feed', name: 'Feed', emoji: '๐Ÿ“ฐ', desc: "Public feed of PostZ from people you follow.", icon: ICON('assets/icons/postz.jpg', '๐Ÿ“ฐ') } ] }, { key: 'ratez', label: 'RateZ', emoji: 'โญ', title: 'โญ RateZ โ€” AI Media Ratings', corey: "AI scores every PostZ on audio quality, visual impact, creativity, engagement. Community thumbs in too. Live feed of what's hot this week.", subapps: [ { key: 'top', name: 'Top Rated', emoji: '๐Ÿ†', desc: "This week's top-scored PostZ.", icon: ICON('assets/icons/ratez.jpg', '๐Ÿ†') }, { key: 'live', name: 'Live Feed', emoji: '๐Ÿ“ก', desc: "New ratings as they happen.", icon: ICON(null, '๐Ÿ“ก') }, { key: 'community', name: 'Community', emoji: '๐Ÿ’ฌ', desc: "Written reviews from other users.", icon: ICON(null, '๐Ÿ’ฌ') } ] }, { key: 'venuez', label: 'VenueZ', emoji: '๐Ÿ›๏ธ', title: '๐Ÿ›๏ธ VenueZ โ€” Go places with your music', corey: "Venues, open mics, festivals, showcases mapped near you. Filter by city, capacity, genre fit.", subapps: [ { key: 'near', name: 'Near Me', emoji: '๐Ÿ“', desc: "Venues close to your location.", icon: ICON('assets/icons/venuez.jpg', '๐Ÿ“') }, { key: 'book', name: 'Book', emoji: '๐ŸŽซ', desc: "Submit a booking request.", icon: ICON(null, '๐ŸŽซ') }, { key: 'mybookings', name: 'My Bookings', emoji: '๐Ÿ“…', desc: "Your confirmed and pending shows.", icon: ICON(null, '๐Ÿ“…') } ] }, { key: 'intelligence', label: 'Intelligence', emoji: '๐Ÿง ', title: '๐Ÿง  Intelligence โ€” AI Media Creation', corey: "AI side of the house: generate audio, art, video, voice, 3D. Inputs in, media files out. Free tier gets a taste, premium goes deep.", subapps: [ { key: 'audio', name: 'Audio AI', emoji: '๐Ÿ”Š', desc: "AI-generated sound, music, voice.", icon: ICON('assets/icons/intelligence.jpg', '๐Ÿ”Š') }, { key: 'image', name: 'Image AI', emoji: '๐Ÿ–ผ๏ธ', desc: "AI-generated images and covers.", icon: ICON(null, '๐Ÿ–ผ๏ธ') }, { key: 'video', name: 'Video AI', emoji: '๐ŸŽž๏ธ', desc: "AI-generated video clips.", icon: ICON(null, '๐ŸŽž๏ธ') }, { key: 'voice', name: 'Voice AI', emoji: '๐ŸŽค', desc: "Voice clones, narration, dubbing.", icon: ICON(null, '๐ŸŽค') } ] }, { key: 'originalz', label: 'OriginalZ', emoji: '๐Ÿงฌ', title: '๐Ÿงฌ OriginalZ โ€” Unique SongZ verification', corey: "Audio-DNA scan against the catalog. Confirms a song is original before BattleZ accepts it or DistributeZ pushes it out.", subapps: [] }, { key: 'settingz', label: 'SettingZ', emoji: 'โš™๏ธ', title: 'โš™๏ธ SettingZ', corey: "Theme, notifications, location, account. Standard.", subapps: [] } ]; /* ======================================================================== ROUTING โ€” hash-based, single-page, so every tab and sub-app modal gets a real URL like #/toolz/lilith. Matches the spec exactly while staying deployable to any static host. ======================================================================== */ function currentRoute() { const h = location.hash.replace(/^#\//, '').replace(/^#/, ''); return h || 'home'; } function route(path) { // path can be 'home' or 'toolz/lilith' location.hash = '#/' + path; } window.addEventListener('hashchange', renderRoute); function renderRoute() { if (!state.auth.loggedIn) { // Force back to login if someone deep-links into an app URL while logged out document.getElementById('screen-login').style.display = 'flex'; document.getElementById('screen-app').style.display = 'none'; return; } document.getElementById('screen-login').style.display = 'none'; document.getElementById('screen-app').style.display = 'block'; const parts = currentRoute().split('/'); const tabKey = parts[0]; const subKey = parts[1]; renderTabBar(tabKey); renderPage(tabKey); if (subKey) openSubapp(tabKey, subKey); else closeModal(true); // close any open sub-app modal when navigating to bare tab } function renderTabBar(activeKey) { const bar = document.getElementById('tabBar'); bar.innerHTML = TABS.map(t => ` ${t.emoji}${t.label} ` ).join(''); // Scroll the active tab into view so it isn't off-screen on mobile const active = bar.querySelector('.tab-link.active'); if (active) active.scrollIntoView({ inline: 'center', behavior: 'smooth', block: 'nearest' }); } /* ======================================================================== PAGE RENDERERS Each top-level tab has a renderer. Pages that don't need bespoke layout fall through to renderGenericTab, which lays out the sub-apps as cards. ======================================================================== */ function renderPage(tabKey) { const tab = TABS.find(t => t.key === tabKey) || TABS[0]; const content = document.getElementById('appContent'); document.getElementById('fab').classList.remove('show'); // Header is consistent across pages let html = ` `; // Dispatch to bespoke renderers, fall through to generic if (tabKey === 'home') html += renderHomePage(); else if (tabKey === 'royaltiez') html += renderRoyaltiezPage(); else if (tabKey === 'groupz') html += renderGroupzPage(); else if (tabKey === 'labelz') html += renderLabelzPage(); else if (tabKey === 'settingz') html += renderSettingzPage(); else if (tabKey === 'bugz') html += renderBugzPage(); else if (tabKey === 'messagez') html += renderMessagezPage(); else html += renderGenericTab(tab); content.innerHTML = html; } function renderGenericTab(tab) { if (!tab.subapps.length) { return `
Coming soon. This tab has its own URL and will populate as features land.
`; } return `
Apps in this tab
${tab.subapps.map(s => subappCard(tab.key, s)).join('')}
`; } function subappCard(tabKey, sub) { // If we have an icon file, paint it as background-image; otherwise emoji fallback const iconStyle = sub.icon.file ? `background-image:url('${sub.icon.file}'); background-size:cover; background-position:center;` : ''; const iconContent = sub.icon.file ? '' : sub.icon.fallback; return `
${iconContent}
${sub.emoji} ${sub.name}
${sub.desc}
`; } /* ---- Home page: profile + personas + ZodiacZ summary ---- */ function renderHomePage() { const u = state.user; const skillsList = state.personas.flatMap(p => p.skills.map(s => s.name)); return `
๐Ÿ‘ค Profile
${u.zodiacSign ? `${u.zodiacSymbol} ZodiacZ: ${u.zodiacSign} ${u.zodiacEmoji}` : 'โ™“ ZodiacZ'}
${u.zodiacSign ? `
${u.zodiacAiSummary}
Range: ${u.zodiacDateRange}
` : `
Add your birthday above to unlock ZodiacZ.
`}
๐ŸŽญ PersonaZ
${state.personas.length === 0 ? `
No personas yet. Add one to start posting skills.
` : state.personas.map((p, i) => `
${p.name}
${p.skills.map(s => `${esc(s.name)} ยท ${esc(s.startDate)}`).join('') || ''}
`).join('')}
`; } function saveProfile() { state.user.username = val('f-username'); state.user.email = val('f-email'); state.user.phone = val('f-phone'); state.user.birthday = val('f-birthday'); state.user.gender = val('f-gender'); state.user.location = val('f-location'); state.user.bio = val('f-bio'); applyZodiacFromBirthday(); saveState(); toast('โœ“ Profile saved'); renderPage('home'); } /* ---- Persona/skill database (kept compatible with old app structure) ---- */ const PERSONA_DB = { 'artist': { label: '๐ŸŽค Independent Artist', icon: 'assets/icons/persona-artist.jpg', categories: { 'Rapping': ['Any Rapping ๐ŸŽค','Alternative Rap ๐ŸŽธ','Boom Bap ๐Ÿฅ','Chopper ๐Ÿš','Cloud Rap โ˜๏ธ','Conscious Rap ๐Ÿง ','Crunk ๐Ÿ”ฅ','Drill โš”๏ธ','Emo Rap ๐Ÿ–ค','G-Funk ๐ŸŒด','Gangsta Rap โ›“๏ธ','Hardcore Hip Hop ๐ŸŽค','Jazz Rap ๐ŸŽท','Mumble Rap ๐Ÿ’ค','Old School ๐Ÿ“ป','Snap ๐Ÿซฐ','Trap ๐Ÿš๏ธ'], 'Singing': ['Any Singing ๐ŸŽถ','Bass ๐Ÿง”โ€โ™‚๏ธ','Baritone ๐ŸŽ™๏ธ','Tenor ๐ŸŽค','Countertenor ๐Ÿ•Š๏ธ','Contralto ๐ŸŽป','Alto ๐ŸŽถ','Mezzo-Soprano ๐ŸŒŠ','Soprano โ˜€๏ธ'], 'Strings': ['Any String ๐ŸŽธ','Acoustic Guitar ๐ŸŽธ','Electric Guitar ๐ŸŽธ','Bass Guitar ๐ŸŽธ','Violin ๐ŸŽป','Cello ๐ŸŽป','Ukulele ๐ŸŽธ'], 'Keys': ['Any Keyboard ๐ŸŽน','Acoustic Piano ๐ŸŽน','Synthesizer ๐ŸŽน','Organ ๐ŸŽน'], 'Percussion': ['Any Percussion ๐Ÿฅ','Drums ๐Ÿฅ','Cymbals ๐Ÿฅ'] } }, 'producer': { label: '๐ŸŽš๏ธ Beat Producer', icon: null, categories: { 'DAWs': ['Any DAW ๐ŸŽ›๏ธ','Ableton Live ๐ŸŽต','FL Studio ๐ŸŽš๏ธ','Logic Pro ๐ŸŽต','Pro Tools ๐ŸŽ™๏ธ','Reason ๐ŸŽ›๏ธ','Reaper ๐Ÿชฆ','Studio One ๐ŸŽ›๏ธ'], 'Production': ['Any Production ๐ŸŽš๏ธ','Beat Making ๐ŸŽš๏ธ','Sampling ๐ŸŽต','Sound Design ๐ŸŽ›๏ธ','Arrangement ๐ŸŽผ','Synthesis ๐ŸŽน'] } }, 'engineer': { label: '๐ŸŽ›๏ธ Mix Engineer', icon: null, categories: { 'DAWs': ['Any DAW ๐ŸŽ›๏ธ','Pro Tools ๐ŸŽ™๏ธ','Logic Pro ๐ŸŽต','Ableton Live ๐ŸŽต','FL Studio ๐ŸŽš๏ธ'], 'Engineering': ['Any Engineering ๐ŸŽ›๏ธ','Mixing ๐ŸŽ›๏ธ','Mastering ๐ŸŽ™๏ธ','EQ ๐Ÿ“Š','Compression ๐Ÿ”ง','Reverb/Effects โœจ'] } }, 'designer': { label: '๐ŸŽจ Designer', icon: null, categories: { 'Software': ['Any Design Software ๐ŸŽจ','Photoshop ๐ŸŽจ','Illustrator ๐Ÿ–Œ๏ธ','Figma ๐ŸŽฏ','Canva ๐ŸŒˆ'], 'Skills': ['Any Design Skill ๐ŸŽจ','UI/UX Design ๐ŸŽฏ','Graphic Design ๐Ÿ–Œ๏ธ','Branding ๐Ÿท๏ธ','Typography ๐Ÿ”ค'] } }, 'videographer': { label: '๐ŸŽฌ Videographer', icon: null, categories: { 'Software': ['Any Video Software ๐ŸŽฌ','Adobe Premiere ๐ŸŽฌ','DaVinci Resolve ๐ŸŽž๏ธ','Final Cut Pro ๐ŸŽฅ','After Effects โœจ'], 'Skills': ['Any Video Skill ๐ŸŽฌ','Editing ๐ŸŽฌ','Color Grading ๐ŸŽจ','Motion Graphics โœจ','Cinematography ๐ŸŽฅ'] } }, 'manager': { label: '๐Ÿ•ด๐Ÿผ Manager', icon: null, categories: { 'Skills': ['A&R ๐ŸŽฏ','Tour Booking ๐Ÿš','Contract Negotiation ๐Ÿ“','PR & Marketing ๐Ÿ“ฃ'] } }, 'ghostwriter': { label: '๐Ÿ‘ป Ghostwriter', icon: null, categories: { 'Skills': ['Lyrics โœ๏ธ','Hooks ๐ŸŽฃ','Topline ๐ŸŽค','Concept Development ๐Ÿ’ก'] } }, 'developer': { label: '๐Ÿ‘พ Developer', icon: 'assets/icons/persona-developer.jpg', categories: { 'Stacks': ['Frontend ๐ŸŽจ','Backend โš™๏ธ','Mobile ๐Ÿ“ฑ','Audio Programming ๐Ÿ”Š'], 'Languages': ['JavaScript ๐ŸŸจ','Python ๐Ÿ','Swift ๐ŸŽ','Kotlin ๐Ÿค–','Rust ๐Ÿฆ€'] } } }; let _modalState = { selectedSkills: new Set(), personaKey: null }; function openAddPersonaModal() { const html = `
Pick a persona, then pick the skills you actually do. Skills are dated โ€” that's how the app shows experience without you typing years.
`; _modalState = { selectedSkills: new Set(), personaKey: null }; showModal('Add Persona', html); } function renderPersonaSkillPicker() { const sel = val('addPersonaSel'); _modalState.personaKey = sel; _modalState.selectedSkills = new Set(); const wrap = document.getElementById('addSkillPicker'); if (!sel) { wrap.innerHTML = ''; return; } const p = PERSONA_DB[sel]; wrap.innerHTML = Object.entries(p.categories).map(([cat, skills]) => `
${cat}
${skills.map(s => { const any = s.toLowerCase().includes('any'); return ``; }).join('')}
`).join(''); } function toggleSkill(btn, name) { btn.classList.toggle('sel'); if (btn.classList.contains('sel')) _modalState.selectedSkills.add(name); else _modalState.selectedSkills.delete(name); } function saveNewPersona() { if (!_modalState.personaKey) return toast('Pick a persona first'); if (_modalState.selectedSkills.size === 0) return toast('Pick at least one skill'); const date = val('addSkillDate'); const def = PERSONA_DB[_modalState.personaKey]; let persona = state.personas.find(p => p.key === _modalState.personaKey); if (!persona) { persona = { key: _modalState.personaKey, name: def.label, skills: [] }; state.personas.push(persona); } for (const skill of _modalState.selectedSkills) { persona.skills.push({ name: skill, startDate: date }); } saveState(); closeModal(); renderPage('home'); toast(`โœ“ Added ${_modalState.selectedSkills.size} skill(s) to ${def.label}`); } function removePersona(i) { if (!confirm('Remove this persona and all its skills?')) return; state.personas.splice(i, 1); saveState(); renderPage('home'); } /* ---- Royaltiez (wallet) ---- */ function renderRoyaltiezPage() { return `
๐Ÿ’ฐ Wallet
Balance
$${state.wallet.balance.toFixed(2)}
Earned (lifetime)
$${state.wallet.earned.toFixed(2)}
๐Ÿ“‹ Royalty logz (timestamped)
${state.paymentHistory.length === 0 ? `
No payments yet.
` : state.paymentHistory.slice().reverse().map(h => `
${esc(h.type === 'stripe' ? '๐Ÿ’ณ Stripe' : '๐ŸŽต Manual')} $${(h.amount || 0).toFixed(2)}
`).join('')}
`; } /* ---- GroupZ ---- */ function renderGroupzPage() { const labels = Object.keys(state.groups); return `
๐Ÿ‘ฅ Your groupings
${labels.map(lbl => `
${lbl}
${state.groups[lbl].map(u => `${esc(u)}`).join('') || ''}
`).join('')}
`; } /* ---- LabelZ ---- */ function renderLabelzPage() { return `
๐Ÿท๏ธ Your labels
LabelZ require Premium + A&R Scout or Manager persona to create or edit. E-signed contracts with advances and terms.
${state.labels.length === 0 ? `
No labels yet.
` : state.labels.map(l => `
${esc(l.name)}
`).join('')}
`; } /* ---- SettingZ ---- */ function renderSettingzPage() { const currentTheme = state.settings.themePalette || 'purple'; const swatches = Object.entries(THEME_PALETTES).map(([key, p]) => { const active = key === currentTheme ? 'border:3px solid white;box-shadow:0 0 14px rgba(255,255,255,0.6)' : 'border:3px solid transparent'; const locked = !state.isPremium ? 'opacity:0.5;cursor:not-allowed;' : 'cursor:pointer;'; const onclick = state.isPremium ? `onclick="pickTheme('${key}')"` : `onclick="promptPremium()"`; return `
${p.emoji} ${p.name} ${!state.isPremium ? '
๐Ÿ”’
' : ''}
`; }).join(''); const premiumBanner = state.isPremium ? `
โญ Premium โ€” theme editing unlocked
` : `
๐Ÿ”’ Theme color editing is a Premium feature. Free users stay on Purple to match the brand.
`; return `
๐ŸŽจ Theme
The app uses a fixed dark base. The accent color (buttons, active tab, glows) is what changes.
${premiumBanner}
${swatches}
Dev toggle (remove for production):
โš™๏ธ Account
๐Ÿ—‘๏ธ Danger zone
`; } function pickTheme(key) { if (!state.isPremium) return promptPremium(); applyThemePalette(key); renderPage('settingz'); toast(`Theme: ${THEME_PALETTES[key].name}`); } function promptPremium() { showModal('๐Ÿ”’ Premium feature', `
Theme color editing is locked to Premium. Upgrade to repaint your whole app โ€” 8 colors, instant swap, every tab and modal follows.
`); } function togglePremiumDev() { state.isPremium = !state.isPremium; saveState(); renderPage('settingz'); toast(state.isPremium ? 'โญ Premium ON (dev)' : '๐Ÿ”’ Premium OFF'); } /* ---- BugZ ---- */ function renderBugzPage() { document.getElementById('fab').classList.add('show'); return `
๐Ÿž Open bugs
${state.bugs.length === 0 ? `
No bugs reported yet.
` : state.bugs.slice().reverse().map((b, i) => `
๐Ÿž
${esc(b.title)}
${esc(b.body)}
`).join('')}
`; } function onFabClick() { const r = currentRoute().split('/')[0]; if (r === 'bugz') openBugSubmitModal(); } function openBugSubmitModal() { showModal('Submit a Bug', `
`); } function submitBug() { const title = val('bug-title'), body = val('bug-body'); if (!title || !body) return toast('Title and details required'); state.bugs.push({ title, body, status: 'new', date: new Date().toLocaleString() }); saveState(); closeModal(); renderPage('bugz'); toast('โœ“ Bug submitted'); } /* ---- MessageZ ---- */ function renderMessagezPage() { return `
๐Ÿ“ฅ Inbox (${state.messages.inbox.length})
${state.messages.inbox.length === 0 ? `
Empty.
` : state.messages.inbox.map(m => `
${esc(m.from)}
${esc(m.body)}
`).join('')}
๐Ÿ“ค Outbox (${state.messages.outbox.length})
${state.messages.outbox.length === 0 ? `
Empty.
` : state.messages.outbox.map(m => `
To: ${esc(m.to)}
${esc(m.body)}
`).join('')}
`; } /* ======================================================================== COREY VOICE INFO MODAL ======================================================================== */ function openCoreyModal(tabKey) { const tab = TABS.find(t => t.key === tabKey); if (!tab) return; const subapps = tab.subapps.length ? `
Apps in this tab
${tab.subapps.map(s => `
${s.emoji} ${s.name} โ€” ${s.desc}
`).join('')}
` : ''; showModal(`${tab.emoji} ${tab.label}`, `
${tab.corey}
URL: musicconnectz.net/${tab.key}
${subapps}`); } /* ======================================================================== SUB-APP MODALS (one per tab/subkey combo). Lilith, SingZ, RapZ, BodieZ each get nested tabs inside the modal. ======================================================================== */ function openSubapp(tabKey, subKey) { const tab = TABS.find(t => t.key === tabKey); const sub = tab?.subapps.find(s => s.key === subKey); if (!sub) { closeModal(true); return; } // Specialized contents for the practice apps if (tabKey === 'toolz' && subKey === 'lilith') return showModal('๐Ÿ’ƒ๐Ÿฝ Lilith โ€” Tasks', renderLilithBody()); if (tabKey === 'toolz' && subKey === 'singz') return showModal('๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค SingZ โ€” Singing Practice', renderSingzBody()); if (tabKey === 'toolz' && subKey === 'rapz') return showModal('๐Ÿ‘จ๐Ÿผโ€๐ŸŽค RapZ โ€” Rapping Practice', renderRapzBody()); if (tabKey === 'toolz' && subKey === 'bodiez') return showModal('๐Ÿ’ช๐Ÿฝ BodieZ โ€” Body Training', renderBodiezBody()); if (tabKey === 'toolz' && subKey === 'dawz') return showModal('๐ŸŽ›๏ธ DawZ โ€” Digital Audio Workstation', renderDawzBody()); // Generic sub-app modal โ€” explains what the app does and opens the work area showModal(`${sub.emoji} ${sub.name}`, `
${sub.desc}
URL: musicconnectz.net/${tab.key}?modal=${sub.key}
Workspace UI for ${sub.name} mounts here. Hook it into your existing component when ready.
`); } /* ---- Lilith (Apple Things knockoff) ---- */ const LILITH_TABS = [ ['inbox', '๐Ÿ“ฅ Inbox', "Where new tasks land before you organize them into projects, areas, or scheduled lists."], ['today', 'โ€ผ๏ธ Today', "Tasks you plan to work on now."], ['upcoming','โ— Upcoming', "Scheduled tasks and calendar-linked planning views."], ['anytime', '๐Ÿ‘๐Ÿผ Anytime', "Tasks you can do whenever without committing them to a specific date."], ['someday', 'โ“ Someday', "Ideas you may want to do later but don't want active in your current workflow."], ['logbook', '๐Ÿงพ Logbook', "Completed items move here."], ['trash', '๐Ÿšฎ Trash', "Deleted items before permanent deletion."] ]; function renderLilithBody() { return nestedTabs('lilith', LILITH_TABS); } /* ---- SingZ ---- */ const SINGZ_TABS = [ ['inbox','๐Ÿ“ฅ Inbox','New vocal ideas, song choices, lyrics, melody concepts, practice notes, teacher feedback land here.'], ['today','๐ŸŽ™๏ธ Today','Today\'s singing practice: warmups, breathwork, drills, assigned songs, range work, cooldowns, voice check-in.'], ['upcoming','๐Ÿ“… Upcoming','Scheduled practices, lessons, recording sessions, performance prep, audition deadlines, vocal rest days.'], ['anytime','๐ŸŽถ Anytime','Quick vocal drills: lip trills, breath drills, pitch matching, scale runs, ear training.'], ['someday','๐Ÿง  Someday','Songs, techniques, styles, riffs, runs, performance ideas you may want to explore later.'], ['routinez','๐Ÿงฉ RoutineZ','Saved practice plans: Daily Warmup, R&B Runs, Rap-Sing Control, Breath Control, Pitch Accuracy, Range Expansion.'], ['exercisez','๐Ÿ“š ExerciseZ','Searchable library by skill type, difficulty, range, style, purpose, duration.'], ['session','โฑ๏ธ Session','Live practice mode: timers, takes, pitch accuracy, fatigue ratings, audio clips for review.'], ['songz','๐ŸŽผ SongZ','Songs you are learning, writing, recording, covering, rehearsing, or performing.'], ['recordingz','๐ŸŽง RecordingZ','Practice clips, vocal takes, before-and-after comparisons, song drafts.'], ['lessonz','๐ŸŽ“ LessonZ','Guided learning paths, technique breakdowns, mini-courses, teacher assignments.'], ['progress','๐Ÿ“ˆ Progress','Streaks, total practice time, range expansion, pitch accuracy, breath duration.'], ['voicemap','๐Ÿ—ฃ๏ธ VoiceMap','Visual development across pitch, range, breath, tone, agility, vibrato, resonance.'], ['coach','๐Ÿค– Coach','Smart recommendations on what to practice next based on weak spots, goals, fatigue.'], ['goalz','๐ŸŽฏ GoalZ','Vocal targets: expand range, improve pitch, master vibrato, prepare for audition.'], ['recovery','๐Ÿ›Œ Recovery','Vocal fatigue, hydration, sleep, rest, strain warnings, cooldown needs.'], ['community','๐ŸŒ Community','Share progress, post covers, join challenges, get feedback.'], ['logbook','๐Ÿงพ Logbook','Completed practices, finished lessons, mastered songs, old recordings.'], ['trash','๐Ÿšฎ Trash','Deleted items before permanent deletion.'] ]; function renderSingzBody() { return nestedTabs('singz', SINGZ_TABS); } /* ---- RapZ ---- */ const RAPZ_TABS = [ ['inbox','๐Ÿ“ฅ Inbox','New rap ideas, punchlines, bars, rhyme schemes, song concepts, beat links, freestyle notes land here.'], ['today','๐ŸŽค Today','Today\'s rap practice: breath drills, flow exercises, freestyle prompts, cadence practice, delivery work.'], ['upcoming','๐Ÿ“… Upcoming','Scheduled practices, studio sessions, writing blocks, release deadlines, cypher prep.'], ['anytime','๐Ÿ”ฅ Anytime','Quick drills: freestyle warmups, 16-bar writing, rhyme stacking, breath control, pocket practice.'], ['someday','๐Ÿง  Someday','Future rap ideas, song concepts, styles, flows, alter egos, mixtape ideas.'], ['routinez','๐Ÿงฉ RoutineZ','Daily Freestyle, 16-Bar Builder, Punchline Training, Breath Control, Chopper Flow, Studio Prep.'], ['exercisez','๐Ÿ“š ExerciseZ','Browse drills by skill, difficulty, style, BPM, purpose, duration.'], ['session','โฑ๏ธ Session','Live practice: writing drills, freestyle over beats, timers, BPM comfort, delivery ratings.'], ['lyricz','๐Ÿ“ LyricZ','Bars, verses, hooks, bridges, punchlines, rhyme stacks, concepts, storytelling drafts.'], ['songz','๐ŸŽผ SongZ','Tracks you are writing, recording, rehearsing, mixing, performing, releasing.'], ['beatz','๐Ÿฅ BeatZ','Beats, instrumentals, loops, producer links, BPM, key, mood, genre, licensing notes.'], ['stylez','๐ŸŽญ StyleZ','Switch flows, study genre-specific delivery, match writing to the right beat.'], ['alternative','๐ŸŽธ Alternative Rap','Experimental lyrics, genre-bending sounds, unusual flows, indie production.'], ['boombap','๐Ÿฅ Boom Bap','Classic hip-hop writing, hard drums, sharp rhyme schemes, head-nod flow.'], ['chopper','๐Ÿš Chopper','Fast rap, rapid syllable control, breath timing, double-time flow.'], ['cloud','โ˜๏ธ Cloud Rap','Atmospheric flows, dreamy delivery, melodic pockets, vibe writing.'], ['conscious','๐Ÿง  Conscious Rap','Message-driven lyrics, social commentary, storytelling, deeper concepts.'], ['crunk','๐Ÿ”ฅ Crunk','High-energy chants, crowd control, aggressive hooks, hype practice.'], ['drill','โš”๏ธ Drill','Dark beats, sliding flows, aggressive delivery, street cadence.'], ['emo','๐Ÿ–ค Emo Rap','Emotional lyrics, melodic rap, vulnerability, raw delivery.'], ['gfunk','๐ŸŒด G-Funk','West Coast flows, smooth pockets, funky basslines, relaxed cadence.'], ['gangsta','โ›“๏ธ Gangsta Rap','Gritty storytelling, street narratives, confident delivery, cinematic verses.'], ['hardcore','๐ŸŽค Hardcore Hip Hop','Aggressive bars, battle energy, complex rhyme schemes.'], ['jazz','๐ŸŽท Jazz Rap','Smooth flows, poetic lyrics, live-instrument beats, abstract writing.'], ['mumble','๐Ÿ’ค Mumble Rap','Vibe-first delivery, melodic repetition, hypnotic flows, ad-libs.'], ['oldschool','๐Ÿ“ป Old School','Foundational flow patterns, simple rhyme schemes, party cadence.'], ['snap','๐Ÿซฐ Snap','Minimal beats, catchy hooks, danceable rhythms, chant delivery.'], ['trap','๐Ÿš๏ธ Trap','Triplet flows, hi-hat pockets, ad-libs, 808 cadence, modern rap.'], ['recordingz','๐ŸŽง RecordingZ','Freestyle clips, practice takes, verse drafts, ad-lib tests, before/after.'], ['freestylez','๐ŸŒ€ FreestyleZ','Off-the-top rapping, prompts, cyphers, beat shuffles, no-pen drills.'], ['flowz','๐ŸŒŠ FlowZ','Cadence, pockets, rhythm changes, triplets, double-time, beat riding.'], ['rhymez','๐Ÿ”ค RhymeZ','Rhyme banks, multisyllabic, internal, slant, compound, wordplay patterns.'], ['delivery','๐Ÿš€ Delivery','Vocal presence, tone, aggression, emotion, clarity, projection, character.'], ['goalz','๐ŸŽฏ GoalZ','Targets: write 100 bars, freestyle 5min, master Chopper, finish a mixtape.'], ['coach','๐Ÿค– Coach','Suggestions based on weak spots, BPM comfort, rhyme complexity, deadlines.'], ['progresz','๐Ÿ“ˆ ProgresZ','Writing streaks, completed verses, freestyle duration, BPM range, recording quality.'], ['rapmap','๐Ÿ—บ๏ธ RapMap','Visual development across flow, lyrics, breath, delivery, punchlines, hooks.'], ['community','๐ŸŒ Community','Share freestyles, cyphers, challenges, producers, artist network.'], ['logbook','๐Ÿงพ Logbook','Completed practices, finished verses, mastered styles, old freestyles.'], ['trash','๐Ÿšฎ Trash','Deleted items before permanent deletion.'] ]; function renderRapzBody() { return nestedTabs('rapz', RAPZ_TABS); } /* ---- BodieZ ---- */ const BODIEZ_TABS = [ ['inbox','๐Ÿ“ฅ Inbox','Workout ideas, exercises, goals, notes, routine concepts land here.'], ['today','๐Ÿ’ช Today','Today\'s workout: routine, warmup, exercises, sets, reps, rest, cardio, recovery, check-in.'], ['upcoming','๐Ÿ“… Upcoming','Scheduled workouts, rest days, progress check-ins, weigh-ins, deloads.'], ['anytime','๐Ÿ‹๏ธ Anytime','Pump sessions, mobility flows, ab circuits, cardio finishers, hotel workouts.'], ['someday','๐Ÿง  Someday','Future programs, exercises, challenges, diet phases, transformation goals.'], ['routines','๐Ÿงฉ Routines','PPL, Upper/Lower, Full Body, Bro Split, Strength, Hypertrophy, Fat Loss, Home.'], ['exercises','๐Ÿ“š Exercises','Browse by muscle, equipment, difficulty, movement pattern, goal, location.'], ['session','โฑ๏ธ Session','Live mode: log sets, reps, weight, duration, distance, RPE, RIR, rest, notes.'], ['progress','๐Ÿ“ˆ Progress','PRs, total volume, consistency, bodyweight, 1RM estimates, muscle group frequency.'], ['bodymap','๐Ÿง BodyMap','Heat map of trained, undertrained, overtrained muscles.'], ['coach','๐Ÿค– Coach','Increase weight, hold, deload, swap exercises, add/reduce volume.'], ['goals','๐ŸŽฏ Goals','Body, strength, habit, and performance targets.'], ['recovery','๐Ÿ›Œ Recovery','Soreness, sleep, fatigue, rest days, deloads, hydration, mobility, injuries.'], ['nutrition','๐Ÿฅ— Nutrition','Calories, protein, water, meal notes, supplements, body comp goals.'], ['community','๐ŸŒ Community','Follow friends, join challenges, share routines, progress photos.'], ['logbook','๐Ÿงพ Logbook','Completed workouts, body check-ins, progress photos, PRs, notes.'], ['trash','๐Ÿšฎ Trash','Deleted items before permanent deletion.'] ]; function renderBodiezBody() { return nestedTabs('bodiez', BODIEZ_TABS); } function renderDawzBody() { return `
DawZ is the in-app studio โ€” tracks, EQ, compression, automation, mixer, master bus, vector scope. The full DAW UI mounts here. Use this modal as the placeholder shell.
DAW UI mounts here.
`; } /* Helper that draws a nested-tab block for app-in-app views */ function nestedTabs(appKey, tabs) { return `
${tabs.map((t, i) => ``).join('')}
${esc(tabs[0][2])}