+ {slides.map((slide, index) => (
+
+ {slide.kind === 'title' ? (
+ {slide.title}
+ ) : (
+
+ )}
+
+
+ ))}
+
+);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterRoot.tsx b/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterRoot.tsx
new file mode 100644
index 0000000000..7dc46889a0
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterRoot.tsx
@@ -0,0 +1,126 @@
+import dynamic from 'next/dynamic';
+import { useRouter } from 'next/router';
+import { useCallback, useEffect } from 'react';
+import { createPortal } from 'react-dom';
+import { css } from 'styled-components';
+
+import { Box, Loading } from '@/components';
+import { useEditorStore } from '@/docs/doc-editor/stores';
+import { useDocStore } from '@/docs/doc-management';
+import { useResponsiveStore } from '@/stores';
+
+import { usePresenterStore } from '../stores';
+
+const coverCss = css`
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ background: white;
+`;
+
+/**
+ * Full-screen white cover shown while the editor boots, so a `?view=present`
+ * deep-link never reveals the layout → skeleton → editor cascade underneath.
+ * Same background and z-index as the presenter overlay, so the swap to slides
+ * is seamless.
+ */
+const PresenterBootCover = () => {
+ if (typeof document === 'undefined') {
+ return null;
+ }
+ return createPortal(
+