// theme.jsx — INDIECASH design tokens, accent palettes, formatters // ─── Accent palettes ───────────────────────────────────────────── // Each lane: 50/100/200/500/600/700/900 + gradient pair. const PALETTES = { emerald: { 50: '#ecfdf5', 100: '#d1fae5', 200: '#a7f3d0', 500: '#10b981', 600: '#059669', 700: '#047857', 900: '#064e3b', grad: ['#10b981', '#0d9488'], tint: 'rgba(16,185,129,0.08)', }, teal: { 50: '#f0fdfa', 100: '#ccfbf1', 200: '#99f6e4', 500: '#14b8a6', 600: '#0d9488', 700: '#0f766e', 900: '#134e4a', grad: ['#14b8a6', '#0891b2'], tint: 'rgba(20,184,166,0.08)', }, mint: { 50: '#f0fdf4', 100: '#dcfce7', 200: '#bbf7d0', 500: '#22c55e', 600: '#16a34a', 700: '#15803d', 900: '#14532d', grad: ['#34d399', '#10b981'], tint: 'rgba(34,197,94,0.08)', }, forest: { 50: '#f7fee7', 100: '#ecfccb', 200: '#d9f99d', 500: '#0f766e', 600: '#115e59', 700: '#134e4a', 900: '#042f2e', grad: ['#065f46', '#064e3b'], tint: 'rgba(15,118,110,0.08)', }, }; // ─── Theme tokens (light/dark) ─────────────────────────────────── const THEMES = { light: { bg: '#f7f8f7', // soft off-white app bg surface: '#ffffff', // cards surfaceAlt: '#f3f5f4', // chip / muted surface border: 'rgba(15,23,42,0.07)', borderStrong: 'rgba(15,23,42,0.12)', fg1: '#0f172a', // primary text fg2: '#475569', // secondary text fg3: '#94a3b8', // tertiary / meta fg4: '#cbd5e1', // disabled / dividers in copy danger: '#ef4444', dangerSoft: '#fee2e2', warning: '#f59e0b', warningSoft: '#fef3c7', success: '#10b981', successSoft: '#d1fae5', overlay: 'rgba(15,23,42,0.45)', shadow1: '0 1px 2px rgba(15,23,42,0.04), 0 1px 1px rgba(15,23,42,0.03)', shadow2: '0 4px 16px rgba(15,23,42,0.06), 0 1px 2px rgba(15,23,42,0.04)', shadow3: '0 12px 32px rgba(15,23,42,0.10), 0 2px 6px rgba(15,23,42,0.05)', statusBarDark: false, // dark icons on light bg }, dark: { bg: '#0a0f14', surface: '#121821', surfaceAlt: '#1a2230', border: 'rgba(255,255,255,0.07)', borderStrong: 'rgba(255,255,255,0.12)', fg1: '#f1f5f9', fg2: '#94a3b8', fg3: '#64748b', fg4: '#475569', danger: '#f87171', dangerSoft: 'rgba(248,113,113,0.12)', warning: '#fbbf24', warningSoft: 'rgba(251,191,36,0.12)', success: '#34d399', successSoft: 'rgba(52,211,153,0.12)', overlay: 'rgba(0,0,0,0.6)', shadow1: '0 1px 2px rgba(0,0,0,0.3)', shadow2: '0 4px 16px rgba(0,0,0,0.4)', shadow3: '0 12px 32px rgba(0,0,0,0.5)', statusBarDark: true, // light icons on dark bg }, }; // ─── Formatters ────────────────────────────────────────────────── // Indonesian: thousands separator is `.` and decimal is `,`. function fmtRp(n, opts = {}) { const { sign = false, short = false, hide = false } = opts; if (hide) return 'Rp ••••••'; const abs = Math.abs(n); let body; if (short && abs >= 1_000_000) { body = (abs / 1_000_000).toFixed(abs >= 10_000_000 ? 0 : 1).replace('.', ',') + ' jt'; } else if (short && abs >= 1_000) { body = (abs / 1_000).toFixed(0) + 'rb'; } else { body = Math.round(abs).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.'); } const s = sign ? (n < 0 ? '−' : '+') : (n < 0 ? '−' : ''); return `${s}Rp ${body}`; } // Compact, no Rp prefix — for chart axes etc. function fmtNum(n) { if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace('.0', '') + 'jt'; if (n >= 1_000) return Math.round(n / 1_000) + 'k'; return String(n); } function fmtDate(d) { // d is ISO string or Date const dt = typeof d === 'string' ? new Date(d) : d; const months = ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Agu','Sep','Okt','Nov','Des']; return `${dt.getDate()} ${months[dt.getMonth()]}`; } function fmtDateLong(d) { const dt = typeof d === 'string' ? new Date(d) : d; const months = ['Januari','Februari','Maret','April','Mei','Juni', 'Juli','Agustus','September','Oktober','November','Desember']; return `${dt.getDate()} ${months[dt.getMonth()]} ${dt.getFullYear()}`; } function fmtRelDay(d) { const dt = typeof d === 'string' ? new Date(d) : d; const now = new Date(); const diffMs = now.setHours(0,0,0,0) - new Date(dt).setHours(0,0,0,0); const days = Math.round(diffMs / 86400000); if (days === 0) return 'Hari ini'; if (days === 1) return 'Kemarin'; if (days < 7) return `${days} hari lalu`; return fmtDate(d); } function fmtTime(d) { const dt = typeof d === 'string' ? new Date(d) : d; return dt.toTimeString().slice(0, 5); // HH:MM } // ─── Hook: theme + accent ──────────────────────────────────────── function useTheme(dark, accentName) { const t = THEMES[dark ? 'dark' : 'light']; const a = PALETTES[accentName] || PALETTES.emerald; return { ...t, accent: a, dark }; } Object.assign(window, { PALETTES, THEMES, useTheme, fmtRp, fmtNum, fmtDate, fmtDateLong, fmtRelDay, fmtTime, });