/* Ten31 CRM — shared client store (single source of truth across the four mobile surfaces). Mirrors the server model: investors carry stage/notes/funds/contacts/priority/last_activity; reminders are their own collection keyed by investor. One canonical copy so a stage move, logged communication, or reminder edit on any tab is reflected on every other tab. Installed as a window singleton so it survives child-surface remounts within the shell. */ (function () { if (window.T31Store) return; var C = function (name, email) { return { name: name, email: email }; }; function seedInvestors() { return [ { id: 1, name: 'Northwall Capital', priority: true, stage: 'commitment', daysAgo: 2, contacts: [C('Dana Reyes', 'dana@northwall.com'), C('Per Holt', 'per@northwall.com')], funds: [['Ten31 Terahash', 1500000], ['Sats and Stats', 600000], ['Join the Fold', 400000]], views: ['Main Fundraising', 'All Investors'], notes: [['Email', 'Confirmed $2.5M allocation across funds', '2026-06-17'], ['Meeting', 'DD call — covered redemption terms', '2026-06-10']] }, { id: 2, name: 'Brightseed Partners', priority: true, stage: 'engaged', daysAgo: 5, contacts: [C('Omar Said', 'omar@brightseed.vc')], funds: [['Ten31 Terahash', 0]], views: ['Main Fundraising', 'Follow-up List'], notes: [['Note', 'Intro from Polaris — warm', '2026-06-14']] }, { id: 3, name: 'Cedarline Family Office', priority: false, stage: 'commitment', daysAgo: 7, contacts: [C('Lena Cho', 'lena@cedarline.com')], funds: [['Ten31 Terahash', 800000], ['Pawn to F4', 400000]], views: ['Main Fundraising', 'All Investors', 'Fund II investors'], notes: [['Call', 'Wire received, fully funded', '2026-06-12']] }, { id: 4, name: 'Vance & Co', priority: false, stage: 'engaged', daysAgo: 3, contacts: [C('Marcus Vance', 'mv@vanceco.com')], funds: [['Ten31 Terahash', 0]], views: ['Main Fundraising', 'Follow-up List'], notes: [] }, { id: 5, name: 'Polaris Endowment', priority: true, stage: 'diligence', daysAgo: 1, contacts: [C('Ruth Almeida', 'ralmeida@polaris.org')], funds: [['Ten31 Terahash', 3000000], ['Sats and Stats', 2000000]], views: ['Main Fundraising', 'All Investors', 'Follow-up List', 'Fund II investors'], notes: [['Meeting', 'IC presentation went well', '2026-06-18'], ['Email', 'Sent data room access', '2026-06-15']] }, { id: 6, name: 'Hartman Group', priority: false, stage: null, daysAgo: 14, contacts: [], funds: [['Ten31 Terahash', 0]], views: ['Main Fundraising'], notes: [] }, { id: 7, name: 'Meridian Trust', priority: false, stage: 'commitment', daysAgo: 4, contacts: [C('Sofia Marin', 'sofia@meridiantrust.com')], funds: [['Ten31 Terahash', 800000]], views: ['Main Fundraising', 'All Investors'], notes: [['Note', 'Signed side letter', '2026-06-14']] }, { id: 8, name: 'Atlas Ventures Fund', priority: false, stage: 'engaged', daysAgo: 6, contacts: [C('Will Tanaka', 'will@atlasvf.com')], funds: [['Ten31 Terahash', 0]], views: ['Main Fundraising'], notes: [] }, { id: 9, name: 'K. Whitfield', priority: false, stage: null, daysAgo: 21, contacts: [C('Kira Whitfield', 'kira@whitfield.io')], funds: [], views: ['Graveyard'], notes: [['Note', 'No allocation — parked', '2026-05-28']] }, { id: 10, name: 'Granite Bay LP', priority: false, stage: 'commitment', daysAgo: 30, contacts: [C('Tom Becker', 'tom@granitebay.com')], funds: [['Ten31 Terahash', 2000000], ['Sats and Stats', 1300000]], views: ['Main Fundraising', 'All Investors', 'Fund II investors'], notes: [] }, { id: 11, name: 'Forsythe Holdings', priority: false, stage: 'lead', daysAgo: 35, contacts: [], funds: [], views: ['Graveyard'], notes: [] } ]; } function seedReminders() { return [ { id: 1, note: 'Resend deck — bounced', orgId: 4, due: '2026-06-18', done: false }, { id: 2, note: 'Re-engage — cold 2 weeks', orgId: 6, due: '2026-06-16', done: false }, { id: 3, note: 'IC memo due', orgId: 5, due: '2026-06-19', done: false }, { id: 4, note: 'Follow up after intro call', orgId: 2, due: '2026-06-19', done: false }, { id: 5, note: 'Share data room link', orgId: 8, due: '2026-06-20', done: false }, { id: 6, note: 'Countersign side letter', orgId: 7, due: '2026-06-21', done: false }, { id: 7, note: 'Send Q2 update deck', orgId: 1, due: '2026-06-24', done: false }, { id: 8, note: 'Quarterly check-in call', orgId: 3, due: '2026-07-08', done: false }, { id: 9, note: 'Thank-you note post-wire', orgId: 10, due: '2026-06-13', done: true } ]; } var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var store = { investors: seedInvestors(), reminders: seedReminders(), tab: 'grid', theme: 'dark', focusInvestorId: null, today: new Date(2026, 5, 19), _subs: [], subscribe: function (fn) { this._subs.push(fn); var self = this; return function () { self._subs = self._subs.filter(function (f) { return f !== fn; }); }; }, _notify: function () { this._subs.slice().forEach(function (f) { try { f(); } catch (e) {} }); }, setTab: function (t) { this.tab = t; this._notify(); }, openInvestor: function (id) { this.focusInvestorId = id; this.tab = 'grid'; this._notify(); }, setTheme: function (t) { this.theme = t; this._notify(); }, toggleTheme: function () { this.theme = this.theme === 'light' ? 'dark' : 'light'; this._notify(); }, // ----- investor mutations ----- updateInvestor: function (id, patch) { this.investors = this.investors.map(function (i) { return i.id === id ? Object.assign({}, i, patch) : i; }); this._notify(); }, logNote: function (id, entry) { this.investors = this.investors.map(function (i) { return i.id === id ? Object.assign({}, i, { notes: [entry].concat(i.notes), daysAgo: 0 }) : i; }); this._notify(); }, addInvestor: function (inv) { var id = this.investors.reduce(function (m, i) { return Math.max(m, i.id); }, 0) + 1; var ni = Object.assign({ id: id, priority: false, stage: 'lead', daysAgo: 0, contacts: [], funds: [['Ten31 Terahash', 0]], views: ['Main Fundraising'], notes: [] }, inv); this.investors = [ni].concat(this.investors); this._notify(); return id; }, // ----- reminder mutations ----- addReminder: function (orgId, note, due) { var id = this.reminders.reduce(function (m, r) { return Math.max(m, r.id); }, 0) + 1; this.reminders = [{ id: id, note: note, orgId: orgId, due: due, done: false }].concat(this.reminders); this._notify(); return id; }, updateReminder: function (id, patch) { this.reminders = this.reminders.map(function (r) { return r.id === id ? Object.assign({}, r, patch) : r; }); this._notify(); }, deleteReminder: function (id) { this.reminders = this.reminders.filter(function (r) { return r.id !== id; }); this._notify(); }, toggleReminder: function (id) { this.reminders = this.reminders.map(function (r) { return r.id === id ? Object.assign({}, r, { done: !r.done }) : r; }); this._notify(); }, reminderFor: function (orgId) { // the soonest open reminder for an investor (used by the Grid detail) var open = this.reminders.filter(function (r) { return r.orgId === orgId && !r.done; }); open.sort(function (a, b) { return a.due < b.due ? -1 : 1; }); return open[0] || null; }, // ----- derived helpers ----- investorById: function (id) { return this.investors.find(function (i) { return i.id === id; }); }, committed: function (i) { return (i.funds || []).reduce(function (a, f) { return a + f[1]; }, 0); }, money: function (n) { if (!n) return '$0'; if (n >= 1e6) return '$' + (n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1) + 'M'; if (n >= 1e3) return '$' + Math.round(n / 1e3) + 'K'; return '$' + n; }, parseDate: function (iso) { var p = iso.split('-'); return new Date(+p[0], +p[1] - 1, +p[2]); }, diffDays: function (iso) { return Math.round((this.parseDate(iso) - this.today) / 86400000); }, monthDay: function (iso) { var d = this.parseDate(iso); return MONTHS[d.getMonth()] + ' ' + d.getDate(); } }; window.T31Store = store; })();