1<!DOCTYPE html>
2<html lang="en" data-theme="dark">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Ledgerlight — Personal finance</title>
7 <link rel="stylesheet" href="style.css">
8 <script>
9
10 (function () {
11 try {
12 var t = localStorage.getItem('vibefin.theme');
13 if (t === 'light' || t === 'dark') {
14 document.documentElement.setAttribute('data-theme', t);
15 }
16 } catch (e) { }
17 })();
18 </script>
19</head>
20<body>
21 <header class="topbar">
22 <div class="brand">
23 <h1>Ledgerlight</h1>
24 <span class="tagline">Personal finance, kept locally</span>
25 </div>
26 <div class="topbar-actions">
27 <button type="button" id="import-btn" class="btn">Import CSV</button>
28 <button type="button" id="export-btn" class="btn">Export CSV</button>
29 <button type="button" id="theme-btn" class="btn" aria-label="Switch to light theme">Light mode</button>
30 <button type="button" id="help-btn" class="btn" aria-label="Keyboard shortcuts and help">?</button>
31 <input type="file" id="import-file" accept=".csv,text/csv" hidden>
32 </div>
33 </header>
34
35 <main>
36 <section class="tiles" aria-label="This month at a glance">
37 <div class="card tile tile-hero">
38 <span class="tile-label" id="hero-label">Spent this month</span>
39 <span class="tile-value hero-value" id="hero-value">$0.00</span>
40 <span class="tile-delta" id="hero-delta"></span>
41 </div>
42 <div class="card tile">
43 <span class="tile-label">Income this month</span>
44 <span class="tile-value" id="income-value">$0.00</span>
45 </div>
46 <div class="card tile">
47 <span class="tile-label">Net this month</span>
48 <span class="tile-value" id="net-value">$0.00</span>
49 </div>
50 </section>
51
52 <section class="filters" aria-label="Filters">
53 <label class="field">
54 <span class="field-label">Category</span>
55 <select id="filter-category"></select>
56 </label>
57 <label class="field">
58 <span class="field-label">Range</span>
59 <select id="filter-range">
60 <option value="month">This month</option>
61 <option value="3m">Last 3 months</option>
62 <option value="6m" selected>Last 6 months</option>
63 <option value="12m">Last 12 months</option>
64 <option value="all">All time</option>
65 </select>
66 </label>
67 <label class="field field-grow">
68 <span class="field-label">Search</span>
69 <input type="search" id="filter-search" placeholder="Search descriptions… ( / )">
70 </label>
71 <button type="button" id="clear-filters" class="btn btn-ghost">Clear</button>
72 </section>
73
74 <section class="charts-row">
75 <div class="card chart-card">
76 <div class="card-head">
77 <h2 id="chart-title">Spending over time</h2>
78 <span class="card-sub" id="chart-subtitle"></span>
79 </div>
80 <div class="chart-wrap">
81 <canvas id="spend-chart" role="img" aria-label="Monthly spending line chart"></canvas>
82 <div id="chart-tooltip" class="tooltip" hidden></div>
83 </div>
84 </div>
85 <div class="card budget-card">
86 <div class="card-head">
87 <h2>Monthly budgets</h2>
88 <span class="card-sub" id="budget-month"></span>
89 </div>
90 <div id="budget-list"></div>
91 </div>
92 </section>
93
94 <section class="card table-card">
95 <div class="card-head">
96 <h2 id="form-title">Transactions</h2>
97 <span class="card-sub" id="txn-count"></span>
98 </div>
99
100 <form id="txn-form" autocomplete="off">
101 <label class="field">
102 <span class="field-label">Date</span>
103 <input type="date" id="f-date" required>
104 </label>
105 <label class="field field-grow">
106 <span class="field-label">Description</span>
107 <input type="text" id="f-desc" placeholder="e.g. Groceries at the market" required>
108 </label>
109 <label class="field">
110 <span class="field-label">Category</span>
111 <select id="f-category"></select>
112 </label>
113 <label class="field">
114 <span class="field-label">Amount ($)</span>
115 <input type="number" id="f-amount" min="0.01" step="0.01" placeholder="0.00" required>
116 </label>
117 <div class="form-buttons">
118 <button type="submit" id="f-submit" class="btn btn-primary">Add</button>
119 <button type="button" id="f-cancel" class="btn btn-ghost" hidden>Cancel</button>
120 </div>
121 </form>
122 <p id="form-error" class="form-error" role="alert"></p>
123 <p id="import-status" class="import-status" role="status"></p>
124
125 <div class="table-scroll">
126 <table>
127 <thead>
128 <tr>
129 <th scope="col">Date</th>
130 <th scope="col">Description</th>
131 <th scope="col">Category</th>
132 <th scope="col" class="col-amount">Amount</th>
133 <th scope="col" class="col-actions"><span class="visually-hidden">Actions</span></th>
134 </tr>
135 </thead>
136 <tbody id="txn-tbody"></tbody>
137 </table>
138 </div>
139 </section>
140 </main>
141
142 <footer>
143 <span>Data stays in this browser (localStorage). Press <kbd>?</kbd> for shortcuts.</span>
144 <a href="tests.html">Self-tests</a>
145 </footer>
146
147 <dialog id="help-dialog" aria-labelledby="help-title">
148 <h2 id="help-title">Shortcuts & help</h2>
149 <table class="shortcut-table">
150 <tbody>
151 <tr><td><kbd>n</kbd></td><td>New transaction (focus the form)</td></tr>
152 <tr><td><kbd>/</kbd></td><td>Search transactions</td></tr>
153 <tr><td><kbd>e</kbd></td><td>Export CSV</td></tr>
154 <tr><td><kbd>i</kbd></td><td>Import CSV</td></tr>
155 <tr><td><kbd>t</kbd></td><td>Toggle light / dark theme</td></tr>
156 <tr><td><kbd>?</kbd></td><td>Open or close this help</td></tr>
157 <tr><td><kbd>Esc</kbd></td><td>Close help / cancel editing</td></tr>
158 </tbody>
159 </table>
160 <h3>CSV format</h3>
161 <p>
162 <code>date,description,category,amount</code> with a header row.
163 Amounts are signed: negative for spending, positive for income.
164 Unknown categories import as <em>Other</em>; <code>$1,234.56</code> and
165 <code>(45.00)</code> amount styles are understood.
166 </p>
167 <form method="dialog"><button class="btn btn-primary">Close</button></form>
168 </dialog>
169
170 <script src="data.js"></script>
171 <script src="charts.js"></script>
172 <script src="app.js"></script>
173</body>
174</html>
175
Discussion
1 comment on this trajectory. Recorded by @patrick-toulme.