diff --git a/src/app/globals.scss b/src/app/globals.scss index f123f11..403f1d9 100644 --- a/src/app/globals.scss +++ b/src/app/globals.scss @@ -20,7 +20,7 @@ html { .word-processor { display: flex; flex-direction: column; - height: 100vh; + height: 100dvh; overflow: hidden; } @@ -443,17 +443,27 @@ html { display: flex; justify-content: center; padding: 24px 24px; + + @media (max-width: 815px) { + padding: 0; + } } // ─── Document Page (white paper) ───────────────────────────────────────────── .document-page { background: #fff; - width: 816px; + width: min(816px, 100%); min-height: 1056px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.55); - padding: 96px; + padding: clamp(16px, 6vw, 96px); flex-shrink: 0; + + @media (max-width: 815px) { + width: 100%; + min-height: 100%; + box-shadow: none; + } } // ─── Document content (contentEditable) ────────────────────────────────────── @@ -536,3 +546,147 @@ html { opacity: 0.85; font-style: italic; } + +// ─── Dev watermark ───────────────────────────────────────────────────────────── + +.dev-watermark { + position: fixed; + bottom: 32px; // sit above the status bar + right: 12px; + z-index: 10000; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; + color: rgba(255, 60, 60, 0.55); + border: 1px solid rgba(255, 60, 60, 0.35); + border-radius: 3px; + padding: 1px 5px; + pointer-events: none; + user-select: none; +} + +// ─── Mobile: Ribbon chunk dropdown ────────────────────────────────────────── + +.word-ribbon--mobile { + .ribbon-panel { + min-height: unset; + padding: 4px 6px; + gap: 4px; + } + + .ribbon-divider { + display: none; + } +} + +.ribbon-chunk-mobile-btn { + display: inline-flex; + align-items: center; + gap: 3px; + padding: 3px 8px; + height: 26px; + background: transparent; + border: 1px solid #c0c0c0; + border-radius: 3px; + font-size: 12px; + color: #222; + cursor: pointer; + white-space: nowrap; + line-height: 1; + + &:hover, + &--open { + background: #dde0e5; + } + + svg { + fill: #222; + flex-shrink: 0; + } +} + +.ribbon-chunk-mobile-dropdown { + position: fixed; + background: #f5f5f5; + border: 1px solid #c6c6c6; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); + z-index: 9999; + padding: 8px; + min-width: 180px; + max-width: calc(100vw - 16px); + overflow-y: auto; + overflow-x: hidden; + + // Re-apply ribbon button overrides — the portal lives outside .word-ribbon. + .cds--btn--ghost { + min-height: 22px; + padding: 2px 4px; + color: #222; + + &:hover { + background: #dde0e5; + color: #222; + } + + svg { + fill: #222; + } + } + + .cds--btn--primary { + min-height: 22px; + padding: 2px 4px; + } + + .ribbon-row { + flex-wrap: wrap; + gap: 3px; + margin-bottom: 4px; + + &:last-child { + margin-bottom: 0; + } + } + + .ribbon-select { + min-width: 100%; + height: 28px; + margin-bottom: 4px; + + &--size { + min-width: 60px; + width: 60px; + } + } + + .ribbon-styles { + gap: 4px; + } + + &__launcher { + display: flex; + justify-content: flex-end; + margin-top: 4px; + padding-top: 4px; + border-top: 1px solid #c8c8c8; + } +} + +// ─── Mobile: Title Bar ──────────────────────────────────────────────────────── + +@media (max-width: 672px) { + // Hide the verbose "Autosave" label and transient status text to free up + // horizontal space; the toggle itself stays so the user can still control it. + .word-title-bar__autosave-toggle-label { + display: none; + } + + .word-title-bar__autosave-status { + display: none; + } + + // Shrink the centred document title so it doesn't crowd the icon buttons. + .word-title-bar__document-title { + font-size: 11px; + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fac41db..31d1078 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -11,6 +11,15 @@ export const metadata: Metadata = { } }; +export const viewport = { + width: 'device-width', + initialScale: 1, + // Shrink the layout viewport (and all vh-sized elements) when the soft + // keyboard opens, instead of overlaying the content. Supported by Chrome + // on Android 108+; other browsers fall back gracefully. + interactiveWidget: 'resizes-content', +}; + export default function RootLayout({ children, }: Readonly<{ @@ -18,8 +27,11 @@ export default function RootLayout({ }>) { return ( - + {children} + {process.env.NODE_ENV === 'development' && ( + + )} ); diff --git a/src/app/page.tsx b/src/app/page.tsx index 29e7ec0..a3ffebd 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -51,6 +51,11 @@ export default function WordProcessor() { const [citationStyle, setCitationStyle] = useState(''); const [alignment, setAlignment] = useState('left'); const [zoom, setZoom] = useState(100); + const [isHydrated, setIsHydrated] = useState(false); + + useEffect(() => { + setIsHydrated(true); + }, []); // Convert tags (created by execCommand) to so // that Word's HTML renderer handles multi-word font names (e.g. Times New Roman) reliably. @@ -409,6 +414,11 @@ export default function WordProcessor() { // Scale factor as a fraction (e.g. 0.8 for 80%) const scaleFactor = zoom / 100; + if (!isHydrated) { + // Keep server and first client render identical to avoid Carbon tooltip/tab ID mismatches. + return