Add mobile responsiveness
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
### 2026-02-01: 1.0.3
|
### 2026-02-01: 1.0.3
|
||||||
|
|
||||||
|
* Add mobile responsiveness
|
||||||
* Fix modal accessibility
|
* Fix modal accessibility
|
||||||
* Fix progress bar accessibility
|
* Fix progress bar accessibility
|
||||||
* Add Open Library API integration for missing book metadata (opt-in)
|
* Add Open Library API integration for missing book metadata (opt-in)
|
||||||
|
|||||||
+18
-17
@@ -512,7 +512,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div style={styles.container}>
|
<div style={styles.container}>
|
||||||
{/* Top controls */}
|
{/* Top controls */}
|
||||||
<div style={styles.topBar}>
|
<div style={styles.topBar} className="top-bar">
|
||||||
<div style={styles.topLeft}>
|
<div style={styles.topLeft}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowTextInput(!showTextInput)}
|
onClick={() => setShowTextInput(!showTextInput)}
|
||||||
@@ -524,7 +524,7 @@ function App() {
|
|||||||
title="Edit text"
|
title="Edit text"
|
||||||
>
|
>
|
||||||
<FileText size={16} />
|
<FileText size={16} />
|
||||||
<span>Text</span>
|
<span className="text-btn-label">Text</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
@@ -534,7 +534,7 @@ function App() {
|
|||||||
disabled={isLoadingFile}
|
disabled={isLoadingFile}
|
||||||
>
|
>
|
||||||
<Upload size={16} />
|
<Upload size={16} />
|
||||||
<span>{isLoadingFile ? "Loading..." : "Upload"}</span>
|
<span className="text-btn-label">{isLoadingFile ? "Loading..." : "Upload"}</span>
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
@@ -613,7 +613,7 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Main display area */}
|
{/* Main display area */}
|
||||||
<div style={styles.mainArea}>
|
<div style={styles.mainArea} className="main-area">
|
||||||
<div style={styles.displayArea}>
|
<div style={styles.displayArea}>
|
||||||
<div style={styles.focalGuide}>
|
<div style={styles.focalGuide}>
|
||||||
<div style={styles.focalLine} />
|
<div style={styles.focalLine} />
|
||||||
@@ -621,14 +621,14 @@ function App() {
|
|||||||
<div style={styles.focalLine} />
|
<div style={styles.focalLine} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={styles.wordContainer}>
|
<div style={styles.wordContainer} className="word-container">
|
||||||
{currentWord ? (
|
{currentWord ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
...styles.wordDisplay,
|
...styles.wordDisplay,
|
||||||
transform: `translateY(-50%) translateX(calc(-${orpIndex}ch - 0.5ch))`,
|
transform: `translateY(-50%) translateX(calc(-${orpIndex}ch - 0.5ch))`,
|
||||||
}}
|
}}
|
||||||
className="mono"
|
className="mono word-display"
|
||||||
>
|
>
|
||||||
<span style={{ ...styles.beforeORP, opacity: sideOpacity }}>
|
<span style={{ ...styles.beforeORP, opacity: sideOpacity }}>
|
||||||
{beforeORP}
|
{beforeORP}
|
||||||
@@ -644,7 +644,7 @@ function App() {
|
|||||||
...styles.wordDisplay,
|
...styles.wordDisplay,
|
||||||
transform: "translateY(-50%) translateX(-50%)",
|
transform: "translateY(-50%) translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
className="mono"
|
className="mono word-display"
|
||||||
>
|
>
|
||||||
<span style={styles.placeholder}>Ready</span>
|
<span style={styles.placeholder}>Ready</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -660,7 +660,7 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom controls */}
|
{/* Bottom controls */}
|
||||||
<div style={styles.bottomArea}>
|
<div style={styles.bottomArea} className="bottom-area">
|
||||||
{/* Controls with play button in center */}
|
{/* Controls with play button in center */}
|
||||||
<div style={styles.controlsRow}>
|
<div style={styles.controlsRow}>
|
||||||
<button
|
<button
|
||||||
@@ -671,7 +671,7 @@ function App() {
|
|||||||
<ChevronLeft size={24} />
|
<ChevronLeft size={24} />
|
||||||
<ChevronLeft size={24} style={{ marginLeft: -14 }} />
|
<ChevronLeft size={24} style={{ marginLeft: -14 }} />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={togglePlay} style={styles.playBtn}>
|
<button onClick={togglePlay} style={styles.playBtn} className="play-btn">
|
||||||
{isPlaying ? <PauseSolid size={32} /> : <PlaySolid size={32} />}
|
{isPlaying ? <PauseSolid size={32} /> : <PlaySolid size={32} />}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -711,7 +711,7 @@ function App() {
|
|||||||
{currentIndex + 1} / {words.length} ({Math.round(progress)}%)
|
{currentIndex + 1} / {words.length} ({Math.round(progress)}%)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={styles.hint}>
|
<div style={styles.hint} className="hint">
|
||||||
<kbd style={styles.kbd}>Space</kbd> play
|
<kbd style={styles.kbd}>Space</kbd> play
|
||||||
<kbd style={styles.kbd}>←</kbd>
|
<kbd style={styles.kbd}>←</kbd>
|
||||||
<kbd style={styles.kbd}>→</kbd> word
|
<kbd style={styles.kbd}>→</kbd> word
|
||||||
@@ -723,22 +723,23 @@ function App() {
|
|||||||
|
|
||||||
{/* Book metadata display */}
|
{/* Book metadata display */}
|
||||||
{bookMetadata && (bookMetadata.title || bookMetadata.cover) && (
|
{bookMetadata && (bookMetadata.title || bookMetadata.cover) && (
|
||||||
<aside style={styles.bookMetadata} aria-label="Current book">
|
<aside style={styles.bookMetadata} aria-label="Current book" className="book-metadata">
|
||||||
{bookMetadata.cover && (
|
{bookMetadata.cover && (
|
||||||
<img
|
<img
|
||||||
src={bookMetadata.cover}
|
src={bookMetadata.cover}
|
||||||
alt={`Cover of ${bookMetadata.title || "current book"}`}
|
alt={`Cover of ${bookMetadata.title || "current book"}`}
|
||||||
style={styles.bookCover}
|
style={styles.bookCover}
|
||||||
|
className="book-cover"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div style={styles.bookInfo}>
|
<div style={styles.bookInfo}>
|
||||||
{bookMetadata.title && (
|
{bookMetadata.title && (
|
||||||
<h3 style={styles.bookTitle}>{bookMetadata.title}</h3>
|
<h3 style={styles.bookTitle} className="book-title">{bookMetadata.title}</h3>
|
||||||
)}
|
)}
|
||||||
{bookMetadata.author && (
|
{bookMetadata.author && (
|
||||||
<p style={styles.bookAuthor}>{bookMetadata.author}</p>
|
<p style={styles.bookAuthor} className="book-author">{bookMetadata.author}</p>
|
||||||
)}
|
)}
|
||||||
<p style={styles.bookStats}>
|
<p style={styles.bookStats} className="book-stats">
|
||||||
{formatReadingTime((words.length - currentIndex) / wpm)} left
|
{formatReadingTime((words.length - currentIndex) / wpm)} left
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -753,7 +754,7 @@ function App() {
|
|||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={styles.modal}
|
style={styles.modal} className="modal"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
@@ -816,7 +817,7 @@ function App() {
|
|||||||
{showInfo && (
|
{showInfo && (
|
||||||
<div style={styles.modalOverlay} onClick={() => setShowInfo(false)} role="presentation">
|
<div style={styles.modalOverlay} onClick={() => setShowInfo(false)} role="presentation">
|
||||||
<div
|
<div
|
||||||
style={styles.modal}
|
style={styles.modal} className="modal"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
@@ -923,7 +924,7 @@ function App() {
|
|||||||
{showSettings && (
|
{showSettings && (
|
||||||
<div style={styles.modalOverlay} onClick={() => setShowSettings(false)} role="presentation">
|
<div style={styles.modalOverlay} onClick={() => setShowSettings(false)} role="presentation">
|
||||||
<div
|
<div
|
||||||
style={styles.modal}
|
style={styles.modal} className="modal"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
|
|||||||
@@ -47,3 +47,99 @@ button:active {
|
|||||||
.wpm-btn:hover {
|
.wpm-btn:hover {
|
||||||
color: #ccc !important;
|
color: #ccc !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile responsiveness */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
/* Hide text labels on buttons */
|
||||||
|
.icon-btn span,
|
||||||
|
.text-btn-label {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduce top bar padding */
|
||||||
|
.top-bar {
|
||||||
|
padding: 12px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smaller WPM display */
|
||||||
|
.wpm-value {
|
||||||
|
font-size: 1.2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smaller word display */
|
||||||
|
.word-display {
|
||||||
|
font-size: 2.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduce main area padding */
|
||||||
|
.main-area {
|
||||||
|
padding: 0 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smaller word container */
|
||||||
|
.word-container {
|
||||||
|
height: 100px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduce bottom area padding */
|
||||||
|
.bottom-area {
|
||||||
|
padding: 12px 16px 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smaller play button */
|
||||||
|
.play-btn {
|
||||||
|
width: 56px !important;
|
||||||
|
height: 56px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide keyboard hints on mobile */
|
||||||
|
.hint {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smaller book metadata */
|
||||||
|
.book-metadata {
|
||||||
|
max-width: 200px !important;
|
||||||
|
bottom: 12px !important;
|
||||||
|
left: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-cover {
|
||||||
|
width: 36px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-title {
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-author,
|
||||||
|
.book-stats {
|
||||||
|
font-size: 0.65rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal adjustments */
|
||||||
|
.modal {
|
||||||
|
max-width: 95% !important;
|
||||||
|
max-height: 90vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
/* Even smaller word display for very small screens */
|
||||||
|
.word-display {
|
||||||
|
font-size: 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-container {
|
||||||
|
height: 80px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide book metadata on very small screens */
|
||||||
|
.book-metadata {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user