import type { Metadata } from "next"; import { Geist_Mono } from "next/font/google"; import localFont from "next/font/local"; import { FeedbackHost } from "@/app/_components/feedback-host"; import { FeedbackProvider } from "@/app/_components/feedback-provider"; import { THEME_ATTRIBUTE_NAME, THEME_STORAGE_KEY, } from "@/lib/constants/theme"; import "./globals.scss"; // 첫 페인트 전 테마 적용 — 는 보다 먼저 파싱되므로 만 조작 가능하다 // (preventing-flash-before-hydration.md의 Themes 패턴). localStorage에 명시 선택("light" // /"dark")이 저장돼 있으면 첫 페인트 전에 을 부여해 FOUC를 막는다. 저장값이 // 없거나("system") 손상된 값이면 속성을 부여하지 않는다 — globals.css의 // `prefers-color-scheme` 미디어 쿼리가 그대로 OS 설정을 따르는 fail-safe 경로다. // // THEME_STORAGE_KEY/THEME_ATTRIBUTE_NAME은 반드시 'use client' 파일이 아닌 // lib/constants/theme에서 import한다 — 'use client' 모듈의 일반 값(문자열) export를 // Server Component가 직접 import하면 클라이언트 레퍼런스 프록시 특성상 undefined로 평가된다 // (컴포넌트 export만 정상적으로 프록시된다). const THEME_BOOTSTRAP_SCRIPT = `(function(){try{var t=localStorage.getItem(${JSON.stringify( THEME_STORAGE_KEY )});if(t==="light"||t==="dark")document.documentElement.setAttribute(${JSON.stringify( THEME_ATTRIBUTE_NAME )},t)}catch(e){}})()`; // `@fox` **밖** 앱 텍스트의 기본 서체. 폰트 파일은 앱이 소유하고 `--app-font-sans` 계약으로 // 넘긴다(@fox/README.md "호스트 앱과의 계약") — 그 변수를 실제로 꽂는 곳은 globals.scss의 // `body`다. @fox 컴포넌트는 이 값을 쓰지 않는다: 토큰이 패밀리명을 `Pretendard`로 직접 // 지정하고 그 @font-face도 globals.scss에 있다. 즉 한 화면에 두 서체가 공존한다. const wantedSans = localFont({ src: [ { path: "../public/fonts/wanted-sans-regular.ttf", weight: "400", style: "normal", }, { path: "../public/fonts/wanted-sans-medium.ttf", weight: "500", style: "normal", }, { path: "../public/fonts/wanted-sans-semibold.ttf", weight: "600", style: "normal", }, { path: "../public/fonts/wanted-sans-bold.ttf", weight: "700", style: "normal", }, ], variable: "--app-font-sans", display: "swap", }); // 코드/숫자 표기용 고정폭 서체 — sans와 동일하게 `--app-font-mono` 계약으로 주입한다. const geistMono = Geist_Mono({ variable: "--app-font-mono", subsets: ["latin"], }); // 관리자 사이트 전체(로그인 포함)의 검색엔진 색인을 차단한다 — 하위 라우트는 별도 설정이 // 없는 한 이 값을 그대로 상속한다. robots.txt(app/robots.ts), next.config.ts의 // X-Robots-Tag 헤더와 함께 SEO 차단 3층 구조를 이룬다. export const metadata: Metadata = { title: "에듀페이 관리자", robots: { index: false, follow: false, nocache: true, googleBot: { index: false, follow: false, noimageindex: true, "max-image-preview": "none", "max-snippet": 0, "max-video-preview": 0, }, }, }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return (