study-lab/index.html
index.htmlRaw
1<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">
7 <title>Study Pal — Branch your thinking</title>
8 <script>
9 try {
10 document.documentElement.dataset.theme = localStorage.getItem('study-pal:theme') ||
11 (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
12 } catch { document.documentElement.dataset.theme = 'light'; }
13 </script>
14 <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
15 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
16 <link rel="stylesheet" href="styles.css">
17</head>
18<body>
19 <header class="topbar">
20 <a class="brand" href="#" aria-label="Study Pal home">
21 <span class="brand-mark" aria-hidden="true">S</span>
22 <span>Study Pal</span>
23 </a>
24 <div class="top-actions">
25 <button id="open-usage" class="usage-button" type="button" aria-haspopup="dialog" aria-label="Open API usage details">
26 <span id="month-tokens">0</span> <span class="usage-caption">tokens</span>
27 </button>
28 <button id="theme-toggle" class="theme-toggle" type="button" aria-label="Switch to dark theme" title="Switch theme">
29 <span aria-hidden="true">☾</span>
30 </button>
31 <button id="new-session" class="button button-ghost" type="button">New map</button>
32 <button id="open-settings" class="button button-dark" type="button" aria-haspopup="dialog">
33 <span class="status-dot" id="key-status" aria-hidden="true"></span>
34 <span class="settings-label">Settings</span>
35 </button>
36 </div>
37 </header>
38
39 <main class="app-shell">
40 <section class="workspace" aria-label="Conversation">
41 <div id="welcome" class="welcome">
42 <div class="eyebrow">A thinking space that branches</div>
43 <h1>Follow every<br><em>line of thought.</em></h1>
44 <p>Ask a question, explore the answer, then branch from any earlier idea without losing your way.</p>
45 <div class="starter-grid" aria-label="Example questions">
46 <button class="starter" data-prompt="Explain gradient descent visually and mathematically.">
47 <span class="starter-icon">↘</span>
48 <strong>Learn a concept</strong>
49 <small>Explain gradient descent visually</small>
50 </button>
51 <button class="starter" data-prompt="Compare TCP and UDP with practical examples.">
52 <span class="starter-icon">⇄</span>
53 <strong>Compare ideas</strong>
54 <small>When should I use TCP or UDP?</small>
55 </button>
56 <button class="starter" data-prompt="Derive the quadratic formula step by step using LaTeX.">
57 <span class="starter-icon">∑</span>
58 <strong>Work through math</strong>
59 <small>Derive the quadratic formula</small>
60 </button>
61 </div>
62 </div>
63
64 <div id="conversation" class="conversation" aria-live="polite"></div>
65
66 <div class="composer-wrap">
67 <div id="branch-context" class="branch-context" hidden>
68 <span>Branching from <strong id="branch-label"></strong></span>
69 <button id="clear-branch" type="button" aria-label="Return to latest node">×</button>
70 </div>
71 <form id="composer" class="composer">
72 <textarea id="prompt" rows="1" placeholder="Ask anything…" aria-label="Message" maxlength="30000"></textarea>
73 <div class="composer-footer">
74 <span class="hint"><kbd>Enter</kbd> send · <kbd>Shift</kbd> + <kbd>Enter</kbd> new line</span>
75 <button id="send" class="send" type="submit" aria-label="Send message">↑</button>
76 </div>
77 </form>
78 <p class="privacy-note">Your map stays in this browser.</p>
79 </div>
80 </section>
81
82 <aside class="map-panel" aria-label="Concept map">
83 <div class="map-heading">
84 <div>
85 <span class="eyebrow">Your exploration</span>
86 <h2>Concept map</h2>
87 </div>
88 <button id="collapse-map" class="icon-button" type="button" aria-label="Collapse concept map">→</button>
89 </div>
90 <p id="map-help" class="map-help">Select a node to revisit it and start a new branch.</p>
91 <nav id="tree" class="tree" aria-label="Question branches"></nav>
92 <div id="map-empty" class="map-empty">
93 <span class="empty-orbit">✦</span>
94 <p>Your questions will grow into a map here.</p>
95 </div>
96 <div class="map-stats">
97 <span><strong id="node-count">0</strong> nodes</span>
98 <span><strong id="branch-count">0</strong> branches</span>
99 </div>
100 </aside>
101 <button id="expand-map" class="expand-map" type="button" hidden>← Concept map</button>
102 </main>
103
104 <div id="selection-menu" class="selection-menu" role="dialog" aria-label="Ask about selected text" hidden>
105 <div class="selection-topline">
106 <span>Ask about selection</span>
107 <button id="close-selection" type="button" aria-label="Close">×</button>
108 </div>
109 <blockquote id="selection-preview"></blockquote>
110 <select id="selection-action" aria-label="Question type">
111 <option value="Explain this passage in simpler terms.">Explain simply</option>
112 <option value="Go deeper into this idea and its implications.">Go deeper</option>
113 <option value="Give me a concrete example of this.">Give an example</option>
114 <option value="What assumptions does this passage make?">Challenge assumptions</option>
115 <option value="custom">Write my own…</option>
116 </select>
117 <textarea id="selection-custom" rows="2" placeholder="What would you like to ask?" hidden></textarea>
118 <button id="ask-selection" class="button button-dark" type="button">Ask</button>
119 </div>
120
121 <dialog id="settings-dialog" class="settings-dialog">
122 <form id="settings-form" method="dialog">
123 <div class="dialog-heading">
124 <div>
125 <span class="eyebrow">Connection</span>
126 <h2>API settings</h2>
127 </div>
128 <button id="close-settings" class="icon-button" type="button" aria-label="Close settings">×</button>
129 </div>
130 <p class="settings-copy">Use an OpenAI API key (not a ChatGPT password). It is stored only in this browser and sent directly to the configured API.</p>
131 <label>
132 API key
133 <div class="secret-field">
134 <input id="api-key" type="password" placeholder="sk-…" autocomplete="off" spellcheck="false">
135 <button id="toggle-key" type="button" aria-label="Show API key">Show</button>
136 </div>
137 </label>
138 <label>
139 Model
140 <input id="model" type="text" value="gpt-5-mini" placeholder="gpt-5-mini" spellcheck="false">
141 </label>
142 <details>
143 <summary>Advanced</summary>
144 <label>
145 Responses API endpoint
146 <input id="api-url" type="url" value="https://api.openai.com/v1/responses" spellcheck="false">
147 </label>
148 <label>
149 System instructions
150 <textarea id="system-prompt" rows="3">You are a patient technical tutor. Use clear Markdown and LaTeX where useful. Build on the conversation context and explain assumptions.</textarea>
151 </label>
152 </details>
153 <div class="security-callout"><strong>Local-use note</strong> — any script running on this origin can access local storage. Use a restricted project key and never deploy this frontend publicly with a saved key.</div>
154 <div class="dialog-actions">
155 <button id="forget-key" class="button button-ghost danger" type="button">Forget key</button>
156 <button class="button button-dark" value="save" type="submit">Save settings</button>
157 </div>
158 </form>
159 </dialog>
160
161 <dialog id="usage-dialog" class="settings-dialog usage-dialog">
162 <form method="dialog">
163 <div class="dialog-heading">
164 <div>
165 <span class="eyebrow">Local estimate</span>
166 <h2>API usage</h2>
167 </div>
168 <button id="close-usage" class="icon-button" type="button" aria-label="Close API usage">×</button>
169 </div>
170 <p class="settings-copy"><span id="usage-month"></span> · Requests made from this browser</p>
171
172 <div class="usage-summary">
173 <div class="usage-stat primary">
174 <span>Tokens</span>
175 <strong id="usage-total">0</strong>
176 </div>
177 <div class="usage-stat">
178 <span>Estimated cost</span>
179 <strong id="usage-cost">$0.00</strong>
180 </div>
181 <div class="usage-stat">
182 <span>Requests</span>
183 <strong id="usage-requests">0</strong>
184 </div>
185 </div>
186
187 <section class="usage-section" aria-labelledby="token-breakdown-title">
188 <h3 id="token-breakdown-title">Token breakdown</h3>
189 <div class="token-breakdown">
190 <span>Input <strong id="usage-input">0</strong></span>
191 <span>Cached input <strong id="usage-cached">0</strong></span>
192 <span>Output <strong id="usage-output">0</strong></span>
193 </div>
194 </section>
195
196 <section class="usage-section" aria-labelledby="models-title">
197 <h3 id="models-title">By model</h3>
198 <div id="usage-models" class="usage-models"></div>
199 </section>
200
201 <section class="usage-section" aria-labelledby="recent-title">
202 <h3 id="recent-title">Recent requests</h3>
203 <div id="usage-recent" class="usage-recent"></div>
204 </section>
205
206 <p id="usage-note" class="usage-note"></p>
207 <p class="usage-disclaimer">This is not your OpenAI invoice. It excludes requests from other browsers and applications, incomplete requests without returned usage, taxes, credits, tools, special processing, and usage recorded before this tracker was added. Prices can change.</p>
208 <div class="dialog-actions">
209 <button id="reset-usage" class="button button-ghost danger" type="button">Reset local history</button>
210 <a class="button button-ghost usage-dashboard" href="https://platform.openai.com/usage" target="_blank" rel="noopener noreferrer">Official dashboard ↗</a>
211 <button class="button button-dark" value="close" type="submit">Done</button>
212 </div>
213 </form>
214 </dialog>
215
216 <div id="toast" class="toast" role="status" aria-live="polite"></div>
217
218 <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
219 <script defer src="app.js"></script>
220</body>
221</html>