Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ stage presence, and the streamer is the director. The visuals moved with the met
| 👻 | app identity / a ghost |
| 🧠 | brain / model / API |
| 📷 | stream & OBS |
| 🎙 | voice / mic transcription |
| 🎙 | voice / mic transcription |
| 💬 | chat behavior & cadence |
| 🎨 | look / theming |
| 🔧 | app plumbing |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Quit Hype Ghost.
| `app.autoUpdate` | Check GitHub for new versions at launch and update automatically (default true). |
| `app.setupComplete` | Set by the setup wizard when you finish it (even via the brain step's "skip"). Once true, `/` opens the dashboard — without a brain that's **preview mode**: everything explorable, cast quiet, $0. |
| `app.costMeter` | Show the live session cost readout in the deck's top bar (default true). |
| `app.fontScale` | UI text size for the deck/settings/wizard: `1` default, `1.1` large, `1.25` extra large (Settings → App, applies live). Never shrinks below default, and the on-stream overlay is unaffected. |
| `app.ttsOutputDevice` | Which speaker/headphone spoken cast messages play on (Settings → App → 🔊, with a test button). Picking a specific device keeps chat out of your stream mix and off the VOD; that path speaks with Windows (SAPI) voices. Blank = system default device with browser voices. |
| `bot.language` | Language the whole cast chats in (default English). |
| `stream.context` | Standing context about your stream (game, format, vibe) the cast always knows. It also sees your current OBS scene name automatically. |
Expand Down
1 change: 1 addition & 0 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"ttsRate": 1,
"ttsOutputDevice": "",
"uiLanguage": "en",
"fontScale": 1,
"setupComplete": false
},
"stream": {
Expand Down
6 changes: 5 additions & 1 deletion public/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@

function setOrb(id, cls, text) { const el = $(id); el.className = 'orb ' + cls; el.querySelector('.lbl').textContent = text; }

let appliedLang = null, appliedAccent = null;
let appliedLang = null, appliedAccent = null, appliedScale = null;
function renderState() {
// Language + accent apply only when they actually change — applyI18n
// sweeps the whole DOM and state broadcasts arrive constantly.
Expand All @@ -450,6 +450,10 @@
applyAccent(state.accent);
applyI18n();
}
if (state.fontScale !== appliedScale) {
appliedScale = state.fontScale;
document.documentElement.style.setProperty('--font-scale', Math.min(1.5, Math.max(1, Number(state.fontScale) || 1)));
}
if (JSON.stringify(cast) !== JSON.stringify(state.cast || [])) { cast = state.cast || []; renderStage(); }

const si = state.streamInfo, np = $('nowplaying');
Expand Down
19 changes: 17 additions & 2 deletions public/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ <h1 class="page-title">⚙ Settings</h1>
<button class="tabbtn on" data-tab="cast">👻 <span>Cast</span></button>
<button class="tabbtn" data-tab="brain">🧠 <span>Brain</span></button>
<button class="tabbtn" data-tab="stream">📷 <span>Stream</span></button>
<button class="tabbtn" data-tab="voice">🎙 <span>Voice</span></button>
<button class="tabbtn" data-tab="voice">🎙 <span>Voice</span></button>
<button class="tabbtn" data-tab="behavior">💬 <span>Behavior</span></button>
<button class="tabbtn" data-tab="look">🎨 <span>Look</span></button>
<button class="tabbtn" data-tab="app">🔧 <span>App</span></button>
Expand Down Expand Up @@ -189,7 +189,7 @@ <h2>🎮 Stream info for the cast</h2>

<!-- ============ VOICE ============ -->
<section class="card" data-tab="voice">
<h2>🎙 Voice awareness</h2>
<h2>🎙 Voice awareness</h2>
<p class="desc">Local mic transcription via the <a href="https://obsproject.com/forum/resources/localvocal-local-live-captions-translation-on-the-go.1769/" target="_blank">LocalVocal</a> OBS plugin, so answering out loud is replying. It's third-party — keep it updated to match your OBS version (an outdated build can crash OBS, e.g. when switching Scene Collections). Optional: leave this Off to reply by typing.</p>
<label>Mode</label>
<select data-path="transcript.mode">
Expand Down Expand Up @@ -302,6 +302,13 @@ <h2>🎨 Look</h2>
<!-- ============ APP ============ -->
<section class="card" data-tab="app">
<h2>🔧 App</h2>
<label>Font size</label>
<select data-path="app.fontScale" data-type="num" id="fontScale" style="max-width:220px">
<option value="1">Default</option>
<option value="1.1">Large</option>
<option value="1.25">Extra large</option>
</select>
<p class="hint">Scales the text across the deck, settings, and wizard (preview is instant; Save keeps it). The on-stream overlay is untouched — its look lives on the Stream tab.</p>
<div class="check">
<input type="checkbox" id="autoUpdate" data-path="app.autoUpdate" data-type="bool">
<label for="autoUpdate">Update automatically (checks GitHub for new versions at launch)</label>
Expand Down Expand Up @@ -538,6 +545,7 @@ <h2 style="color:var(--bad)">Danger zone</h2>
$('aboutVersion').textContent = 'v' + (data.version || '?');
$('aboutConfigPath').textContent = data.configPath || '';
initStorage();
applyFontScale(cfg.app && cfg.app.fontScale);
if (apiKeySaved) $('apiKeyField').placeholder = 'saved ✓ — leave blank to keep the current key';
const saved = 'saved ✓ — leave blank to keep';
if (data.openaiKeySaved) fields.find((f) => f.dataset.path === 'brain.openaiApiKey').placeholder = saved;
Expand Down Expand Up @@ -668,6 +676,12 @@ <h2 style="color:var(--bad)">Danger zone</h2>
$('browseFile').onclick = () => pickInto('transcript.file', 'transcript.mode');
$('browseFile2').onclick = () => pickInto('transcript2.file', 'transcript2.mode');

// ---- font scale (instant preview; Save persists + hot-applies) ----
function applyFontScale(v) {
document.documentElement.style.setProperty('--font-scale', Math.min(1.5, Math.max(1, Number(v) || 1)));
}
$('fontScale').onchange = () => applyFontScale($('fontScale').value);

// ---- storage (data folder) ----
// Desktop only: the server fronts Electron's folder dialog + file migration,
// then relaunches. Headless/dev just shows where the data lives.
Expand Down Expand Up @@ -719,6 +733,7 @@ <h2 style="color:var(--bad)">Danger zone</h2>
for (const f of fields) {
let v;
if (f.dataset.type === 'bool') v = f.checked;
else if (f.dataset.type === 'num') v = Number(f.value) || 0; // selects with numeric values
else if (f.type === 'number') { if (f.value === '') continue; v = Number(f.value); }
else v = f.value.trim ? f.value.trim() : f.value;
setPath(cfg, f.dataset.path, v);
Expand Down
3 changes: 2 additions & 1 deletion public/setup.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ <h1 class="page-title" style="margin-top:4px">Let's build your room</h1>
<div class="step" data-step="5">
<b>Optional extras</b>
<details>
<summary>🎙 Voice awareness — the cast hears what you say (recommended)</summary>
<summary>🎙 Voice awareness — the cast hears what you say (recommended)</summary>
<p class="hint">Install the free <a href="https://obsproject.com/forum/resources/localvocal-local-live-captions-translation-on-the-go.1769/" target="_blank">LocalVocal</a> OBS plugin (speech-to-text runs 100% locally), add its filter to your mic, and point it at a file or text source. Keep LocalVocal updated to match your OBS version — an outdated build can crash OBS on newer OBS releases (it's third-party, not part of Hype Ghost). Voice awareness is optional; leave this Off to reply by typing.</p>
<label>Mode</label>
<select id="trMode"><option value="off">Off</option><option value="file">File — tail LocalVocal's .txt/.srt output</option><option value="textSource">Text source — read via OBS WebSocket</option></select>
Expand Down Expand Up @@ -170,6 +170,7 @@ <h2>Your room is ready!</h2>

fetch('/api/config').then((r) => r.json()).then((data) => {
cfg = data.config; apiKeySaved = data.apiKeySaved; canRestart = data.canRestart;
document.documentElement.style.setProperty('--font-scale', Math.min(1.5, Math.max(1, Number(cfg.app?.fontScale) || 1)));
archetypes = data.archetypes || []; palette = data.palette || {};
$('model').innerHTML = data.models.map((m) => `<option value="${m.id}">${m.label}</option>`).join('');
// archetype cards
Expand Down
20 changes: 12 additions & 8 deletions public/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,16 @@
--r-full: 999px;

/* --- type scale --- */
--fs-xs: 11px;
--fs-sm: 13px;
--fs-base: 15px;
--fs-lg: 18px;
--fs-xl: 22px;
--fs-2xl: 30px;
/* Every size scales with the font-size setting (Settings → App): pages set
--font-scale from app.fontScale (1 / 1.1 / 1.25). The overlay never sets
it — the on-stream look (and the watermark) stays as designed. */
--font-scale: 1;
--fs-xs: calc(12px * var(--font-scale));
--fs-sm: calc(14px * var(--font-scale));
--fs-base: calc(15px * var(--font-scale));
--fs-lg: calc(18px * var(--font-scale));
--fs-xl: calc(22px * var(--font-scale));
--fs-2xl: calc(30px * var(--font-scale));

/* --- depth --- */
--shadow: 0 18px 44px -20px rgba(0, 0, 0, 0.75);
Expand Down Expand Up @@ -192,7 +196,7 @@ input:focus:not(:focus-visible), textarea:focus:not(:focus-visible), select:focu

/* the AI label — the ethics promise made visible; identical on every surface */
.badge-ai {
font-size: 10px;
font-size: calc(10px * var(--font-scale)); /* grows with the UI; --font-scale never goes below 1 */
font-weight: 800;
letter-spacing: .6px;
padding: 2px 7px;
Expand All @@ -218,7 +222,7 @@ input:focus:not(:focus-visible), textarea:focus:not(:focus-visible), select:focu
/* ---------- layout helpers ---------- */
.row { display: flex; gap: 14px; }
.row > div { flex: 1; }
.check { display: flex; align-items: flex-start; gap: 10px; margin-top: 14px; font-size: 14px; }
.check { display: flex; align-items: flex-start; gap: 10px; margin-top: 14px; font-size: var(--fs-sm); }
.check input { width: auto; margin-top: 3px; flex: none; }
.check label { margin: 0; color: var(--text); font-weight: 450; }
.withbtn { display: flex; gap: 8px; }
Expand Down
7 changes: 6 additions & 1 deletion src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export function startServer(opts = {}) {
outputDevice: config.app.ttsOutputDevice || '',
engine: sapiTts.available() ? 'sapi' : 'browser',
});
// UI font scale (Settings → App). Never below 1: text can grow, not shrink —
// and the 🤖 AI badge inherits the same guarantee.
const fontScale = () => Math.min(1.5, Math.max(1, Number(config.app.fontScale) || 1));
const brain = new Brain({
brain: config.brain,
anthropic: config.anthropic,
Expand Down Expand Up @@ -114,6 +117,7 @@ export function startServer(opts = {}) {
costMeter: config.app.costMeter !== false,
tts: ttsState(),
uiLanguage: config.app.uiLanguage || 'en',
fontScale: fontScale(),
usage: { messages: 0, inputTokens: 0, outputTokens: 0, cost: 0, costKnown: true },
};
const history = []; // {id, author, role: 'bot'|'streamer', text, ts}
Expand Down Expand Up @@ -229,7 +233,7 @@ export function startServer(opts = {}) {
// transcripts, Twitch, port) still restarts.
const HOT_PATHS = [
'energy', 'talkingPoints', 'theme.', 'overlay.', 'moments.', 'memory.', 'stream.',
'app.costMeter', 'app.uiLanguage', 'app.autoPause', 'app.autoPauseMinutes', 'app.autoUpdate',
'app.costMeter', 'app.uiLanguage', 'app.fontScale', 'app.autoPause', 'app.autoPauseMinutes', 'app.autoUpdate',
'app.ttsVoice', 'app.ttsRate', 'app.ttsOutputDevice',
'cadence.soloSeconds', 'cadence.quietSeconds', 'cadence.jitter', 'cadence.burstChance',
'cadence.lullChance', 'cadence.replyDelaySeconds', 'cadence.minVoiceReplyGapSeconds',
Expand Down Expand Up @@ -272,6 +276,7 @@ export function startServer(opts = {}) {
state.accent = config.theme?.accent || 'violet';
state.costMeter = config.app.costMeter !== false;
state.uiLanguage = config.app.uiLanguage || 'en';
state.fontScale = fontScale();
state.tts = ttsState(); // voice/rate/output device are hot; the on/off toggle is not
broadcast({ type: 'config' }); // overlay clients re-fetch /api/overlay-config
broadcastState();
Expand Down