diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f60c750 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,91 @@ +# Mingling 프론트엔드 작업 지침 + +## 1. 적용 범위와 우선순위 + +- 이 지침은 저장소 전체에 적용한다. +- 현재 UI와 서비스 기능을 기본적으로 보존한다. +- 사용자가 UI 또는 기능 변경을 명시적으로 요청한 경우에만 해당 요청 범위 내에서 예외를 허용한다. +- 사용자 승인 없이 하위 디렉터리에 `AGENTS.md` 또는 `AGENTS.override.md`를 생성하지 않는다. +- 작업을 완료하려면 이 지침을 위반해야 한다고 판단되는 경우, 파일을 수정하지 말고 충돌 내용과 필요한 결정을 사용자에게 설명한다. + +## 2. UI 불변 조건 + +- 사용자가 명시적으로 요청하지 않은 문구, 라벨, 아이콘, 색상, 폰트, 글자 크기, 굵기, 줄 높이, 자간을 변경하지 않는다. +- 여백, 크기, 정렬, 테두리, 그림자, 애니메이션, 반응형 분기점, 이미지 크롭, 페이지 구성을 변경하지 않는다. +- 화면에 보이는 요소를 임의로 추가, 제거, 재배치하거나 스타일을 변경하지 않는다. +- 375px, 768px, 1024px, 1440px 뷰포트에서 현재 화면의 시각적 결과를 보존한다. +- 최종 렌더링 결과가 시각적으로 동일한 경우에만 내부 DOM, 컴포넌트 구조, 이미지 렌더링 방식, 리소스 로딩 방식을 변경할 수 있다. +- 사용자 승인 없이 폰트 패밀리, 등록된 폰트 굵기, 타이포그래피 메트릭, 폰트 preload 정책을 변경하지 않는다. +- 이미지, 지도, 다이얼로그, 로딩 화면, 동적 콘텐츠의 크기와 종횡비를 유지하고 새로운 레이아웃 이동을 발생시키지 않는다. + +## 3. 기능 불변 조건 + +- 사용자가 요청하지 않은 기능을 추가, 제거하거나 의미를 변경하지 않는다. +- 라우트, 리다이렉트, 링크 목적지, 쿼리 파라미터를 변경하지 않는다. +- API 엔드포인트, 요청 및 응답 구조, 환경 변수 이름을 변경하지 않는다. +- localStorage 및 sessionStorage 키, 로그인 판정과 인증 동작을 변경하지 않는다. +- 폼 검증, 제출 순서, 오류 처리, 로딩 상태, 모달 동작을 유지한다. +- 지도, 장소 추천, 모임 생성·참여·공유 흐름과 반응형 상호작용을 유지한다. +- 분석 이벤트 이름, payload, 전송 순서와 GTM 로드 전 `dataLayer` 큐 동작을 유지한다. +- 성능 최적화로 인해 해당 화면의 필수 기능이 늦게 활성화되거나 비활성화되어서는 안 된다. +- 사용자가 명시적으로 요청하지 않은 공개 TypeScript 인터페이스나 공용 데이터 계약을 변경하지 않는다. + +## 4. 변경 범위 관리 + +- 파일을 수정하기 전에 `git status`와 관련 파일을 확인한다. +- 사용자가 이미 작성한 변경 사항을 보존하며, 관련 없는 변경을 되돌리거나 덮어쓰지 않는다. +- 요청을 충족하는 데 필요한 최소한의 파일과 코드만 수정한다. +- 관련 없는 코드 정리, 대규모 리팩터링, 파일 이동, 이름 변경, 저장소 전체 포매팅을 수행하지 않는다. +- 자동 생성 파일을 직접 수정하지 않는다. +- 저장소의 lockfile과 CI 설정에 맞춰 `pnpm`을 사용한다. +- 새로운 production 의존성을 추가하려면 사용자 승인을 먼저 받는다. +- 사용되지 않는 것으로 확인된 의존성은 제거할 수 있으며, `package.json`과 `pnpm-lock.yaml`을 함께 갱신한다. +- 사용자가 요청하지 않은 패키지 버전 업그레이드는 수행하지 않는다. +- `.env*` 파일의 비밀값을 출력하거나 코드와 문서에 포함하거나 커밋하지 않는다. + +## 5. 성능 최적화 제약조건 + +- 화면을 변경하지 않는 범위에서 리소스 발견 시점, 우선순위, 이미지 포맷, 캐시, 번들 경계, 스크립트 실행 시점을 최적화한다. +- Above the Fold 리소스 중 실제 LCP 후보로 확인된 항목만 높은 우선순위를 부여한다. +- Below the Fold 이미지는 지연 로딩을 유지한다. +- CSS로 숨겨진 반응형 이미지까지 preload하거나 eager loading하지 않는다. +- 반응형 이미지 preload의 `srcset`, `sizes`, media query와 실제 이미지 요청 URL이 일치해야 한다. +- 모든 페이지에서 사용하지 않는 리소스 힌트는 해당 라우트에만 적용한다. +- 서드파티 스크립트는 기존 기능과 이벤트 큐가 보존되는 경우에만 지연시킨다. +- 최적화 전 기준값을 기록하고 동일한 라우트, 뷰포트, 네트워크 제한, 캐시 상태에서 결과를 비교한다. +- 성능 목표를 달성하려면 UI 또는 기능 불변 조건을 위반해야 하는 경우, 제약조건을 임의로 완화하지 말고 남은 병목을 사용자에게 보고한다. + +## 6. 필수 검증 + +런타임 코드, 렌더링, 리소스 또는 의존성을 변경한 경우 다음 명령을 실행한다. + +1. `pnpm exec tsc --noEmit` +2. `pnpm run lint` +3. `pnpm run build` + +다음 항목도 확인한다. + +- `pnpm run start`로 로컬 프로덕션 서버를 실행하고 영향받은 흐름을 테스트한다. +- 새로운 브라우저 콘솔 오류, 요청 실패, hydration 경고, 접근성 회귀가 없어야 한다. +- 375px, 768px, 1024px, 1440px에서 변경 전후 화면을 비교한다. +- 문구, 레이아웃, 이미지 크롭, 버튼, 폼, 모달, 지도와 반응형 동작이 동일해야 한다. +- Network 패널에서 preload, 요청 우선순위, 이미지 포맷, 서드파티 로딩 시점이 의도대로 동작하는지 확인한다. + +랜딩 페이지 성능 작업은 cold cache와 Lighthouse Mobile 기본 환경에서 3회 측정하고 중앙값을 기록한다. + +- Performance 점수: 90점 이상 +- LCP: 1.5초 이하 +- TBT: 50ms 이하 +- CLS: 0.01 이하 +- FCP: 기록된 기준값에서 유의미하게 악화되지 않을 것 + +필수 검증이 실패한 상태에서는 작업을 완료한 것으로 간주하지 않는다. 범위 안에서 실패를 해결하거나 정확한 차단 원인을 보고한다. + +## 7. 작업 완료 보고 + +- 변경한 모든 파일을 나열한다. +- 주요 변경 사항의 Before/After 차이를 설명한다. +- 실행한 검증 명령과 결과를 보고한다. +- 수행한 화면 및 기능 검증 내용을 보고한다. +- 성능 작업은 Lighthouse와 네트워크 측정의 변경 전후 결과를 제공한다. +- 실행하지 못한 검증, 남아 있는 위험과 완료를 막은 제약조건을 명시한다. diff --git a/app/globals.css b/app/globals.css index 09468ea..66f7e61 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,6 +1,15 @@ @import 'tailwindcss'; @import 'tw-animate-css'; +@font-face { + font-family: 'Pretendard Fallback'; + src: local('Arial'); + ascent-override: 93.76%; + descent-override: 23.75%; + line-gap-override: 0%; + size-adjust: 101.55%; +} + /* 사용자 정의 컬러 시스템 */ @theme { --color-gray-1: #f2f3f8; @@ -59,6 +68,7 @@ } :root { + --font-pretendard: 'Pretendard', 'Pretendard Fallback'; --radius: 0.625rem; /* 흰색 배경, 검은색 글씨 */ diff --git a/app/layout.tsx b/app/layout.tsx index da93d99..f0ebfe8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,28 +1,12 @@ import './globals.css'; import type { Metadata } from 'next'; import Script from 'next/script'; -import localFont from 'next/font/local'; import Header from '../components/header'; import Footer from '../components/footer'; import GlobalModal from '@/components/modal/globalModal'; import QueryProvider from '@/components/providers/queryProvider'; -import { GoogleTagManager } from '@next/third-parties/google'; -const pretendard = localFont({ - src: [ - { path: '../public/fonts/Pretendard-Thin.woff2', weight: '100' }, - { path: '../public/fonts/Pretendard-ExtraLight.woff2', weight: '200' }, - { path: '../public/fonts/Pretendard-Light.woff2', weight: '300' }, - { path: '../public/fonts/Pretendard-Regular.woff2', weight: '400' }, - { path: '../public/fonts/Pretendard-Medium.woff2', weight: '500' }, - { path: '../public/fonts/Pretendard-SemiBold.woff2', weight: '600' }, - { path: '../public/fonts/Pretendard-Bold.woff2', weight: '700' }, - { path: '../public/fonts/Pretendard-ExtraBold.woff2', weight: '800' }, - { path: '../public/fonts/Pretendard-Black.woff2', weight: '900' }, - ], - variable: '--font-pretendard', - display: 'swap', -}); +const GTM_ID = 'GTM-MSQ45TJD'; export const metadata: Metadata = { metadataBase: new URL(process.env.NEXT_PUBLIC_BASE_URL || 'https://www.mingling.kr'), @@ -37,8 +21,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - +
@@ -46,9 +29,24 @@ export default function RootLayout({