diff --git a/src/app.css b/src/app.css index c68df99..0abf8e5 100644 --- a/src/app.css +++ b/src/app.css @@ -20,8 +20,12 @@ .mdv-app { --mdv-writing-font-size: 14px; --mdv-writing-line-height: 1.55; + --mdv-prose-font-family: var(--font-ui); --mdv-prose-font-size: 15px; --mdv-prose-line-height: 1.65; + --mdv-reading-font-size: 16px; + --mdv-reading-content-width: 880px; + --mdv-reading-prose-width: 720px; display: grid; grid-template-rows: var(--titlebar-h) var(--breadcrumb-h) 1fr var(--statusbar-h); height: 100vh; @@ -62,13 +66,22 @@ display: flex; } +html.is-mac .mdv-app.has-hidden-titlebar:not(.is-reading) .mdv-breadcrumb { + padding-left: 132px; + height: 42px; +} + +html.is-mac .mdv-app.has-hidden-titlebar:not(.is-reading) { + grid-template-rows: 0 42px 1fr var(--statusbar-h); +} + /* iA Writer-style reading mode: wide max-width, generous breathing room, subtle warmer tint, and slow calm fade-in */ .mdv-app.is-reading .mdv-shell > .mdv-preview { flex: 1; min-width: 0; - padding-left: max(56px, calc((100% - 880px) / 2)); - padding-right: max(56px, calc((100% - 880px) / 2)); + padding-left: max(56px, calc((100% - var(--mdv-reading-content-width)) / 2)); + padding-right: max(56px, calc((100% - var(--mdv-reading-content-width)) / 2)); padding-top: 80px; padding-bottom: 96px; background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); @@ -76,8 +89,8 @@ } .mdv-app.is-reading .mdv-shell > .mdv-preview .mdv-prose { - max-width: 720px; - font-size: 16px; + max-width: var(--mdv-reading-prose-width); + font-size: var(--mdv-reading-font-size); line-height: 1.75; } diff --git a/src/app.tsx b/src/app.tsx index 17de59e..04f28de 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -37,6 +37,9 @@ import { getContextBundleStats, getWhatsNewToastMessage, isSupportedTextPath, + normalizeProseFontFamily, + normalizeReadingFontSize, + normalizeReadingWidth, normalizeWritingFontSize, normalizeWritingLineHeight, PdfExportError, @@ -47,6 +50,9 @@ import { removeEntry, STORAGE_KEYS, useI18n, + type ProseFontFamily, + type ReadingFontSize, + type ReadingWidth, type WritingDisplay, type WritingFontSize, type WritingLineHeight, @@ -260,6 +266,18 @@ export function App() { STORAGE_KEYS.writingLineHeight, DEFAULT_WRITING_DISPLAY.lineHeight, ); + const [readingFontSize, setReadingFontSize] = usePersistedState( + STORAGE_KEYS.readingFontSize, + DEFAULT_WRITING_DISPLAY.readingFontSize, + ); + const [readingWidth, setReadingWidth] = usePersistedState( + STORAGE_KEYS.readingWidth, + DEFAULT_WRITING_DISPLAY.readingWidth, + ); + const [proseFontFamily, setProseFontFamily] = usePersistedState( + STORAGE_KEYS.proseFontFamily, + DEFAULT_WRITING_DISPLAY.proseFontFamily, + ); const [dragActive, setDragActive] = useState(false); const [stagedPaths, setStagedPaths] = useState([]); const [stagedTokenLabel, setStagedTokenLabel] = useState("0"); @@ -269,8 +287,11 @@ export function App() { () => ({ fontSize: normalizeWritingFontSize(writingFontSize), lineHeight: normalizeWritingLineHeight(writingLineHeight), + readingFontSize: normalizeReadingFontSize(readingFontSize), + readingWidth: normalizeReadingWidth(readingWidth), + proseFontFamily: normalizeProseFontFamily(proseFontFamily), }), - [writingFontSize, writingLineHeight], + [writingFontSize, writingLineHeight, readingFontSize, readingWidth, proseFontFamily], ); const writingDisplayStyle = useMemo( @@ -281,7 +302,10 @@ export function App() { const resetWritingDisplay = useCallback(() => { setWritingFontSize(DEFAULT_WRITING_DISPLAY.fontSize); setWritingLineHeight(DEFAULT_WRITING_DISPLAY.lineHeight); - }, [setWritingFontSize, setWritingLineHeight]); + setReadingFontSize(DEFAULT_WRITING_DISPLAY.readingFontSize); + setReadingWidth(DEFAULT_WRITING_DISPLAY.readingWidth); + setProseFontFamily(DEFAULT_WRITING_DISPLAY.proseFontFamily); + }, [setWritingFontSize, setWritingLineHeight, setReadingFontSize, setReadingWidth, setProseFontFamily]); const handleToggleSidebar = useCallback(() => { setSidebarOpen((v: boolean) => !v); @@ -815,6 +839,9 @@ export function App() { writingDisplay={writingDisplay} onWritingFontSizeChange={setWritingFontSize} onWritingLineHeightChange={setWritingLineHeight} + onReadingFontSizeChange={setReadingFontSize} + onReadingWidthChange={setReadingWidth} + onProseFontFamilyChange={setProseFontFamily} onResetWritingDisplay={resetWritingDisplay} /> @@ -839,6 +866,9 @@ export function App() { writingDisplay={writingDisplay} onWritingFontSizeChange={setWritingFontSize} onWritingLineHeightChange={setWritingLineHeight} + onReadingFontSizeChange={setReadingFontSize} + onReadingWidthChange={setReadingWidth} + onProseFontFamilyChange={setProseFontFamily} onResetWritingDisplay={resetWritingDisplay} /> diff --git a/src/components/chrome/breadcrumb.tsx b/src/components/chrome/breadcrumb.tsx index 20759bb..f4220ba 100644 --- a/src/components/chrome/breadcrumb.tsx +++ b/src/components/chrome/breadcrumb.tsx @@ -18,6 +18,9 @@ import { startWindowDrag, useI18n, type Translate, + type ProseFontFamily, + type ReadingFontSize, + type ReadingWidth, type WritingDisplay, type WritingFontSize, type WritingLineHeight, @@ -48,6 +51,9 @@ type BreadcrumbProps = { writingDisplay: WritingDisplay; onWritingFontSizeChange: (value: WritingFontSize) => void; onWritingLineHeightChange: (value: WritingLineHeight) => void; + onReadingFontSizeChange: (value: ReadingFontSize) => void; + onReadingWidthChange: (value: ReadingWidth) => void; + onProseFontFamilyChange: (value: ProseFontFamily) => void; onResetWritingDisplay: () => void; }; @@ -93,6 +99,9 @@ export function Breadcrumb({ writingDisplay, onWritingFontSizeChange, onWritingLineHeightChange, + onReadingFontSizeChange, + onReadingWidthChange, + onProseFontFamilyChange, onResetWritingDisplay, }: BreadcrumbProps) { const { t } = useI18n(); @@ -101,7 +110,11 @@ export function Breadcrumb({ const label = statusLabel(saveStatus, t); return ( -
+
- ) : null} -
+ {/* file actions — border-left matches the status→actions separator */} +
+ {onCopyMarkdown ? ( + + ) : null} +
+ + ) : null}
); diff --git a/src/components/chrome/theme-button.tsx b/src/components/chrome/theme-button.tsx index 4003357..4093fe4 100644 --- a/src/components/chrome/theme-button.tsx +++ b/src/components/chrome/theme-button.tsx @@ -20,13 +20,19 @@ import { Button, Icon, Popover } from "@/components/primitives"; import { getSystemTheme, LANGUAGE_CHOICES, + PROSE_FONT_FAMILY_OPTIONS, previewTheme, + READING_FONT_SIZE_OPTIONS, + READING_WIDTH_OPTIONS, THEME_GROUPS, WRITING_FONT_SIZE_OPTIONS, WRITING_LINE_HEIGHT_OPTIONS, useI18n, useThemeMode, useTransparency, + type ProseFontFamily, + type ReadingFontSize, + type ReadingWidth, type Theme, type ThemeMode, type WritingDisplay, @@ -58,6 +64,9 @@ type ThemeButtonProps = { writingDisplay: WritingDisplay; onWritingFontSizeChange: (value: WritingFontSize) => void; onWritingLineHeightChange: (value: WritingLineHeight) => void; + onReadingFontSizeChange: (value: ReadingFontSize) => void; + onReadingWidthChange: (value: ReadingWidth) => void; + onProseFontFamilyChange: (value: ProseFontFamily) => void; onResetWritingDisplay: () => void; }; @@ -103,12 +112,49 @@ function SliderControl({ ); } +type SelectControlProps = { + label: string; + value: T; + options: readonly T[]; + labelFor: (option: T) => string; + onChange: (value: T) => void; +}; + +function SelectControl({ + label, + value, + options, + labelFor, + onChange, +}: SelectControlProps) { + return ( + + ); +} + export function ThemeButton({ vimOn = false, onToggleVim, writingDisplay, onWritingFontSizeChange, onWritingLineHeightChange, + onReadingFontSizeChange, + onReadingWidthChange, + onProseFontFamilyChange, onResetWritingDisplay, }: ThemeButtonProps) { const { language, setLanguage, t } = useI18n(); @@ -245,7 +291,8 @@ export function ThemeButton({ {opacity >= 100 ? t("title.off") : `${100 - opacity}%`} /{" "} {t(`writing.font.${writingDisplay.fontSize}`)} /{" "} - {t(`writing.spacing.${writingDisplay.lineHeight}`)} + {t(`writing.spacing.${writingDisplay.lineHeight}`)} /{" "} + {t(`reading.width.${writingDisplay.readingWidth}`)} @@ -289,6 +336,27 @@ export function ThemeButton({ labelFor={(option) => t(`writing.spacing.${option}`)} onChange={onWritingLineHeightChange} /> + t(`writing.font.${option}`)} + onChange={onReadingFontSizeChange} + /> + t(`reading.width.${option}`)} + onChange={onReadingWidthChange} + /> + t(`prose.font.${option}`)} + onChange={onProseFontFamilyChange} + />