import type { Metadata } from "next"; import { Geist_Mono } from "next/font/google"; import localFont from "next/font/local"; import { ViewTransition } from "react"; import { FeedbackHost } from "@/app/_components/feedback-host"; import { FeedbackProvider } from "@/app/_components/feedback-provider"; import { SplashGate } from "@/app/_components/splash-gate"; import { SPLASH_SHOWN_STORAGE_KEY } from "@/lib/constants/splash"; import "./globals.css"; // 스플래시 재표시 방지 — 는 보다 먼저 파싱되므로 body 하위 요소는 아직 존재하지 // 않는다(preventing-flash-before-hydration.md의 Themes 패턴과 동일하게 만 조작 가능). // 이번 세션에 이미 표시했다면 첫 페인트 전에 에 data attribute를 부여하고, 아래 스타일 // 규칙이 SplashGate 루트를 즉시 숨긴다 — useEffect보다 먼저 실행되어 깜빡임이 없다. // // SPLASH_SHOWN_STORAGE_KEY는 반드시 'use client' 파일이 아닌 lib/constants/splash에서 // import한다 — splash-gate.tsx('use client')의 일반 값(문자열) export를 Server Component가 // 직접 import하면 클라이언트 레퍼런스 프록시 특성상 undefined로 평가된다(컴포넌트 export만 // 정상적으로 프록시된다). const SPLASH_HIDE_STYLE = `html[data-splash-shown] [data-splash-gate]{display:none}`; const SPLASH_GATE_SCRIPT = `(function(){try{if(sessionStorage.getItem(${JSON.stringify( SPLASH_SHOWN_STORAGE_KEY )}))document.documentElement.setAttribute("data-splash-shown","")}catch(e){}})()`; // 어드민 전체 기본 서체(edupay-app 일체화, design 전달 사양) — Geist는 대체되어 제거했다. // globals.css `--font-sans`가 이 변수를 우선 참조하고, 로드 전에도 system-ui로 안전하게 // 폴백하도록 이미 구성돼 있다. 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: "--font-wanted-sans", display: "swap", }); // 코드/숫자 표기용 고정폭 서체 — globals.css `--font-mono`가 여전히 이 변수를 직접 참조하므로 // (폴백 체인 없음) 유지한다. const geistMono = Geist_Mono({ variable: "--font-geist-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 (