import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { initTheme } from './stores/themeStore'; import { initBackground } from './stores/backgroundStore'; import './index.css'; // 背景开启时要变半透明的浅色表面类(由 scripts/gen-background-takeover.mjs 生成) import './background-takeover.generated.css';; // 必须在 render 之前:晚一步就会让深色偏好的用户看到一帧白色闪屏。 // index.html 里还有一段更早的内联脚本处理「JS bundle 到达前」那段空窗, // 这里做的是把 store 状态与 DOM 对齐并订阅系统主题变化。 initTheme(); // 自定义背景同样要在 render 之前套用(读 localStorage + 写 CSS 变量)。 // 它只是装饰层,晚一帧不会像主题那样闪出刺眼白底,但提前套用能避免 // 「先看到纯色底、再变成背景」的抽动。 initBackground(); /** * 用 Visual Viewport 驱动应用高度。 * * 手机软键盘通常只缩小「可见视口」,不缩小传统 100%/100vh;如果外壳还按 * 布局视口排版,正文和发送按钮就会落到键盘后面。dvh 是 CSS 兜底,这个变量 * 处理 iOS WebView 与部分旧 Chromium 对动态视口更新不及时的情况。 */ const syncVisibleViewport = () => { const viewport = window.visualViewport; const height = viewport?.height ?? window.innerHeight; document.documentElement.style.setProperty('--app-height', `${Math.round(height)}px`); }; syncVisibleViewport(); window.addEventListener('resize', syncVisibleViewport, { passive: true }); window.visualViewport?.addEventListener('resize', syncVisibleViewport, { passive: true }); window.visualViewport?.addEventListener('scroll', syncVisibleViewport, { passive: true }); // 编辑表单时隐藏窄屏底栏,把有限的键盘上方空间留给正文与操作按钮。 // focusout 要延后一帧:从一个输入框切到另一个时 activeElement 会短暂回到 body。 const syncEditingState = () => { const el = document.activeElement; const editing = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement; document.documentElement.classList.toggle('form-editing', editing); }; document.addEventListener('focusin', syncEditingState); document.addEventListener('focusout', () => requestAnimationFrame(syncEditingState)); ReactDOM.createRoot(document.getElementById('root')!).render( {/* 背景层挂在 App **之外**。 挂在 App 里面的话,它只会存在于主界面的那几个分支上 —— 登录页、加载页、 初始化向导都各自 return 自己的外层 div。背景是全局装饰,用户不该 「一进登录页背景就没了」。它 position:fixed + z-index:-1,与 App 的 布局无关,放这里最稳。 */}