Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .gemini/styleguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 1. Language & Persona (언어 및 페르소나)

- **Language**: You **MUST** write all comments and feedback in **Korean (한국어)**.
- **Persona**: Act as a **Senior Front-end Engineer** with deep knowledge of modern React and Next.js ecosystem. Be precise, insightful, and strict about performance and maintainability.
- **Persona**: Act as a **Senior Front-end Engineer** with deep knowledge of modern React and Next.js ecosystem. Be precise, insightful, and strict about performance, web standards, and maintainability.
- **Tone**: Polite but professional. (정중하되, 문제는 명확하게 지적해주세요.)

## 2. React 19 & Next.js 16 Best Practices
Expand Down Expand Up @@ -57,7 +57,14 @@
## 6. Security & Accessibility (보안 및 접근성)

- **A11y**: 이미지의 `alt` 속성, 아이콘 전용 버튼의 `aria-label`, 키보드 네비게이션을 확인하세요.
- **Semantic HTML**: 적절한 경우 `<div>` 대신 `<button>`, `<article>`, `<section>`, `<nav>` 등 시맨틱 태그 사용을 제안하세요.
- **Input Validation**: 사용자 입력에 대한 클라이언트와 서버 양측 검증이 제대로 되어 있는지 확인하세요.
- **Authentication & Authorization**: 서버 액션이나 API에서 권한 확인이 적절히 이루어지는지 검토하세요.
- **XSS Prevention**: 사용자 입력을 렌더링할 때 적절한 이스케이핑이 되고 있는지 확인하세요.

## 7. Web Standards & SEO (웹 표준 및 검색 엔진 최적화)

- **Semantic Markup**: 단순히 스타일을 위해 `<div>`나 `<span>`을 남용하지 말고, 콘텐츠의 의미에 맞는 태그(`main`, `section`, `article`, `header`, `footer`, `aside`, `nav` 등)를 사용했는지 엄격히 확인하세요.
- **Valid DOM Nesting**: React Hydration Error의 주원인인 '잘못된 태그 중첩'을 지적하세요. (예: `<p>` 태그 안에 `<div>`나 `<ul>` 같은 블록 레벨 요소가 들어가는 것은 웹 표준 위반입니다.)
- **Heading Hierarchy**: 문서의 논리적 구조를 위해 `<h1>`부터 `<h6>`까지의 헤딩 태그가 순차적이고 계층적으로 사용되었는지 확인하세요. (디자인을 위해 태그 순서를 건너뛰면 안 됩니다.)
- **Standard Attributes**: 비표준 속성(Non-standard attributes) 사용을 지양하고, 커스텀 데이터가 필요할 경우 반드시 `data-*` 속성을 사용하도록 안내하세요.
- **Form Standards**: `<form>` 태그 내에서 입력 필드(`input`, `textarea`)는 반드시 `label`과 연결(`for` & `id` 또는 중첩)되어야 하며, 제출 버튼은 `type="submit"`을 명시하는 등 폼 표준을 준수하는지 확인하세요.
7 changes: 4 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.28.1
run_install: false

# 1. 의존성 캐싱 (기존에 있던 것)
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
node-version: "22"
cache: "pnpm"

# 2. Next.js 빌드 캐싱 (새로 추가할 것)
# 주의: pnpm을 쓰시므로 hashFiles 대상을 pnpm-lock.yaml로 변경했습니다.
Expand All @@ -37,4 +38,4 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Run build
run: pnpm build
run: pnpm build
1 change: 1 addition & 0 deletions .github/workflows/lint-checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.28.1
run_install: true

- name: Setup Node
Expand Down
22 changes: 22 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
Binary file added app/fonts/PretendardVariable.woff2
Binary file not shown.
127 changes: 113 additions & 14 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,26 +1,125 @@
@import 'tailwindcss';
@import "tw-animate-css";

:root {
--background: #ffffff;
--foreground: #171717;
}
@custom-variant dark (&:is(.dark *));

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-sans: var(--font-pretendard);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}

@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
47 changes: 31 additions & 16 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
});

const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
const pretendard = localFont({
src: "./fonts/PretendardVariable.woff2",
display: "swap",
weight: "45 920",
variable: "--font-pretendard",
});

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
title: {
default: "코매칭",
template: "%s | 코매칭",
},
description: "대학교 축제에서 운명의 인연을 만나보세요!",
keywords: ["대학축제", "커플매칭", "소개팅", "만남", "대학생", "축제"],
openGraph: {
title: "코매칭 - 대학축제 커플매칭",
description: "대학교 축제에서 운명의 인연을 만나보세요!",
type: "website",
locale: "ko_KR",
},
viewport: {
width: "device-width",
initialScale: 1,
maximumScale: 1,
userScalable: false,
},
Comment on lines +25 to +30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

viewport 메타데이터에 user-scalable=falsemaximum-scale=1이 설정되어 있습니다. 이 설정은 사용자가 페이지를 확대하는 것을 막아, 저시력 사용자 등에게 큰 접근성 문제를 야기합니다. 웹 접근성 표준에 따라 사용자의 확대/축소 기능을 막지 않는 것이 강력히 권장됩니다.

  viewport: {
    width: "device-width",
    initialScale: 1,
  },

};

export default function RootLayout({
Expand All @@ -23,11 +36,13 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="ko" className={pretendard.variable}>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
className={`${pretendard.className} flex justify-center bg-white antialiased md:bg-gray-100`}
>
{children}
<div className="min-h-dvh w-full overflow-x-hidden bg-white px-4 text-black md:max-w-[430px] md:shadow-lg">
{children}
</div>
</body>
</html>
);
Expand Down
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function Home() {
<h1 className="max-w-xs text-3xl leading-10 font-semibold tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
<p className="max-w-md text-lg leading-8 font-[700] text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
Expand Down
22 changes: 22 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components/ui",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
6 changes: 6 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@
"prepare": "husky"
},
"dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.562.0",
"next": "16.1.3",
"react": "19.2.3",
"react-dom": "19.2.3"
"react-dom": "19.2.3",
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
Expand All @@ -29,6 +33,7 @@
"prettier": "^3.8.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5"
},
"lint-staged": {
Expand Down
Loading