From cde095169f905ff8240d02f4e567bd3f08699cc9 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Fri, 2 Jan 2026 21:39:54 +0000 Subject: [PATCH] feat: Implement settings modal with theme and font size adjustments Co-authored-by: aider (gemini/gemini-2.5-pro) --- webclient/src/App.js | 70 ++++++++++++++++++++++++++++++++- webclient/src/Style-light.css | 74 +++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/webclient/src/App.js b/webclient/src/App.js index 9c5e192..0600e08 100644 --- a/webclient/src/App.js +++ b/webclient/src/App.js @@ -20,6 +20,11 @@ function App() { const cache = useRef({}); const [isFullScreen, setIsFullScreen] = useState(!!document.fullscreenElement); const [waitingWorker, setWaitingWorker] = useState(null); + const [settingsOpen, setSettingsOpen] = useState(false); + const defaultBodyFontSize = 16; + const defaultStoryFontSize = 1.2; + const [bodyFontSize, setBodyFontSize] = useState(Number(localStorage.getItem('bodyFontSize')) || defaultBodyFontSize); + const [storyFontSize, setStoryFontSize] = useState(Number(localStorage.getItem('storyFontSize')) || defaultStoryFontSize); const updateCache = useCallback((key, value) => { cache.current[key] = value; @@ -45,6 +50,27 @@ function App() { localStorage.setItem('theme', 'red'); }; + const changeBodyFontSize = (amount) => { + const newSize = bodyFontSize + amount; + setBodyFontSize(newSize); + localStorage.setItem('bodyFontSize', newSize); + }; + + const changeStoryFontSize = (amount) => { + const newSize = storyFontSize + amount; + setStoryFontSize(parseFloat(newSize.toFixed(1))); + localStorage.setItem('storyFontSize', newSize.toFixed(1)); + }; + + const resetFontSettings = () => { + setBodyFontSize(defaultBodyFontSize); + localStorage.removeItem('bodyFontSize'); + setStoryFontSize(defaultStoryFontSize); + localStorage.removeItem('storyFontSize'); + }; + + const fontSettingsChanged = bodyFontSize !== defaultBodyFontSize || storyFontSize !== defaultStoryFontSize; + useEffect(() => { const onSWUpdate = e => { setWaitingWorker(e.detail.waiting); @@ -92,6 +118,21 @@ function App() { } }, [theme]); + useEffect(() => { + document.documentElement.style.fontSize = `${bodyFontSize}px`; + }, [bodyFontSize]); + + useEffect(() => { + const styleId = 'story-font-size-style'; + let style = document.getElementById(styleId); + if (!style) { + style = document.createElement('style'); + style.id = styleId; + document.head.appendChild(style); + } + style.innerHTML = `.story-text { font-size: ${storyFontSize}rem !important; }`; + }, [storyFontSize]); + const fullScreenAvailable = document.fullscreenEnabled || document.mozFullscreenEnabled || document.webkitFullscreenEnabled || @@ -99,6 +140,33 @@ function App() { return (
+ {settingsOpen && +
setSettingsOpen(false)}> +
e.stopPropagation()}> +

Settings

+
+

Theme

+ + + + +
+
+

Body Font Size

+ + {bodyFontSize}px + +
+
+

Story Font Size

+ + {storyFontSize.toFixed(1)}rem + +
+ +
+
+ } {waitingWorker &&
Client version mismatch, please refresh:{' '} @@ -117,7 +185,7 @@ function App() {

QotNews - light()}>Light - dark()}>Dark - black()}>Black - red()}>Red +
Hacker News, Reddit, Lobsters, and Tildes articles rendered in reader mode.

diff --git a/webclient/src/Style-light.css b/webclient/src/Style-light.css index 4344b9c..58ae032 100644 --- a/webclient/src/Style-light.css +++ b/webclient/src/Style-light.css @@ -405,3 +405,77 @@ button.comment { visibility: visible; opacity: 1; } + +.settings-button { + float: right; + background: transparent; + border: 1px solid #828282; + border-radius: 4px; + padding: 0.25rem 0.75rem; + cursor: pointer; + font: inherit; + color: inherit; +} + +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + z-index: 100; +} + +.modal-content { + position: fixed; + top: 1rem; + right: 1rem; + background: #eee; + color: #000; + padding: 1rem; + border-radius: 4px; + z-index: 101; + min-width: 250px; + border: 1px solid #828282; +} + +.modal-content h3, .modal-content h4 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +.modal-content .setting-group { + margin-bottom: 1rem; +} + +.modal-content button { + margin-right: 0.5rem; + padding: 0.25rem 0.75rem; + border: 1px solid #828282; + border-radius: 4px; + background-color: transparent; + cursor: pointer; + font: inherit; + color: inherit; +} + +.modal-content button:last-child { + margin-right: 0; +} + +.modal-content button.active { + background-color: #ccc; +} + +.modal-content button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.modal-content .font-size-display { + display: inline-block; + width: 50px; + text-align: center; + margin: 0 0.25rem; +}