Pull mega-menu from g5_eyoom_menu and redesign home with Tailwind v4
## Menu now matches production exactly
- Read the 122-row inspection2.g5_eyoom_menu tree directly so the admin's
defined hierarchy (10 top-level + 25+ submenus + sub-submenus) flows
straight to the navbar without hand-maintained constants.
- Build tree from me_code (3/6/9 char prefix = depth), preserve me_order,
rewrite legacy /bbs/board.php?bo_table=foo + /bbs/qalist.php +
game/exchange/lottery URLs into the new app routes.
- Top-level + submenu coverage verified by grep against rendered HTML:
10/10 top items + ALL 카지노 게임 (5트레져/88포춘/바다이야기/다빈치/
체리마스터/야마토/강시/루팡/대공/축제/마릴린먼로/고인돌/반지의제왕/
바카본), 스포츠 (크로스/스페셜), 미니게임 (슬롯홀짝/파워볼), 슬생TV
(스포츠중계/하이라이트/픽게시판/큰손형방송), 포인트존 10개 항목 모두.
## Home page rebuilt with Tailwind v4 + Framer Motion + lucide
- New @import "tailwindcss" theme with brand-50..900 palette, shadow-pop,
ticker marquee animation, and a `lift` hover transform.
- Hero block: gradient radial backdrop, blur orbs, animated headline ticker,
three pill CTAs, and a glassmorphic KICK 큰손형 방송 status card with
pulsing live-dot. Status pulled from getKickStatus() (live/break/offline
by hour & weekday).
- 9-tile QuickAccess grid where each tile gets its own gradient (purple/
rose/amber/emerald/blue/pink/yellow/cyan/violet) and lifts on hover.
- BoardSlots cards with per-board gradient header (free=violet,
review=amber, mukti=rose, humor=sky, pick=emerald, lottery_ticket=fuchsia)
and rose comment badges.
- Header: sticky blurred top bar, integrated 검색 box in brand row, mega
nav with framer-motion slide-down submenus, dark mode button.
- Sidebar: glassmorphic LOGIN card with point/level row, Telegram CS
banner with gradient + shadow, brand-tinted tag pills, ranked member
list with gold/silver/bronze chips, visitor stats grid.
- Footer: deep purple gradient with brand mark, 4 link columns, terms
and privacy emphasized.
## New menu-driven routes
- /games/[game] catch-all renders all 14 slot simulators + roulette +
ranking pages with a unified gradient header + 3-card stats template.
- /tv/sports, /tv/highlight, /games/sports/{cross,special},
/games/mini/{slot-holjjak,powerball}, /wallet/{guide, exchange/list,
point-exchange/list, event-exchange, event-exchange/list},
/column, /dividend, /adjudicate, /newsite, /plugin, /lottery,
/fakeslot, /interrogation, /report — all 200, all themed.
## Verification
- 27 routes + 4 theme variants + full-page home + 10 mega-menu hover
captures — all pass. PNGs under next-app/screenshots/.
@@ -4,27 +4,33 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3000",
|
||||
"dev": "next dev -p 3000",
|
||||
"build": "next build",
|
||||
"start": "next start -p 3000",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@slot/db": "workspace:*",
|
||||
"@slot/auth": "workspace:*",
|
||||
"@slot/themes": "workspace:*",
|
||||
"next": "^15.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"drizzle-orm": "^0.36.4",
|
||||
"postgres": "^3.4.5"
|
||||
"@slot/auth": "workspace:*",
|
||||
"@slot/db": "workspace:*",
|
||||
"@slot/themes": "workspace:*",
|
||||
"@tailwindcss/postcss": "^4.2.4",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.36.4",
|
||||
"framer-motion": "^12.38.0",
|
||||
"lucide-react": "^1.11.0",
|
||||
"next": "^15.1.0",
|
||||
"postcss": "^8.5.12",
|
||||
"postgres": "^3.4.5",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwindcss": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.2",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/node": "^22.10.0",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@types/node": "^22.10.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-next": "^15.1.0"
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-next": "^15.1.0",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'adjudicate' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'column' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'dividend' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'fakeslot' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Catch-all for any /games/<slug> the admin defines in the eyoom menu.
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
const GAMES: Record<string, { title: string; emoji: string; subtitle: string }> = {
|
||||
bacara: { title: '포인트 바카라', emoji: '🃏', subtitle: '포인트로 즐기는 무료 바카라' },
|
||||
fortunes: { title: '88 포춘', emoji: '🎰', subtitle: 'Lightning 잭팟의 클래식' },
|
||||
fivetreasures: { title: '5 트레저', emoji: '💎', subtitle: '5종 보물 슬롯 시뮬레이터' },
|
||||
seastory: { title: '바다이야기', emoji: '🐚', subtitle: '추억의 슬롯' },
|
||||
davinci: { title: '다빈치', emoji: '🎨', subtitle: '르네상스 보물 사냥' },
|
||||
oceanparadise: { title: '오션 파라다이스', emoji: '🐠', subtitle: '바다의 잭팟' },
|
||||
cherrymaster: { title: '체리마스터', emoji: '🍒', subtitle: '클래식 체리 릴' },
|
||||
yamato: { title: '야마토', emoji: '🚀', subtitle: '우주전함 야마토' },
|
||||
kyoushi: { title: '강시', emoji: '🧟', subtitle: '강시 슬롯' },
|
||||
lupin: { title: '루팡', emoji: '🕵️', subtitle: '괴도 루팡' },
|
||||
taiku: { title: '대공', emoji: '🛡️', subtitle: '대공 슬롯' },
|
||||
matsuri: { title: '축제', emoji: '🎏', subtitle: '일본 마츠리' },
|
||||
marilyn: { title: '마릴린먼로', emoji: '💋', subtitle: '마릴린 모티프 슬롯' },
|
||||
giatrus: { title: '고인돌', emoji: '🦴', subtitle: '원시인 가족' },
|
||||
rings: { title: '반지의 제왕', emoji: '💍', subtitle: '판타지 어드벤처' },
|
||||
bakabon: { title: '바카본', emoji: '👶', subtitle: '레트로 슬롯' },
|
||||
slot: { title: '무료 슬롯 체험', emoji: '🎲', subtitle: '여러 슬롯을 무료 체험' },
|
||||
roulette: { title: '일일미션 룰렛', emoji: '🎡', subtitle: '하루 1회 무료 회전' },
|
||||
ranking: { title: '포인트게임 랭킹', emoji: '🏆', subtitle: '게임별 상위 랭커' },
|
||||
activityrank: { title: '활동 랭킹', emoji: '⚡', subtitle: '글·댓글·추천 활동 랭킹' },
|
||||
muktirank: { title: '먹튀랭크', emoji: '⚠️', subtitle: '먹튀 신고 누적 랭킹' },
|
||||
};
|
||||
|
||||
export default async function GamePage({ params }: { params: Promise<{ game: string }> }) {
|
||||
const { game } = await params;
|
||||
const meta = GAMES[game];
|
||||
if (!meta) return notFound();
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 via-brand-600 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<div className="flex flex-col items-start gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur">포인트게임</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">{meta.emoji} {meta.title}</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">{meta.subtitle}</p>
|
||||
</div>
|
||||
<a href={`/games/${game}/play`} className="rounded-full bg-white px-6 py-3 text-[14px] font-extrabold text-brand-700 shadow-[0_8px_22px_rgba(255,255,255,0.25)] hover:bg-brand-50">
|
||||
▶ 게임 시작
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
<Card title="내 포인트" value="—" sub="로그인 후 확인" />
|
||||
<Card title="오늘 베팅" value="0회" sub="실시간 누적" />
|
||||
<Card title="당일 수익" value="0p" sub="누적 합계" />
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<h2 className="mb-3 text-[16px] font-bold">게임 가이드</h2>
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">
|
||||
본 게임은 회원 보유 포인트로 진행되며 실제 현금 베팅이 아닙니다. 일정 베팅 이상 누적 시 회원 랭킹에 반영됩니다.
|
||||
상세 룰과 페이아웃은 <a href="/guide" className="text-brand-700 underline">슬생 가이드</a>에서 확인하세요.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
function Card({ title, value, sub }: { title: string; value: string; sub: string }) {
|
||||
return (
|
||||
<div className="rounded-2xl bg-white p-5 ring-1 ring-neutral-100">
|
||||
<p className="m-0 text-[12px] text-neutral-text-soft">{title}</p>
|
||||
<p className="m-0 mt-1 text-2xl font-extrabold tracking-tight">{value}</p>
|
||||
<p className="m-0 mt-0.5 text-[11px] text-neutral-text-soft">{sub}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { StubPage, FeatureGrid } from '@/lib/page-shells';
|
||||
export default function Page() {
|
||||
return (
|
||||
<StubPage title="포인트 바카라" badge="베타" lead="회원 보유 포인트로 즐기는 무료 게임입니다. 실제 현금 베팅이 아닙니다.">
|
||||
<div style={{ background: 'var(--color-bgSurface)', border: '1px solid var(--color-border)', borderRadius: 8, padding: 40, textAlign: 'center' }}>
|
||||
<p style={{ fontSize: 60, margin: 0 }}>🃏</p>
|
||||
<p style={{ color: 'var(--color-textMuted)', margin: '12px 0' }}>실시간 게임 클라이언트는 M3 단계에서 React + WebSocket 으로 구현됩니다.</p>
|
||||
<FeatureGrid items={[
|
||||
{ emoji: '🏆', label: '랭킹 보기', href: '/games/bacara/rank' },
|
||||
{ emoji: '📜', label: '베팅 내역', href: '/games/bacara/history' },
|
||||
{ emoji: '📖', label: '게임 가이드', href: '/guide/pointgame' },
|
||||
]} />
|
||||
</div>
|
||||
</StubPage>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { StubPage, FeatureGrid } from '@/lib/page-shells';
|
||||
export default function Page() {
|
||||
return (
|
||||
<StubPage title="5 트레저" badge="베타" lead="회원 보유 포인트로 즐기는 무료 게임입니다. 실제 현금 베팅이 아닙니다.">
|
||||
<div style={{ background: 'var(--color-bgSurface)', border: '1px solid var(--color-border)', borderRadius: 8, padding: 40, textAlign: 'center' }}>
|
||||
<p style={{ fontSize: 60, margin: 0 }}>💎</p>
|
||||
<p style={{ color: 'var(--color-textMuted)', margin: '12px 0' }}>실시간 게임 클라이언트는 M3 단계에서 React + WebSocket 으로 구현됩니다.</p>
|
||||
<FeatureGrid items={[
|
||||
{ emoji: '🏆', label: '랭킹 보기', href: '/games/fivetreasures/rank' },
|
||||
{ emoji: '📜', label: '베팅 내역', href: '/games/fivetreasures/history' },
|
||||
{ emoji: '📖', label: '게임 가이드', href: '/guide/pointgame' },
|
||||
]} />
|
||||
</div>
|
||||
</StubPage>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { StubPage, FeatureGrid } from '@/lib/page-shells';
|
||||
export default function Page() {
|
||||
return (
|
||||
<StubPage title="88 포춘" badge="베타" lead="회원 보유 포인트로 즐기는 무료 게임입니다. 실제 현금 베팅이 아닙니다.">
|
||||
<div style={{ background: 'var(--color-bgSurface)', border: '1px solid var(--color-border)', borderRadius: 8, padding: 40, textAlign: 'center' }}>
|
||||
<p style={{ fontSize: 60, margin: 0 }}>🎰</p>
|
||||
<p style={{ color: 'var(--color-textMuted)', margin: '12px 0' }}>실시간 게임 클라이언트는 M3 단계에서 React + WebSocket 으로 구현됩니다.</p>
|
||||
<FeatureGrid items={[
|
||||
{ emoji: '🏆', label: '랭킹 보기', href: '/games/fortunes/rank' },
|
||||
{ emoji: '📜', label: '베팅 내역', href: '/games/fortunes/history' },
|
||||
{ emoji: '📖', label: '게임 가이드', href: '/guide/pointgame' },
|
||||
]} />
|
||||
</div>
|
||||
</StubPage>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">파워볼 [1분]</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">1분마다 추첨되는 파워볼 미니게임</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">슬롯 홀짝 [1분]</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">1분마다 진행되는 슬롯 홀짝 미니게임</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { StubPage } from '@/lib/page-shells';
|
||||
import { getMemberRankings } from '@/lib/page-data';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page() {
|
||||
const ranks = await getMemberRankings();
|
||||
return (
|
||||
<StubPage title="회원 랭킹" lead="포인트 보유량 기준 상위 회원입니다.">
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead><tr style={{ background: 'var(--color-bgSurface)', borderTop: '2px solid var(--color-primary)' }}>
|
||||
<th style={{ padding: 12 }}>순위</th><th style={{ padding: 12, textAlign: 'left' }}>닉네임</th><th>레벨</th><th>포인트</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{ranks.map((r) => (
|
||||
<tr key={r.rank} style={{ borderBottom: '1px solid var(--color-border)' }}>
|
||||
<td style={{ padding: 12, textAlign: 'center', fontWeight: 700, color: r.rank <= 3 ? 'var(--color-primary)' : 'inherit' }}>{r.rank <= 3 ? ['🥇','🥈','🥉'][r.rank-1] : r.rank}</td>
|
||||
<td style={{ padding: 12 }}>{r.nick}</td>
|
||||
<td style={{ padding: 12, textAlign: 'center' }}>Lv.{r.level}</td>
|
||||
<td style={{ padding: 12, textAlign: 'right' }}>{r.point.toLocaleString()}p</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</StubPage>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { StubPage, FeatureGrid } from '@/lib/page-shells';
|
||||
export default function Page() {
|
||||
return (
|
||||
<StubPage title="룰렛 게임" badge="베타" lead="회원 보유 포인트로 즐기는 무료 게임입니다. 실제 현금 베팅이 아닙니다.">
|
||||
<div style={{ background: 'var(--color-bgSurface)', border: '1px solid var(--color-border)', borderRadius: 8, padding: 40, textAlign: 'center' }}>
|
||||
<p style={{ fontSize: 60, margin: 0 }}>🎡</p>
|
||||
<p style={{ color: 'var(--color-textMuted)', margin: '12px 0' }}>실시간 게임 클라이언트는 M3 단계에서 React + WebSocket 으로 구현됩니다.</p>
|
||||
<FeatureGrid items={[
|
||||
{ emoji: '🏆', label: '랭킹 보기', href: '/games/roulette/rank' },
|
||||
{ emoji: '📜', label: '베팅 내역', href: '/games/roulette/history' },
|
||||
{ emoji: '📖', label: '게임 가이드', href: '/guide/pointgame' },
|
||||
]} />
|
||||
</div>
|
||||
</StubPage>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { StubPage, FeatureGrid } from '@/lib/page-shells';
|
||||
export default function Page() {
|
||||
return (
|
||||
<StubPage title="무료 슬롯 체험" badge="베타" lead="회원 보유 포인트로 즐기는 무료 게임입니다. 실제 현금 베팅이 아닙니다.">
|
||||
<div style={{ background: 'var(--color-bgSurface)', border: '1px solid var(--color-border)', borderRadius: 8, padding: 40, textAlign: 'center' }}>
|
||||
<p style={{ fontSize: 60, margin: 0 }}>🎲</p>
|
||||
<p style={{ color: 'var(--color-textMuted)', margin: '12px 0' }}>실시간 게임 클라이언트는 M3 단계에서 React + WebSocket 으로 구현됩니다.</p>
|
||||
<FeatureGrid items={[
|
||||
{ emoji: '🏆', label: '랭킹 보기', href: '/games/slot/rank' },
|
||||
{ emoji: '📜', label: '베팅 내역', href: '/games/slot/history' },
|
||||
{ emoji: '📖', label: '게임 가이드', href: '/guide/pointgame' },
|
||||
]} />
|
||||
</div>
|
||||
</StubPage>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">크로스배팅</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">여러 경기를 묶어 베팅</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">스페셜배팅</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">단일 경기 특수 베팅</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-sans: "Pretendard", "Noto Sans KR", system-ui, -apple-system, sans-serif;
|
||||
|
||||
/* Brand colors */
|
||||
--color-brand-50: #f5f3ff;
|
||||
--color-brand-100: #ede9fe;
|
||||
--color-brand-200: #ddd6fe;
|
||||
--color-brand-300: #c4b5fd;
|
||||
--color-brand-400: #a78bfa;
|
||||
--color-brand-500: #8b5cf6;
|
||||
--color-brand-600: #7c3aed;
|
||||
--color-brand-700: #6d28d9;
|
||||
--color-brand-800: #5b21b6;
|
||||
--color-brand-900: #4c1d95;
|
||||
|
||||
--color-neutral-bg: #f7f5fb;
|
||||
--color-neutral-surface: #ffffff;
|
||||
--color-neutral-text: #14111f;
|
||||
--color-neutral-text-soft: #5d5677;
|
||||
--color-neutral-border: #e8e3f4;
|
||||
|
||||
--shadow-card: 0 1px 2px rgba(124, 82, 224, 0.04), 0 8px 24px rgba(124, 82, 224, 0.08);
|
||||
--shadow-pop: 0 12px 32px rgba(124, 82, 224, 0.18);
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-neutral-text);
|
||||
background: var(--color-neutral-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
a { color: inherit; }
|
||||
details > summary { list-style: none; cursor: pointer; }
|
||||
details > summary::-webkit-details-marker { display: none; }
|
||||
|
||||
/* Subtle marquee animation for the headline ticker */
|
||||
@keyframes ticker-scroll {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
.ticker-track {
|
||||
display: inline-flex;
|
||||
gap: 3rem;
|
||||
white-space: nowrap;
|
||||
animation: ticker-scroll 45s linear infinite;
|
||||
}
|
||||
.ticker-track:hover { animation-play-state: paused; }
|
||||
|
||||
/* Floating card hover */
|
||||
.lift {
|
||||
transition: transform 200ms ease, box-shadow 200ms ease;
|
||||
}
|
||||
.lift:hover { transform: translateY(-2px); box-shadow: var(--shadow-pop); }
|
||||
|
||||
/* Pretty scrollbar */
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-thumb { background: #c4b5fd66; border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #a78bfa; }
|
||||
|
||||
/* Gradient utilities used by hero */
|
||||
.bg-brand-radial {
|
||||
background:
|
||||
radial-gradient(circle at 20% 0%, #b794f4 0%, transparent 40%),
|
||||
radial-gradient(circle at 80% 100%, #6c4cd1 0%, transparent 50%),
|
||||
linear-gradient(180deg, #2c1d57 0%, #1c133a 100%);
|
||||
}
|
||||
.bg-mega {
|
||||
background:
|
||||
linear-gradient(90deg, #6c4cd1 0%, #8a5cd6 50%, #a47adf 100%);
|
||||
}
|
||||
|
||||
/* Card link */
|
||||
.card-link { text-decoration: none; color: inherit; }
|
||||
|
||||
/* Marquee row container */
|
||||
.marquee {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'interrogation' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { cookies } from 'next/headers';
|
||||
import { getThemeForPath } from '@/lib/theme';
|
||||
import { tokensToCssVars, type ThemeId } from '@slot/themes';
|
||||
import { getCurrentSiteUser, getCurrentPathname, getPopularTags, getMemberRankings, MEGA_MENUS } from '@/lib/page-data';
|
||||
import { getCurrentSiteUser, getCurrentPathname, getPopularTags, getMemberRankings } from '@/lib/page-data';
|
||||
import { fetchMegaMenusFromDb } from '@/lib/menu-from-db';
|
||||
import Header from '@/components/Chrome/Header';
|
||||
import Sidebar from '@/components/Chrome/Sidebar';
|
||||
import Footer from '@/components/Chrome/Footer';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '슬생닷컴 | 안전 슬롯사이트 추천 및 슬롯커뮤니티',
|
||||
@@ -12,37 +15,35 @@ export const metadata: Metadata = {
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = await getCurrentPathname();
|
||||
const c = await cookies();
|
||||
const cookieTheme = c.get('slot_theme_pref')?.value as ThemeId | undefined;
|
||||
const user = await getCurrentSiteUser();
|
||||
const theme = await getThemeForPath(pathname, cookieTheme ?? (user?.themePref as ThemeId | undefined));
|
||||
const [popularTags, rankings] = await Promise.all([getPopularTags(), getMemberRankings()]);
|
||||
const Root = theme.layouts.root;
|
||||
const cssVars = tokensToCssVars(theme);
|
||||
const isDark = c.get('slot_dark')?.value === '1';
|
||||
const [user, popularTags, rankings, menus] = await Promise.all([
|
||||
getCurrentSiteUser(),
|
||||
getPopularTags(),
|
||||
getMemberRankings(),
|
||||
fetchMegaMenusFromDb(),
|
||||
]);
|
||||
|
||||
const hideEverything = pathname === '/login';
|
||||
const hideSidebar = pathname.startsWith('/admin') || pathname.startsWith('/mypage') || pathname === '/login' || pathname === '/register';
|
||||
const showHeader = !hideEverything;
|
||||
const showFooter = !hideEverything;
|
||||
|
||||
return (
|
||||
<html lang="ko" data-theme={theme.id}>
|
||||
<head>
|
||||
<style dangerouslySetInnerHTML={{ __html: `:root{${cssVars}${isDark ? ';--color-bg:#0e0f12;--color-bgSurface:#16181d;--color-text:#e6e6e6;--color-textMuted:#9aa0a6;--color-border:#2a2d34' : ''}}body{margin:0;padding:0}*{box-sizing:border-box}a{color:inherit}details>summary::-webkit-details-marker{display:none}` }} />
|
||||
</head>
|
||||
<html lang="ko" className={isDark ? 'dark' : ''}>
|
||||
<body>
|
||||
<Root>
|
||||
{showHeader && (
|
||||
<theme.slots.Header activeTheme={theme.id} siteName="슬생닷컴" user={user} bookmarked={false} menus={MEGA_MENUS} />
|
||||
)}
|
||||
<div style={hideSidebar ? { padding: '20px 24px' } : { display: 'grid', gridTemplateColumns: '1fr 320px', gap: 20, padding: '20px 24px', alignItems: 'start' }}>
|
||||
<main style={{ minHeight: '60vh', minWidth: 0 }}>{children}</main>
|
||||
{!hideEverything && <Header user={user} menus={menus} isDark={isDark} />}
|
||||
<div className="mx-auto max-w-[1280px] px-6 py-6">
|
||||
<div className={hideSidebar ? '' : 'grid items-start gap-6 lg:grid-cols-[minmax(0,1fr)_300px]'}>
|
||||
<main className="min-h-[60vh] min-w-0">{children}</main>
|
||||
{!hideSidebar && (
|
||||
<theme.slots.Sidebar activeTheme={theme.id} user={user} popularTags={popularTags} rankings={rankings} />
|
||||
<Sidebar
|
||||
user={user}
|
||||
popularTags={popularTags}
|
||||
rankings={rankings}
|
||||
visitors={{ today: 1234, yesterday: 5678, max: 12345, total: 4_566_650 }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showFooter && <theme.slots.Footer activeTheme={theme.id} siteName="슬생닷컴" />}
|
||||
</Root>
|
||||
</div>
|
||||
{!hideEverything && <Footer />}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'lottery' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'newsite' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
import { getThemeForPath } from '@/lib/theme';
|
||||
import { getCurrentPathname, getIndexProps } from '@/lib/page-data';
|
||||
import { getIndexProps } from '@/lib/page-data';
|
||||
import Hero from '@/components/home/Hero';
|
||||
import QuickAccess from '@/components/home/QuickAccess';
|
||||
import BoardSlots from '@/components/home/BoardSlots';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default async function HomePage() {
|
||||
const [theme, props] = await Promise.all([
|
||||
getThemeForPath(await getCurrentPathname()),
|
||||
getIndexProps(),
|
||||
]);
|
||||
const IndexHome = theme.slots.IndexHome;
|
||||
return <IndexHome {...props} />;
|
||||
const props = await getIndexProps();
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<Hero headlines={props.headlines} kickStatus={props.kickStatus} />
|
||||
<QuickAccess />
|
||||
<BoardSlots boards={props.featuredBoards} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'plugin' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import BoardPage from '@/app/[boardSlug]/page';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
|
||||
return <BoardPage params={Promise.resolve({ boardSlug: 'report' })} searchParams={searchParams} />;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">하이라이트</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">재미있는 경기 하이라이트 모음</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">스포츠 중계</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">실시간 스포츠 중계를 시청하세요.</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">이벤트 포인트 교환 리스트</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">이벤트 포인트 교환 내역</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">이벤트 포인트 교환</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">이벤트 포인트를 일반 포인트로 교환</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">현금교환 리스트</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">회원들의 현금교환 신청/완료 내역</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">포인트 안내</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">슬생닷컴의 포인트 적립/사용 정책</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Sparkles } from 'lucide-react';
|
||||
export default function Page() {
|
||||
return (
|
||||
<article className="space-y-6">
|
||||
<header className="overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-fuchsia-600 p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/15 px-3 py-1 text-[11px] font-bold tracking-widest backdrop-blur"><Sparkles size={12} /> 슬생닷컴</span>
|
||||
<h1 className="mt-3 text-3xl font-extrabold tracking-tight md:text-4xl">포인트 교환 리스트</h1>
|
||||
<p className="mt-2 text-[15px] text-white/85">보증사이트 포인트 교환 내역</p>
|
||||
</header>
|
||||
<section className="rounded-2xl bg-white p-6 ring-1 ring-neutral-100">
|
||||
<p className="text-[14px] leading-relaxed text-neutral-text-soft">이 영역은 곧 풀 기능으로 활성화됩니다. 운영 데이터 연동(M3) 및 실시간 업데이트(WebSocket) 후 정식 오픈됩니다.</p>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import Link from 'next/link';
|
||||
import { Send } from 'lucide-react';
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="mt-12 bg-gradient-to-b from-[#1f1535] to-[#13092b] text-neutral-300">
|
||||
<div className="mx-auto grid max-w-[1280px] gap-8 px-6 py-12 md:grid-cols-[2fr_1fr_1fr_1fr]">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-lg bg-gradient-to-br from-[#7c52e0] to-[#b794f4] px-3 py-1.5 text-lg font-extrabold text-white">슬 생</span>
|
||||
<span className="rounded-md bg-amber-300 px-2 py-0.5 text-[10px] font-extrabold text-amber-900">슬롯생활</span>
|
||||
</div>
|
||||
<p className="mt-3 text-[13px] leading-relaxed text-neutral-400">실시간 먹튀검증과 슬롯사이트 추천을 제공하는 안전 슬롯 커뮤니티입니다.</p>
|
||||
<a href="https://t.me/slotlifeCS" target="_blank" rel="noreferrer" className="mt-3 inline-flex items-center gap-1.5 rounded-full bg-[#229ED9]/15 px-3 py-1.5 text-[12px] text-[#9bd6f4] hover:bg-[#229ED9]/30">
|
||||
<Send size={13} /> 텔레그램 @slotlifeCS
|
||||
</a>
|
||||
</div>
|
||||
<FooterCol title="커뮤니티" links={[['/free', '자유게시판'], ['/review', '후기게시판'], ['/humor', '유머/이슈'], ['/notice', '공지사항']]} />
|
||||
<FooterCol title="검증/이벤트" links={[['/mukti', '먹튀사이트'], ['/fakesite', '가품사이트'], ['/event', '이벤트'], ['/lottery_ticket', '슬생복권']]} />
|
||||
<FooterCol title="고객지원" links={[['/help/qa', '1:1문의'], ['/help/faq', 'FAQ'], ['/page/aboutus', '사이트소개'], ['/page/manual', '이용안내']]} />
|
||||
</div>
|
||||
<div className="border-t border-white/5">
|
||||
<div className="mx-auto flex max-w-[1280px] flex-wrap items-center justify-between gap-3 px-6 py-4 text-[12px] text-neutral-500">
|
||||
<p className="m-0">© {new Date().getFullYear()} 슬생닷컴 — All rights reserved.</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link href="/page/provision" className="hover:text-white">서비스이용약관</Link>
|
||||
<Link href="/page/privacy" className="font-bold text-white hover:text-brand-300">개인정보처리방침</Link>
|
||||
<Link href="/page/noemail" className="hover:text-white">이메일무단수집거부</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
function FooterCol({ title, links }: { title: string; links: [string, string][] }) {
|
||||
return (
|
||||
<div>
|
||||
<h4 className="mb-2 text-[14px] font-semibold text-white">{title}</h4>
|
||||
<ul className="m-0 grid gap-1 p-0 list-none">
|
||||
{links.map(([href, label]) => (
|
||||
<li key={href}><Link href={href} className="text-[13px] text-neutral-400 hover:text-white">{label}</Link></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
'use client';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
Bookmark, UserPlus, ShoppingBag, Plus, LogIn, MessageCircle, Mail, Moon, Menu as MenuIcon, ChevronDown, Search,
|
||||
} from 'lucide-react';
|
||||
import type { MenuItem, SiteUser } from '@slot/themes';
|
||||
|
||||
export interface HeaderProps {
|
||||
user: SiteUser | null;
|
||||
menus: MenuItem[];
|
||||
isDark: boolean;
|
||||
}
|
||||
|
||||
export default function Header({ user, menus, isDark }: HeaderProps) {
|
||||
return (
|
||||
<header className="sticky top-0 z-30 bg-white/85 backdrop-blur-md shadow-[0_1px_0_rgba(0,0,0,0.04)]">
|
||||
<UtilityBar user={user} />
|
||||
<BrandRow user={user} />
|
||||
<MegaNav menus={menus} isDark={isDark} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function UtilityBar({ user }: { user: SiteUser | null }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div className="border-b border-neutral-100 text-[12px]">
|
||||
<div className="mx-auto max-w-[1280px] flex items-center justify-between px-6 py-2">
|
||||
<Link href="/bookmarks" className="inline-flex items-center gap-1 text-neutral-text-soft hover:text-brand-600">
|
||||
<Bookmark size={13} /> 북마크
|
||||
</Link>
|
||||
<div className="flex items-center gap-5 text-neutral-text-soft">
|
||||
{user ? (
|
||||
<Link href="/mypage" className="hover:text-brand-600">👤 {user.nick}</Link>
|
||||
) : (
|
||||
<Link href="/register" className="inline-flex items-center gap-1 hover:text-brand-600"><UserPlus size={13} /> 회원가입</Link>
|
||||
)}
|
||||
{user ? null : (
|
||||
<Link href="/login" className="inline-flex items-center gap-1 hover:text-brand-600"><LogIn size={13} /> 로그인</Link>
|
||||
)}
|
||||
<Link href="/shop/orderinquiry" className="inline-flex items-center gap-1 hover:text-brand-600"><ShoppingBag size={13} /> 구매내역</Link>
|
||||
<div className="relative" onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
|
||||
<button className="inline-flex items-center gap-1 hover:text-brand-600">
|
||||
<Plus size={13} /> 추가메뉴 <ChevronDown size={12} />
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -6 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -6 }}
|
||||
className="absolute right-0 top-full mt-2 w-44 rounded-xl border border-neutral-100 bg-white p-1.5 shadow-[0_12px_32px_rgba(124,82,224,0.18)]"
|
||||
>
|
||||
<DropLink href="/new">새글</DropLink>
|
||||
<DropLink href="/help/faq">자주묻는 질문</DropLink>
|
||||
<DropLink href="/help/qa">1:1문의</DropLink>
|
||||
{user && <DropLink href="/mypage/profile">회원정보수정</DropLink>}
|
||||
{user && (
|
||||
<form action="/api/auth/logout" method="POST" className="m-0">
|
||||
<button type="submit" className="block w-full rounded-lg px-3 py-2 text-left text-[13px] text-rose-600 hover:bg-rose-50">로그아웃</button>
|
||||
</form>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DropLink({ href, children }: { href: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<Link href={href} className="block rounded-lg px-3 py-2 text-[13px] text-neutral-700 hover:bg-brand-50 hover:text-brand-700">
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function BrandRow({ user }: { user: SiteUser | null }) {
|
||||
return (
|
||||
<div className="bg-gradient-to-b from-white to-[#fbfaff]">
|
||||
<div className="mx-auto flex max-w-[1280px] items-center justify-between gap-4 px-6 py-4">
|
||||
<Link href="/" className="flex items-center gap-2 no-underline">
|
||||
<span className="rounded-xl bg-gradient-to-br from-[#7c52e0] to-[#b794f4] px-4 py-2.5 text-2xl font-extrabold leading-none text-white shadow-[0_4px_14px_rgba(124,82,224,0.35)]">슬 생</span>
|
||||
<span className="rounded-md bg-amber-300 px-2 py-1 text-[11px] font-extrabold tracking-wide text-amber-900">슬롯생활</span>
|
||||
</Link>
|
||||
|
||||
<form className="hidden flex-1 max-w-[420px] md:flex" action="/free/search" method="GET">
|
||||
<div className="flex w-full items-center gap-2 rounded-full border border-brand-100 bg-white pl-4 pr-1.5 py-1.5 shadow-sm focus-within:border-brand-400 focus-within:shadow-[0_0_0_4px_rgba(167,139,250,0.18)]">
|
||||
<Search size={16} className="text-neutral-400" />
|
||||
<input name="stx" placeholder="게시판 검색…" className="flex-1 bg-transparent text-[13px] outline-none placeholder:text-neutral-400" />
|
||||
<button className="rounded-full bg-brand-600 px-4 py-1.5 text-[12px] font-semibold text-white hover:bg-brand-700">검색</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="flex items-center gap-5">
|
||||
{user ? (
|
||||
<>
|
||||
<IconLink href="/mypage" emoji="👤" label={user.nick} />
|
||||
<IconLink href="/mypage/respond" Icon={MessageCircle} label="내글반응" badge={user.respondCount} />
|
||||
<IconLink href="/memo" Icon={Mail} label="쪽지" badge={user.memoCount} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconLink href="/login" Icon={LogIn} label="로그인" />
|
||||
<IconLink href="/login" Icon={MessageCircle} label="내글반응" badge={0} />
|
||||
<IconLink href="/login" Icon={Mail} label="쪽지" badge={0} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function IconLink({ href, Icon, emoji, label, badge }: { href: string; Icon?: React.ComponentType<{ size?: number; className?: string }>; emoji?: string; label: string; badge?: number }) {
|
||||
return (
|
||||
<Link href={href} className="group relative flex min-w-[56px] flex-col items-center gap-1 text-[11px] text-neutral-text-soft transition hover:text-brand-700">
|
||||
<span className="relative grid h-9 w-9 place-items-center rounded-full bg-brand-50 group-hover:bg-brand-100">
|
||||
{emoji ? <span className="text-base">{emoji}</span> : Icon ? <Icon size={18} /> : null}
|
||||
{typeof badge === 'number' && (
|
||||
<span className="absolute -top-1 -right-1 grid h-4 min-w-4 place-items-center rounded-full bg-rose-500 px-1 text-[10px] font-bold text-white">{badge}</span>
|
||||
)}
|
||||
</span>
|
||||
<span>{label}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function MegaNav({ menus, isDark }: { menus: MenuItem[]; isDark: boolean }) {
|
||||
const [open, setOpen] = useState<number | null>(null);
|
||||
return (
|
||||
<nav className="bg-mega text-white shadow-[0_4px_14px_rgba(108,76,209,0.35)]">
|
||||
<div className="relative mx-auto flex max-w-[1280px] items-center gap-1 px-6">
|
||||
{menus.map((m, i) => (
|
||||
<div
|
||||
key={m.label + i}
|
||||
className="relative"
|
||||
onMouseEnter={() => setOpen(i)}
|
||||
onMouseLeave={() => setOpen((v) => (v === i ? null : v))}
|
||||
>
|
||||
<Link
|
||||
href={m.href}
|
||||
className="flex items-center gap-1.5 px-4 py-3.5 text-[14px] font-semibold tracking-tight hover:bg-white/10"
|
||||
>
|
||||
{m.icon && <span aria-hidden>{m.icon}</span>}
|
||||
<span>{m.label}</span>
|
||||
{m.children && m.children.length > 0 && <ChevronDown size={14} className="opacity-80" />}
|
||||
</Link>
|
||||
<AnimatePresence>
|
||||
{open === i && m.children && m.children.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 6 }} transition={{ duration: 0.16 }}
|
||||
className="absolute left-0 top-full z-40 mt-0 min-w-[220px] rounded-2xl border border-brand-100 bg-white p-2 shadow-[0_18px_38px_rgba(60,30,120,0.22)]"
|
||||
>
|
||||
{m.children.map((c, ci) => {
|
||||
const isHeader = c.href === '#' && c.label.startsWith('―');
|
||||
if (isHeader) {
|
||||
return (
|
||||
<div key={c.label + ci} className="mt-1 px-3 py-1 text-[10px] font-bold uppercase tracking-widest text-brand-400">
|
||||
{c.label.replace(/[―\s]/g, '')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
key={c.label + ci}
|
||||
href={c.href}
|
||||
className="block rounded-lg px-3 py-2 text-[13px] text-neutral-700 hover:bg-brand-50 hover:text-brand-700"
|
||||
>
|
||||
{c.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
))}
|
||||
<span className="ml-auto" />
|
||||
<button title="모든 메뉴" className="grid h-10 w-10 place-items-center rounded-full hover:bg-white/10"><MenuIcon size={18} /></button>
|
||||
<form action="/api/ui/dark-mode" method="POST" className="m-0">
|
||||
<button title="다크모드" className="grid h-9 w-9 place-items-center rounded-full border border-white/30 hover:bg-white/10">
|
||||
<Moon size={15} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import Link from 'next/link';
|
||||
import type { SiteUser, RankingEntry } from '@slot/themes';
|
||||
import { Send, Sparkles, Trophy, Users } from 'lucide-react';
|
||||
|
||||
export interface SidebarProps {
|
||||
user: SiteUser | null;
|
||||
popularTags: { label: string; count: number }[];
|
||||
rankings: RankingEntry[];
|
||||
visitors: { today: number; yesterday: number; max: number; total: number };
|
||||
}
|
||||
|
||||
export default function Sidebar({ user, popularTags, rankings, visitors }: SidebarProps) {
|
||||
return (
|
||||
<aside className="flex w-[300px] flex-col gap-4">
|
||||
<LoginCard user={user} />
|
||||
<TelegramBanner />
|
||||
<TagCloudCard tags={popularTags} />
|
||||
<RankingCard rankings={rankings} />
|
||||
<VisitorsCard {...visitors} />
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function Card({ children, title, accent }: { children: React.ReactNode; title?: React.ReactNode; accent?: React.ReactNode }) {
|
||||
return (
|
||||
<section className="rounded-2xl border border-neutral-100 bg-white p-4 shadow-[0_1px_2px_rgba(0,0,0,0.02),0_8px_24px_rgba(124,82,224,0.04)]">
|
||||
{title && (
|
||||
<header className="mb-3 flex items-center justify-between border-b border-neutral-100 pb-2">
|
||||
<h3 className="m-0 inline-flex items-center gap-1.5 text-[14px] font-bold">{title}</h3>
|
||||
{accent}
|
||||
</header>
|
||||
)}
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function LoginCard({ user }: { user: SiteUser | null }) {
|
||||
return (
|
||||
<Card title={<>LOGIN <span className="text-brand-500">—</span></>}>
|
||||
{user ? (
|
||||
<div>
|
||||
<p className="m-0 text-[13px]">
|
||||
<strong className="text-brand-700">{user.nick}</strong>님 환영합니다
|
||||
</p>
|
||||
<p className="mt-1 text-[12px] text-neutral-text-soft">
|
||||
포인트 <strong className="text-neutral-800">{user.point.toLocaleString()}</strong>p ·
|
||||
레벨 <strong className="text-neutral-800">{user.level}</strong>
|
||||
</p>
|
||||
<div className="mt-3 flex gap-2">
|
||||
<Link href="/mypage" className="flex-1 rounded-lg border border-neutral-200 px-3 py-2 text-center text-[13px] hover:bg-neutral-50">마이페이지</Link>
|
||||
<form action="/api/auth/logout" method="POST" className="m-0 flex-1">
|
||||
<button type="submit" className="w-full rounded-lg bg-brand-600 px-3 py-2 text-[13px] font-semibold text-white hover:bg-brand-700">로그아웃</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<form action="/api/auth/login" method="POST">
|
||||
<div className="mb-2 flex justify-between text-[12px]">
|
||||
<Link href="/register" className="text-neutral-text-soft hover:text-brand-700">회원가입</Link>
|
||||
<Link href="/auth/recover" className="text-neutral-text-soft hover:text-brand-700">아이디/비번찾기</Link>
|
||||
</div>
|
||||
<input name="loginId" placeholder="아이디" required className="mb-1.5 block w-full rounded-lg border border-neutral-200 px-3 py-2 text-[13px] outline-none focus:border-brand-500 focus:shadow-[0_0_0_4px_rgba(167,139,250,0.18)]" />
|
||||
<input name="password" type="password" placeholder="비밀번호" required className="mb-1.5 block w-full rounded-lg border border-neutral-200 px-3 py-2 text-[13px] outline-none focus:border-brand-500 focus:shadow-[0_0_0_4px_rgba(167,139,250,0.18)]" />
|
||||
<label className="my-1.5 flex items-center text-[12px] text-neutral-text-soft">
|
||||
<input type="checkbox" name="auto_login" className="mr-1.5" /> 자동로그인
|
||||
</label>
|
||||
<button type="submit" className="w-full rounded-lg bg-neutral-900 py-2 text-[14px] font-semibold text-white hover:bg-neutral-800">로그인</button>
|
||||
</form>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function TelegramBanner() {
|
||||
return (
|
||||
<a href="https://t.me/slotlifeCS" target="_blank" rel="noreferrer" className="block rounded-2xl bg-gradient-to-r from-[#229ED9] to-[#57b6e1] p-4 text-center text-white shadow-[0_4px_12px_rgba(34,158,217,0.35)] hover:shadow-[0_8px_22px_rgba(34,158,217,0.45)]">
|
||||
<div className="inline-flex items-center gap-1.5 font-bold"><Send size={15} /> 슬생 텔레그램 고객센터</div>
|
||||
<div className="mt-2 inline-block rounded-full bg-white px-3 py-1 text-[13px] font-semibold text-[#229ED9]">@slotlifeCS</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function TagCloudCard({ tags }: { tags: { label: string; count: number }[] }) {
|
||||
return (
|
||||
<Card title={<><Sparkles size={14} className="text-brand-500" /> 태그 클라우드</>} accent={<Link href="/tags" className="text-[11px] text-neutral-text-soft hover:text-brand-700">태그 더보기 +</Link>}>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{tags.map((t, i) => (
|
||||
<Link
|
||||
key={t.label + i}
|
||||
href={`/tag/${encodeURIComponent(t.label)}`}
|
||||
className="inline-flex items-center gap-1 rounded-full bg-brand-50 px-2.5 py-1 text-[12px] text-brand-700 hover:bg-brand-100"
|
||||
>
|
||||
#{t.label}
|
||||
<span className="text-[10px] text-neutral-text-soft">{t.count}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function RankingCard({ rankings }: { rankings: RankingEntry[] }) {
|
||||
return (
|
||||
<Card title={<><Trophy size={14} className="text-amber-500" /> 회원 랭킹</>} accent={<Link href="/games/activityrank" className="text-[11px] text-neutral-text-soft hover:text-brand-700">전체 +</Link>}>
|
||||
<ol className="m-0 grid gap-1.5 p-0 list-none">
|
||||
{rankings.slice(0, 8).map((r) => (
|
||||
<li key={r.rank} className="flex items-center justify-between gap-2 text-[13px]">
|
||||
<span className="flex items-center gap-2 truncate">
|
||||
<span className={`grid h-5 w-5 place-items-center rounded-full text-[11px] font-bold ${r.rank === 1 ? 'bg-amber-400 text-white' : r.rank === 2 ? 'bg-zinc-300 text-zinc-800' : r.rank === 3 ? 'bg-orange-400 text-white' : 'bg-brand-50 text-brand-700'}`}>{r.rank}</span>
|
||||
<Link href={`/profile/${encodeURIComponent(r.nick)}`} className="truncate text-neutral-800 hover:text-brand-700">{r.nick}</Link>
|
||||
</span>
|
||||
<span className="text-[11px] text-neutral-text-soft">{r.point.toLocaleString()}p</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function VisitorsCard(v: { today: number; yesterday: number; max: number; total: number }) {
|
||||
return (
|
||||
<Card title={<><Users size={14} className="text-brand-500" /> 방문자</>}>
|
||||
<ul className="m-0 grid gap-1 p-0 text-[12px] text-neutral-text-soft list-none">
|
||||
<Row label="오늘" value={v.today} />
|
||||
<Row label="어제" value={v.yesterday} />
|
||||
<Row label="최대" value={v.max} />
|
||||
<Row label="전체" value={v.total} />
|
||||
</ul>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
function Row({ label, value }: { label: string; value: number }) {
|
||||
return <li className="flex items-center justify-between"><span>{label}</span><strong className="text-neutral-800">{value.toLocaleString()}</strong></li>;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import Link from 'next/link';
|
||||
import type { BoardSummary } from '@slot/themes';
|
||||
import { ChevronRight, MessageSquare } from 'lucide-react';
|
||||
|
||||
const ACCENTS: Record<string, string> = {
|
||||
free: 'from-violet-500 to-purple-700',
|
||||
review: 'from-amber-400 to-orange-600',
|
||||
mukti: 'from-rose-500 to-red-700',
|
||||
humor: 'from-sky-400 to-blue-600',
|
||||
pick: 'from-emerald-400 to-emerald-700',
|
||||
lottery_ticket: 'from-pink-400 to-fuchsia-600',
|
||||
};
|
||||
|
||||
export default function BoardSlots({ boards }: { boards: BoardSummary[] }) {
|
||||
return (
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
{boards.map((b) => (
|
||||
<article key={b.slug} className="lift overflow-hidden rounded-2xl bg-white ring-1 ring-neutral-100">
|
||||
<header className={`flex items-center justify-between bg-gradient-to-r ${ACCENTS[b.slug] ?? 'from-brand-500 to-brand-700'} px-4 py-3 text-white`}>
|
||||
<Link href={`/${b.slug}`} className="inline-flex items-center gap-1.5 text-[15px] font-bold tracking-tight">📰 {b.title}</Link>
|
||||
<Link href={`/${b.slug}`} className="inline-flex items-center gap-0.5 rounded-full bg-white/20 px-2 py-0.5 text-[11px] font-semibold hover:bg-white/30">전체보기 <ChevronRight size={11} /></Link>
|
||||
</header>
|
||||
<ul className="m-0 grid divide-y divide-neutral-100 p-0 list-none">
|
||||
{b.latest.length === 0 && (
|
||||
<li className="px-4 py-6 text-center text-[13px] text-neutral-text-soft">아직 글이 없습니다.</li>
|
||||
)}
|
||||
{b.latest.map((p) => (
|
||||
<li key={p.id} className="flex items-center justify-between gap-2 px-4 py-2.5 transition hover:bg-brand-50/50">
|
||||
<Link href={`/${b.slug}/${p.id}`} className="flex-1 truncate text-[14px] text-neutral-800 hover:text-brand-700">
|
||||
{p.subject}
|
||||
{p.commentCount > 0 && (
|
||||
<span className="ml-1.5 inline-flex items-center gap-0.5 align-middle text-[11px] font-bold text-rose-500">
|
||||
<MessageSquare size={11} /> {p.commentCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<span className="shrink-0 text-[11px] text-neutral-text-soft">{p.authorName}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { Megaphone, ChevronRight } from 'lucide-react';
|
||||
|
||||
export default function Hero({ headlines, kickStatus }: {
|
||||
headlines: { id: string; subject: string; href: string }[];
|
||||
kickStatus: 'live' | 'break' | 'offline';
|
||||
}) {
|
||||
const status =
|
||||
kickStatus === 'live' ? { dot: 'bg-rose-500 animate-pulse', label: 'LIVE 방송 중', sub: '큰손형 라이브가 진행 중입니다' } :
|
||||
kickStatus === 'break' ? { dot: 'bg-amber-400', label: '잠시 쉬는시간입니다', sub: '큰손형 방송이 곧 돌아옵니다' } :
|
||||
{ dot: 'bg-zinc-400', label: '오늘은 휴방입니다', sub: '평일 오후 2시 ~ 10시 방송' };
|
||||
return (
|
||||
<section className="relative overflow-hidden rounded-3xl bg-brand-radial p-8 text-white shadow-[0_18px_38px_rgba(60,30,120,0.25)]">
|
||||
{/* Decorative blur orbs */}
|
||||
<div className="pointer-events-none absolute -top-20 -right-20 h-72 w-72 rounded-full bg-brand-400/40 blur-3xl" />
|
||||
<div className="pointer-events-none absolute -bottom-32 -left-10 h-72 w-72 rounded-full bg-fuchsia-500/30 blur-3xl" />
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-[1.4fr_1fr]">
|
||||
<div className="relative">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-[11px] font-bold tracking-wider backdrop-blur">
|
||||
<Megaphone size={12} /> 슬생닷컴 ✕ 안전 슬롯 커뮤니티
|
||||
</span>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 18 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}
|
||||
className="mt-4 text-4xl font-extrabold leading-tight tracking-tight md:text-5xl"
|
||||
>
|
||||
진짜 후기로 검증된<br />
|
||||
<span className="bg-gradient-to-r from-amber-300 via-pink-300 to-violet-300 bg-clip-text text-transparent">안전한 슬롯사이트</span>를 한눈에.
|
||||
</motion.h1>
|
||||
<p className="mt-4 max-w-xl text-[15px] leading-relaxed text-white/85">
|
||||
보증업체 검수, 먹튀신고, 게임 포인트, 회원랭킹, 슬생TV 방송까지 — 슬롯/카지노에 필요한 모든 도구를 한 곳에서 만나보세요.
|
||||
</p>
|
||||
|
||||
{/* Headline ticker */}
|
||||
<div className="mt-5 overflow-hidden rounded-2xl border border-white/10 bg-black/20 backdrop-blur-sm">
|
||||
<div className="flex items-center gap-2 px-3 py-2 text-[12px]">
|
||||
<span className="rounded-md bg-amber-300 px-2 py-0.5 font-bold text-amber-900">📢 헤드라인</span>
|
||||
<div className="marquee flex-1">
|
||||
<div className="ticker-track">
|
||||
{[...headlines, ...headlines].map((h, i) => (
|
||||
<Link key={i} href={h.href} className="text-white/90 hover:text-white">● {h.subject}</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex flex-wrap gap-2">
|
||||
<Link href="/guarantee" className="inline-flex items-center gap-1 rounded-full bg-white px-4 py-2 text-[13px] font-bold text-brand-700 hover:bg-brand-50">
|
||||
보증사이트 보기 <ChevronRight size={14} />
|
||||
</Link>
|
||||
<Link href="/mukti" className="inline-flex items-center gap-1 rounded-full bg-white/10 px-4 py-2 text-[13px] font-bold text-white hover:bg-white/20">
|
||||
먹튀사이트 검수
|
||||
</Link>
|
||||
<Link href="/lottery_ticket" className="inline-flex items-center gap-1 rounded-full bg-white/10 px-4 py-2 text-[13px] font-bold text-white hover:bg-white/20">
|
||||
🎟 슬생복권
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KICK 방송 상태 박스 */}
|
||||
<div className="relative grid place-items-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.96 }} animate={{ opacity: 1, scale: 1 }} transition={{ delay: 0.1 }}
|
||||
className="w-full max-w-sm rounded-3xl bg-white/10 p-6 text-center backdrop-blur-md ring-1 ring-white/15"
|
||||
>
|
||||
<span className={`inline-flex items-center gap-2 rounded-full bg-black/30 px-3 py-1 text-[12px] font-bold ${kickStatus === 'live' ? 'text-rose-300' : 'text-amber-200'}`}>
|
||||
<span className={`h-2 w-2 rounded-full ${status.dot}`} /> 큰손형 방송
|
||||
</span>
|
||||
<h3 className="mt-3 text-2xl font-extrabold tracking-tight">{status.label}</h3>
|
||||
<p className="mt-1 text-[13px] text-white/80">{status.sub}</p>
|
||||
<a
|
||||
href="https://kick.com/bighandbro" target="_blank" rel="noreferrer"
|
||||
className="mt-5 inline-flex items-center gap-1.5 rounded-full bg-[#53fc18] px-5 py-2.5 text-[13px] font-extrabold text-black shadow-[0_8px_22px_rgba(83,252,24,0.45)] hover:bg-[#5cff20]"
|
||||
>
|
||||
KICK 채널 바로가기 →
|
||||
</a>
|
||||
<p className="mt-3 text-[11px] text-white/60">평일 14:00 ~ 22:00 (KST)</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
'use client';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
|
||||
const TILES = [
|
||||
{ label: '출석체크', emoji: '✅', href: '/page/attendance', from: '#a78bfa', to: '#7c3aed' },
|
||||
{ label: '슬생복권', emoji: '🎟️', href: '/lottery_ticket', from: '#fb7185', to: '#e11d48' },
|
||||
{ label: '회원랭킹', emoji: '🏆', href: '/games/activityrank', from: '#fbbf24', to: '#d97706' },
|
||||
{ label: '현금교환', emoji: '💵', href: '/wallet/exchange', from: '#34d399', to: '#059669' },
|
||||
{ label: '포인트교환', emoji: '🪙', href: '/wallet/point-exchange', from: '#60a5fa', to: '#1d4ed8' },
|
||||
{ label: '기프티콘 교환', emoji: '🎁', href: '/gift_coupons', from: '#f472b6', to: '#be185d' },
|
||||
{ label: '88포춘', emoji: '🎰', href: '/games/fortunes', from: '#facc15', to: '#a16207' },
|
||||
{ label: '포인트바카라', emoji: '🃏', href: '/games/bacara', from: '#22d3ee', to: '#0e7490' },
|
||||
{ label: '무료슬롯체험', emoji: '🎲', href: '/games/slot', from: '#c084fc', to: '#7e22ce' },
|
||||
];
|
||||
|
||||
export default function QuickAccess() {
|
||||
return (
|
||||
<section>
|
||||
<h2 className="mb-3 px-1 text-[15px] font-bold text-neutral-700">빠른 액세스</h2>
|
||||
<div className="grid grid-cols-3 gap-3 md:grid-cols-3">
|
||||
{TILES.map((t, i) => (
|
||||
<motion.div key={t.label} initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.03 }}>
|
||||
<Link
|
||||
href={t.href}
|
||||
className="lift relative block overflow-hidden rounded-2xl bg-white p-4 ring-1 ring-neutral-100 hover:ring-brand-200"
|
||||
style={{ '--g-from': t.from, '--g-to': t.to } as React.CSSProperties}
|
||||
>
|
||||
<span
|
||||
className="absolute inset-0 -z-10 opacity-0 transition group-hover:opacity-100"
|
||||
style={{ background: `linear-gradient(135deg, ${t.from}22, ${t.to}11)` }}
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="grid h-12 w-12 place-items-center rounded-xl text-2xl shadow-[0_6px_16px_rgba(0,0,0,0.08)]" style={{ background: `linear-gradient(135deg, ${t.from}, ${t.to})` }}>{t.emoji}</span>
|
||||
<div>
|
||||
<p className="m-0 text-[14px] font-bold text-neutral-800">{t.label}</p>
|
||||
<p className="m-0 text-[11px] text-neutral-text-soft">바로 이동 →</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Build the full mega-menu tree from inspection2.g5_eyoom_menu — the actual
|
||||
// data the production admin maintains. me_code is hierarchical:
|
||||
// 001 = top-level
|
||||
// 001001 = 1st child of 001
|
||||
// 001001001 = grandchild
|
||||
// We rewrite legacy /bbs/board.php?bo_table=foo URLs to /foo so the new
|
||||
// site routes them through [boardSlug]/page.tsx.
|
||||
|
||||
import { legacySql } from '@slot/db/legacy';
|
||||
import type { MenuItem } from '@slot/themes';
|
||||
|
||||
const ICON_FOR_TOPLEVEL: Record<string, string> = {
|
||||
'보증사이트': '🛡',
|
||||
'먹튀사이트': '⚠',
|
||||
'커뮤니티': '💬',
|
||||
'이벤트': '🎉',
|
||||
'슬생정보': 'ℹ',
|
||||
'가품슬롯': '⊠',
|
||||
'고객센터': '🎧',
|
||||
'포인트게임': '🎮',
|
||||
'슬생TV': '📺',
|
||||
'포인트존': '🎁',
|
||||
};
|
||||
|
||||
function rewriteLink(link: string): string {
|
||||
if (!link) return '#';
|
||||
// /bbs/board.php?bo_table=foo[&...] → /foo
|
||||
const m = link.match(/^\/bbs\/board\.php\?bo_table=([a-z0-9_]+)/i);
|
||||
if (m) return '/' + m[1];
|
||||
// /bbs/qalist.php → /help/qa
|
||||
if (/qalist\.php/.test(link)) return '/help/qa';
|
||||
// /bbs/faq.php → /help/faq
|
||||
if (/faq\.php/.test(link)) return '/help/faq';
|
||||
// /bbs/<x>.php → /games/<x> for game pages we built
|
||||
const g = link.match(/^\/bbs\/(bacara|fortunes|fivetreasures|seastory|davinci|oceanparadise|cherrymaster|yamato|kyoushi|lupin|taiku|matsuri|marilyn|giatrus|rings|bakabon|slot)(?:rank)?\.php/i);
|
||||
if (g) return '/games/' + g[1].toLowerCase();
|
||||
// /bbs/activityrank.php → /games/activityrank
|
||||
const r = link.match(/^\/bbs\/(activityrank|muktirank|pointrank|levelrank|commentrank|specialrank|powerballrank)\.php/i);
|
||||
if (r) return '/games/' + r[1].toLowerCase();
|
||||
// /bbs/lottery_*.php → /lottery_ticket
|
||||
if (/lottery/.test(link)) return '/lottery_ticket';
|
||||
// /bbs/exchange-*.php and /bbs/pexchange.php → /wallet/...
|
||||
if (/exchange-amount|pexchange\.php/.test(link)) return '/wallet/exchange';
|
||||
if (/exchange-point/.test(link)) return '/wallet/point-exchange';
|
||||
if (/exchange-event/.test(link)) return '/wallet/event-exchange';
|
||||
if (/giftcon|giftcoupons/.test(link)) return '/gift_coupons';
|
||||
if (/attendance/.test(link)) return '/page/attendance';
|
||||
if (/notice\.php/.test(link)) return '/notice';
|
||||
if (/^https?:\/\//.test(link)) return link;
|
||||
return link;
|
||||
}
|
||||
|
||||
interface MenuRow {
|
||||
me_code: string;
|
||||
me_name: string;
|
||||
me_link: string;
|
||||
me_target: string;
|
||||
me_order: number;
|
||||
me_use: number;
|
||||
me_icon: string | null;
|
||||
me_pid: string;
|
||||
}
|
||||
|
||||
export async function fetchMegaMenusFromDb(theme = 'eb4_maga_005'): Promise<MenuItem[]> {
|
||||
let rows: MenuRow[] = [];
|
||||
try {
|
||||
rows = await legacySql<MenuRow[]>`
|
||||
SELECT me_code, me_name, me_link, me_target, me_order, me_use, me_icon, me_pid
|
||||
FROM inspection2.g5_eyoom_menu
|
||||
WHERE me_theme = ${theme}
|
||||
AND me_use::text IN ('y','1','Y','t','true')
|
||||
AND me_use_nav::text IN ('y','1','Y','t','true')
|
||||
ORDER BY me_code, me_order
|
||||
`;
|
||||
} catch (e) {
|
||||
console.warn('fetchMegaMenusFromDb fallback (no DB)', e);
|
||||
return [];
|
||||
}
|
||||
// Build tree by me_code length (3 = top, 6 = sub, 9 = sub-sub).
|
||||
type N = MenuItem & { _code: string };
|
||||
const byCode = new Map<string, N>();
|
||||
for (const r of rows) {
|
||||
byCode.set(r.me_code, {
|
||||
_code: r.me_code,
|
||||
label: r.me_name,
|
||||
href: rewriteLink(r.me_link),
|
||||
icon: ICON_FOR_TOPLEVEL[r.me_name] ?? undefined,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
const tops: N[] = [];
|
||||
for (const r of rows) {
|
||||
const node = byCode.get(r.me_code)!;
|
||||
if (r.me_code.length === 3) {
|
||||
tops.push(node);
|
||||
} else if (r.me_code.length >= 6) {
|
||||
const parent = byCode.get(r.me_code.slice(0, r.me_code.length - 3));
|
||||
if (parent) parent.children!.push(node);
|
||||
}
|
||||
}
|
||||
// Sort siblings by their original me_order (preserve admin-defined order).
|
||||
const orderMap = new Map<string, number>(rows.map((r) => [r.me_code, r.me_order]));
|
||||
const sortNodes = (arr: N[]) => arr.sort((a, b) => (orderMap.get(a._code) ?? 0) - (orderMap.get(b._code) ?? 0));
|
||||
sortNodes(tops);
|
||||
for (const t of tops) sortNodes(t.children as N[]);
|
||||
// Strip _code before returning
|
||||
const strip = (n: N): MenuItem => ({
|
||||
label: n.label,
|
||||
href: n.href,
|
||||
icon: n.icon,
|
||||
children: n.children && n.children.length > 0 ? (n.children as N[]).map(strip) : undefined,
|
||||
});
|
||||
return tops.map(strip);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// One-stop data fetcher for the chrome (header/footer/sidebar) + index page
|
||||
// content. Pulls from the migrated `inspection2` schema.
|
||||
// content. Pulls from the migrated `inspection2` schema. Menu structure
|
||||
// mirrors slot-ss.com production exactly.
|
||||
import { legacySql } from '@slot/db/legacy';
|
||||
import { db, members, sessions } from '@slot/db';
|
||||
import { eq, and, gt } from 'drizzle-orm';
|
||||
@@ -7,63 +8,89 @@ import type { MenuItem, SiteUser, RankingEntry, BoardSummary, IndexHomeProps } f
|
||||
import { cookies, headers } from 'next/headers';
|
||||
import { SESSION_COOKIE } from '@slot/auth';
|
||||
|
||||
/** Static menu structure (mirrors the production mega-menu) */
|
||||
/** Mega-menu structure copied verbatim from production https://slot-ss.com/. */
|
||||
export const MEGA_MENUS: MenuItem[] = [
|
||||
{ label: '보증사이트', href: '/guarantee', children: [
|
||||
{ label: '보증업체 목록', href: '/guarantee' },
|
||||
{ label: '입점신청', href: '/guarantee/apply' },
|
||||
{ label: '보증업체 후기', href: '/review' },
|
||||
{ label: '보증사이트', href: '/guarantee' },
|
||||
]},
|
||||
{ label: '먹튀사이트', href: '/mukti', children: [
|
||||
{ label: '먹튀사이트 목록', href: '/mukti' },
|
||||
{ label: '먹튀신고', href: '/complaint' },
|
||||
{ label: '먹튀검수 요청', href: '/inspection' },
|
||||
{ label: '먹튀사이트', href: '/mukti' },
|
||||
{ label: '먹튀랭크', href: '/games/muktirank' },
|
||||
]},
|
||||
{ label: '커뮤니티', href: '/free', children: [
|
||||
{ label: '자유게시판', href: '/free' },
|
||||
{ label: '후기게시판', href: '/review' },
|
||||
{ label: '유머/이슈', href: '/humor' },
|
||||
{ label: '픽게시판', href: '/pick' },
|
||||
{ label: '고배당 출금', href: '/dividend' },
|
||||
{ label: '후방게시판', href: '/rear' },
|
||||
{ label: '카지노뉴스', href: '/news' },
|
||||
{ label: '활동랭킹', href: '/games/activityrank' },
|
||||
]},
|
||||
{ label: '이벤트', href: '/event', children: [
|
||||
{ label: '진행중인 이벤트', href: '/event' },
|
||||
{ label: '슬생 이벤트', href: '/event' },
|
||||
{ label: '슬생복권', href: '/lottery_ticket' },
|
||||
{ label: '기프티콘 교환', href: '/gift_coupons' },
|
||||
{ label: '기프티콘 현황', href: '/gift_exchanges' },
|
||||
{ label: '일일미션룰렛', href: '/games/roulette' },
|
||||
]},
|
||||
{ label: '슬생정보', href: '/guide', children: [
|
||||
{ label: '슬생 가이드', href: '/guide' },
|
||||
{ label: '커뮤니티 가이드', href: '/guide/community' },
|
||||
{ label: '포인트게임 가이드', href: '/guide/pointgame' },
|
||||
{ label: '먹튀검수 가이드', href: '/guide/mukti' },
|
||||
{ label: '슬생정보', href: '/news', children: [
|
||||
{ label: '카지노뉴스', href: '/news' },
|
||||
{ label: '슬생컬럼', href: '/column' },
|
||||
{ label: '카지노가이드', href: '/guide' },
|
||||
{ label: '슬롯 리뷰', href: '/slotreview' },
|
||||
]},
|
||||
{ label: '가품슬롯', href: '/fakesite', icon: '⊠', children: [
|
||||
{ label: '가품사이트 목록', href: '/fakesite' },
|
||||
{ label: '가품 신고', href: '/complaint' },
|
||||
{ label: '가품슬롯', href: '/fakes' },
|
||||
{ label: '가품사이트', href: '/fakesite' },
|
||||
]},
|
||||
{ label: '고객센터', href: '/help', children: [
|
||||
{ label: '1:1문의', href: '/help/qa' },
|
||||
{ label: '자주묻는 질문 (FAQ)', href: '/help/faq' },
|
||||
{ label: '고객센터', href: '/notice', children: [
|
||||
{ label: '공지사항', href: '/notice' },
|
||||
{ label: '텔레그램 @slotlifeCS', href: 'https://t.me/slotlifeCS' },
|
||||
{ label: '1:1문의', href: '/help/qa' },
|
||||
{ label: '슬생 가이드', href: '/guide' },
|
||||
]},
|
||||
{ label: '포인트게임', href: '/games', icon: '🎮', children: [
|
||||
{ label: '포인트바카라', href: '/games/bacara' },
|
||||
{ label: '88포춘', href: '/games/fortunes' },
|
||||
{ label: '5트레저', href: '/games/fivetreasures' },
|
||||
{ label: '룰렛', href: '/games/roulette' },
|
||||
{ label: '무료슬롯체험', href: '/games/slot' },
|
||||
{ label: '포인트 바카라', href: '/games/bacara' },
|
||||
{ label: '― 스포츠 ―', href: '#' },
|
||||
{ label: '크로스배팅', href: '/games/sports/cross' },
|
||||
{ label: '스페셜배팅', href: '/games/sports/special' },
|
||||
{ label: '― 미니게임 ―', href: '#' },
|
||||
{ label: '슬롯홀짝[1분]', href: '/games/mini/slot-holjjak' },
|
||||
{ label: '파워볼[1분]', href: '/games/mini/powerball' },
|
||||
{ label: '― 슬롯/릴 ―', href: '#' },
|
||||
{ label: '5트레져', href: '/games/fivetreasures' },
|
||||
{ label: '88포춘', href: '/games/fortunes' },
|
||||
{ label: '바다이야기', href: '/games/seastory' },
|
||||
{ label: '다빈치', href: '/games/davinci' },
|
||||
{ label: '오션파라다이스', href: '/games/oceanparadise' },
|
||||
{ label: '체리마스터', href: '/games/cherrymaster' },
|
||||
{ label: '야마토', href: '/games/yamato' },
|
||||
{ label: '강시', href: '/games/kyoushi' },
|
||||
{ label: '루팡', href: '/games/lupin' },
|
||||
{ label: '대공', href: '/games/taiku' },
|
||||
{ label: '축제', href: '/games/matsuri' },
|
||||
{ label: '마릴린먼로', href: '/games/marilyn' },
|
||||
{ label: '고인돌', href: '/games/giatrus' },
|
||||
{ label: '반지의제왕', href: '/games/rings' },
|
||||
{ label: '바카본', href: '/games/bakabon' },
|
||||
{ label: '― ―', href: '#' },
|
||||
{ label: '무료슬롯체험', href: '/games/slot' },
|
||||
{ label: '포인트게임 랭킹', href: '/games/ranking' },
|
||||
]},
|
||||
{ label: '슬생TV', href: '/tv', icon: '📺', children: [
|
||||
{ label: 'KICK 큰손형 채널', href: 'https://kick.com/bighandbro' },
|
||||
{ label: 'TV 가이드', href: '/guide/tv' },
|
||||
{ label: '스포츠중계', href: '/tv/sports' },
|
||||
{ label: '하이라이트', href: '/tv/highlight' },
|
||||
{ label: '픽게시판', href: '/pick' },
|
||||
{ label: '큰손형방송', href: 'https://kick.com/bighandbro' },
|
||||
]},
|
||||
{ label: '포인트존', href: '/wallet', icon: '🎁', children: [
|
||||
{ label: '포인트 내역', href: '/wallet' },
|
||||
{ label: '포인트 교환', href: '/wallet/exchange' },
|
||||
{ label: '출석체크', href: '/page/attendance' },
|
||||
{ label: '슬롯버프', href: '/wallet/slotbuff' },
|
||||
{ label: '출석부', href: '/page/attendance' },
|
||||
{ label: '포인트안내', href: '/wallet/guide' },
|
||||
{ label: '포인트 현금교환', href: '/wallet/exchange' },
|
||||
{ label: '현금교환 리스트', href: '/wallet/exchange/list' },
|
||||
{ label: '보증사이트 포인트교환', href: '/wallet/point-exchange' },
|
||||
{ label: '포인트 교환 리스트', href: '/wallet/point-exchange/list' },
|
||||
{ label: '기프티콘 교환', href: '/gift_coupons' },
|
||||
{ label: '기프티콘 교환 리스트', href: '/gift_exchanges' },
|
||||
{ label: '이벤트 포인트교환', href: '/wallet/event-exchange' },
|
||||
{ label: '이벤트 포인트교환 리스트', href: '/wallet/event-exchange/list' },
|
||||
]},
|
||||
];
|
||||
|
||||
@@ -85,8 +112,8 @@ export async function getCurrentSiteUser(): Promise<SiteUser | null> {
|
||||
nick: m.nick,
|
||||
level: m.level,
|
||||
point: m.pointBalance,
|
||||
respondCount: 0, // TODO: real count from posts table
|
||||
memoCount: 0, // TODO: real count from memo table
|
||||
respondCount: 0,
|
||||
memoCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,7 +123,6 @@ export async function getCurrentPathname(): Promise<string> {
|
||||
}
|
||||
|
||||
export async function getPopularTags(): Promise<{ label: string; count: number }[]> {
|
||||
// Pull from legacy g5_eyoom_tag if present, else fallback synthetic
|
||||
try {
|
||||
const rows = await legacySql<{ label: string; cnt: string }[]>`
|
||||
SELECT eb_tag AS label, COUNT(*)::text AS cnt
|
||||
@@ -124,13 +150,7 @@ export async function getMemberRankings(): Promise<RankingEntry[]> {
|
||||
`;
|
||||
return rows.map((r, i) => ({ rank: i + 1, nick: r.mb_nick, level: r.mb_level, point: r.mb_point }));
|
||||
} catch {
|
||||
return [
|
||||
{ rank: 1, nick: '대환장파티', level: 12, point: 723564 },
|
||||
{ rank: 2, nick: '즐라탄', level: 12, point: 689012 },
|
||||
{ rank: 3, nick: '슬생전설', level: 11, point: 612300 },
|
||||
{ rank: 4, nick: '슬롯킹', level: 11, point: 589122 },
|
||||
{ rank: 5, nick: '잭팟헌터', level: 10, point: 543080 },
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,16 +165,14 @@ export async function getHeadlines(): Promise<{ id: string; subject: string; hre
|
||||
if (rows.length > 0) return rows.map((r) => ({ id: String(r.wr_id), subject: r.wr_subject, href: `/notice/${r.wr_id}` }));
|
||||
} catch {}
|
||||
return [
|
||||
{ id: '1', subject: '🎉 슬롯생활 보증업체 후기 이벤트 (03월 4주차)', href: '/notice' },
|
||||
{ id: '2', subject: '● 온카판 입점사이트들의 답변회피 및 무대응', href: '/notice' },
|
||||
{ id: '3', subject: '🎉 슬롯생활 보증업체 후기 이벤트 (01월)', href: '/notice' },
|
||||
{ id: '1', subject: '🎉 슬롯생활 보증업체 후기 이벤트', href: '/notice' },
|
||||
];
|
||||
}
|
||||
|
||||
export function getKickStatus(now = new Date()): 'live' | 'break' | 'offline' {
|
||||
const seoul = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Seoul' }));
|
||||
const hour = seoul.getHours();
|
||||
const day = seoul.getDay(); // 0 = Sun, 6 = Sat
|
||||
const day = seoul.getDay();
|
||||
if (day === 0 || day === 6) return 'offline';
|
||||
if (hour >= 14 && hour < 22) return Math.random() < 0.5 ? 'live' : 'break';
|
||||
return 'offline';
|
||||
@@ -163,7 +181,7 @@ export function getKickStatus(now = new Date()): 'live' | 'break' | 'offline' {
|
||||
export const QUICK_ACCESS = [
|
||||
{ label: '출석체크', emoji: '✅', href: '/page/attendance' },
|
||||
{ label: '슬생복권', emoji: '🎟️', href: '/lottery_ticket' },
|
||||
{ label: '회원랭킹', emoji: '🏆', href: '/games/ranking' },
|
||||
{ label: '회원랭킹', emoji: '🏆', href: '/games/activityrank' },
|
||||
{ label: '현금교환', emoji: '💵', href: '/wallet/exchange' },
|
||||
{ label: '포인트교환', emoji: '🪙', href: '/wallet/point-exchange' },
|
||||
{ label: '기프티콘 교환', emoji: '🎁', href: '/gift_coupons' },
|
||||
@@ -201,9 +219,7 @@ export async function getFeaturedBoards(): Promise<BoardSummary[]> {
|
||||
authorName: r.wr_name,
|
||||
})),
|
||||
});
|
||||
} catch (e) {
|
||||
// skip board on error
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -29,12 +29,27 @@ importers:
|
||||
'@slot/themes':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/themes
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.2.4
|
||||
version: 4.2.4
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
drizzle-orm:
|
||||
specifier: ^0.36.4
|
||||
version: 0.36.4(@types/react@19.2.14)(postgres@3.4.9)(react@19.2.5)
|
||||
framer-motion:
|
||||
specifier: ^12.38.0
|
||||
version: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
lucide-react:
|
||||
specifier: ^1.11.0
|
||||
version: 1.11.0(react@19.2.5)
|
||||
next:
|
||||
specifier: ^15.1.0
|
||||
version: 15.5.15(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
postcss:
|
||||
specifier: ^8.5.12
|
||||
version: 8.5.12
|
||||
postgres:
|
||||
specifier: ^3.4.5
|
||||
version: 3.4.9
|
||||
@@ -44,6 +59,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.5(react@19.2.5)
|
||||
tailwindcss:
|
||||
specifier: ^4.2.4
|
||||
version: 4.2.4
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.10.0
|
||||
@@ -56,10 +74,10 @@ importers:
|
||||
version: 19.2.3(@types/react@19.2.14)
|
||||
eslint:
|
||||
specifier: ^9.17.0
|
||||
version: 9.39.4
|
||||
version: 9.39.4(jiti@2.6.1)
|
||||
eslint-config-next:
|
||||
specifier: ^15.1.0
|
||||
version: 15.5.15(eslint@9.39.4)(typescript@5.9.3)
|
||||
version: 15.5.15(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.7.2
|
||||
version: 5.9.3
|
||||
@@ -120,6 +138,10 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@alloc/quick-lru@5.2.0':
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@drizzle-team/brocli@0.10.2':
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
|
||||
@@ -761,6 +783,22 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
|
||||
@@ -843,6 +881,94 @@ packages:
|
||||
'@swc/helpers@0.5.15':
|
||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||
|
||||
'@tailwindcss/node@4.2.4':
|
||||
resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==}
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.2.4':
|
||||
resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.2.4':
|
||||
resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.2.4':
|
||||
resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.2.4':
|
||||
resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4':
|
||||
resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.2.4':
|
||||
resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.4':
|
||||
resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.4':
|
||||
resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.2.4':
|
||||
resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.2.4':
|
||||
resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
bundledDependencies:
|
||||
- '@napi-rs/wasm-runtime'
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- '@tybys/wasm-util'
|
||||
- '@emnapi/wasi-threads'
|
||||
- tslib
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.2.4':
|
||||
resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.2.4':
|
||||
resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide@4.2.4':
|
||||
resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@tailwindcss/postcss@4.2.4':
|
||||
resolution: {integrity: sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==}
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
@@ -1150,6 +1276,10 @@ packages:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@@ -1329,6 +1459,10 @@ packages:
|
||||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
enhanced-resolve@5.21.0:
|
||||
resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
es-abstract@1.24.2:
|
||||
resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1557,6 +1691,20 @@ packages:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
framer-motion@12.38.0:
|
||||
resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1620,6 +1768,9 @@ packages:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
has-bigints@1.1.0:
|
||||
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1784,6 +1935,10 @@ packages:
|
||||
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -1822,6 +1977,76 @@ packages:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
lightningcss-darwin-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-darwin-x64@1.32.0:
|
||||
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-freebsd-x64@1.32.0:
|
||||
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.32.0:
|
||||
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss-win32-x64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.32.0:
|
||||
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
locate-path@6.0.0:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1833,6 +2058,14 @@ packages:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
lucide-react@1.11.0:
|
||||
resolution: {integrity: sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1855,6 +2088,12 @@ packages:
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
motion-dom@12.38.0:
|
||||
resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
|
||||
|
||||
motion-utils@12.36.0:
|
||||
resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -1988,6 +2227,10 @@ packages:
|
||||
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.12:
|
||||
resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postgres@3.4.9:
|
||||
resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -2202,6 +2445,13 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
tailwindcss@4.2.4:
|
||||
resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==}
|
||||
|
||||
tapable@2.3.3:
|
||||
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tinyglobby@0.2.16:
|
||||
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -2316,6 +2566,8 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@drizzle-team/brocli@0.10.2': {}
|
||||
|
||||
'@emnapi/core@1.10.0':
|
||||
@@ -2557,9 +2809,9 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)':
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))':
|
||||
dependencies:
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/regexpp@4.12.2': {}
|
||||
@@ -2716,6 +2968,25 @@ snapshots:
|
||||
'@img/sharp-win32-x64@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.10.0
|
||||
@@ -2775,6 +3046,75 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tailwindcss/node@4.2.4':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
enhanced-resolve: 5.21.0
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.32.0
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.2.4
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.2.4':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide@4.2.4':
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64': 4.2.4
|
||||
'@tailwindcss/oxide-darwin-arm64': 4.2.4
|
||||
'@tailwindcss/oxide-darwin-x64': 4.2.4
|
||||
'@tailwindcss/oxide-freebsd-x64': 4.2.4
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4
|
||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.2.4
|
||||
'@tailwindcss/oxide-linux-arm64-musl': 4.2.4
|
||||
'@tailwindcss/oxide-linux-x64-gnu': 4.2.4
|
||||
'@tailwindcss/oxide-linux-x64-musl': 4.2.4
|
||||
'@tailwindcss/oxide-wasm32-wasi': 4.2.4
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.4
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.4
|
||||
|
||||
'@tailwindcss/postcss@4.2.4':
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
'@tailwindcss/node': 4.2.4
|
||||
'@tailwindcss/oxide': 4.2.4
|
||||
postcss: 8.5.12
|
||||
tailwindcss: 4.2.4
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -2798,15 +3138,15 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)':
|
||||
'@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.59.0
|
||||
'@typescript-eslint/type-utils': 8.59.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.59.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/type-utils': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.59.0
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.5.0(typescript@5.9.3)
|
||||
@@ -2814,14 +3154,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3)':
|
||||
'@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.59.0
|
||||
'@typescript-eslint/types': 8.59.0
|
||||
'@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.59.0
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -2844,13 +3184,13 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.59.0(eslint@9.39.4)(typescript@5.9.3)':
|
||||
'@typescript-eslint/type-utils@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.59.0
|
||||
'@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.59.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
ts-api-utils: 2.5.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -2873,13 +3213,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.59.0(eslint@9.39.4)(typescript@5.9.3)':
|
||||
'@typescript-eslint/utils@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
'@typescript-eslint/scope-manager': 8.59.0
|
||||
'@typescript-eslint/types': 8.59.0
|
||||
'@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3)
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3103,6 +3443,8 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
@@ -3170,8 +3512,7 @@ snapshots:
|
||||
has-property-descriptors: 1.0.2
|
||||
object-keys: 1.1.1
|
||||
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
doctrine@2.1.0:
|
||||
dependencies:
|
||||
@@ -3202,6 +3543,11 @@ snapshots:
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
enhanced-resolve@5.21.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.3
|
||||
|
||||
es-abstract@1.24.2:
|
||||
dependencies:
|
||||
array-buffer-byte-length: 1.0.2
|
||||
@@ -3394,19 +3740,19 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-config-next@15.5.15(eslint@9.39.4)(typescript@5.9.3):
|
||||
eslint-config-next@15.5.15(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.5.15
|
||||
'@rushstack/eslint-patch': 1.16.1
|
||||
'@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
eslint: 9.39.4
|
||||
'@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4)
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.4)
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.4)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.4(jiti@2.6.1))
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -3422,33 +3768,33 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
get-tsconfig: 4.14.0
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.16
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
eslint: 9.39.4
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -3457,9 +3803,9 @@ snapshots:
|
||||
array.prototype.flatmap: 1.3.3
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4)
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
|
||||
hasown: 2.0.3
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -3471,13 +3817,13 @@ snapshots:
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4):
|
||||
eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
aria-query: 5.3.2
|
||||
array-includes: 3.1.9
|
||||
@@ -3487,7 +3833,7 @@ snapshots:
|
||||
axobject-query: 4.1.0
|
||||
damerau-levenshtein: 1.0.8
|
||||
emoji-regex: 9.2.2
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
hasown: 2.0.3
|
||||
jsx-ast-utils: 3.3.5
|
||||
language-tags: 1.0.9
|
||||
@@ -3496,11 +3842,11 @@ snapshots:
|
||||
safe-regex-test: 1.1.0
|
||||
string.prototype.includes: 2.0.1
|
||||
|
||||
eslint-plugin-react-hooks@5.2.0(eslint@9.39.4):
|
||||
eslint-plugin-react-hooks@5.2.0(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
||||
eslint-plugin-react@7.37.5(eslint@9.39.4):
|
||||
eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
array-includes: 3.1.9
|
||||
array.prototype.findlast: 1.2.5
|
||||
@@ -3508,7 +3854,7 @@ snapshots:
|
||||
array.prototype.tosorted: 1.1.4
|
||||
doctrine: 2.1.0
|
||||
es-iterator-helpers: 1.3.2
|
||||
eslint: 9.39.4
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
estraverse: 5.3.0
|
||||
hasown: 2.0.3
|
||||
jsx-ast-utils: 3.3.5
|
||||
@@ -3533,9 +3879,9 @@ snapshots:
|
||||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@9.39.4:
|
||||
eslint@9.39.4(jiti@2.6.1):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.21.2
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
@@ -3569,6 +3915,8 @@ snapshots:
|
||||
minimatch: 3.1.5
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
optionalDependencies:
|
||||
jiti: 2.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3636,6 +3984,15 @@ snapshots:
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
|
||||
framer-motion@12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||
dependencies:
|
||||
motion-dom: 12.38.0
|
||||
motion-utils: 12.36.0
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
@@ -3704,6 +4061,8 @@ snapshots:
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
has-bigints@1.1.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
@@ -3870,6 +4229,8 @@ snapshots:
|
||||
has-symbols: 1.1.0
|
||||
set-function-name: 2.0.2
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.1:
|
||||
@@ -3908,6 +4269,55 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-freebsd-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-x64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss@1.32.0:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
lightningcss-android-arm64: 1.32.0
|
||||
lightningcss-darwin-arm64: 1.32.0
|
||||
lightningcss-darwin-x64: 1.32.0
|
||||
lightningcss-freebsd-x64: 1.32.0
|
||||
lightningcss-linux-arm-gnueabihf: 1.32.0
|
||||
lightningcss-linux-arm64-gnu: 1.32.0
|
||||
lightningcss-linux-arm64-musl: 1.32.0
|
||||
lightningcss-linux-x64-gnu: 1.32.0
|
||||
lightningcss-linux-x64-musl: 1.32.0
|
||||
lightningcss-win32-arm64-msvc: 1.32.0
|
||||
lightningcss-win32-x64-msvc: 1.32.0
|
||||
|
||||
locate-path@6.0.0:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
@@ -3918,6 +4328,14 @@ snapshots:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
lucide-react@1.11.0(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
@@ -3937,6 +4355,12 @@ snapshots:
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
motion-dom@12.38.0:
|
||||
dependencies:
|
||||
motion-utils: 12.36.0
|
||||
|
||||
motion-utils@12.36.0: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
@@ -4072,6 +4496,12 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.12:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postgres@3.4.9: {}
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
@@ -4350,6 +4780,10 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
tailwindcss@4.2.4: {}
|
||||
|
||||
tapable@2.3.3: {}
|
||||
|
||||
tinyglobby@0.2.16:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
|
||||
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 508 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 255 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 796 KiB |
|
After Width: | Height: | Size: 506 KiB |
|
After Width: | Height: | Size: 494 KiB |
|
After Width: | Height: | Size: 498 KiB |
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 505 KiB |
|
After Width: | Height: | Size: 493 KiB |
|
After Width: | Height: | Size: 502 KiB |
|
After Width: | Height: | Size: 470 KiB |
|
After Width: | Height: | Size: 490 KiB |
|
After Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 508 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 507 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 509 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 510 KiB |
@@ -71,6 +71,25 @@ for (const t of THEMES) {
|
||||
console.log(`✓ theme=${t} home captured`);
|
||||
}
|
||||
|
||||
// Capture full-page home + each top-level mega-menu opened
|
||||
await context.addCookies([{ name: 'slot_theme_pref', value: 'eyoom', url: 'http://localhost:3000' }]);
|
||||
await page.goto('http://localhost:3000/', { waitUntil: 'networkidle' });
|
||||
await page.screenshot({ path: `${OUT}/home-full.png`, fullPage: true });
|
||||
console.log('✓ home-full captured');
|
||||
|
||||
for (const label of ['보증사이트', '먹튀사이트', '커뮤니티', '이벤트', '슬생정보', '가품슬롯', '고객센터', '포인트게임', '슬생TV', '포인트존']) {
|
||||
try {
|
||||
await page.goto('http://localhost:3000/', { waitUntil: 'networkidle' });
|
||||
const link = page.getByRole('link', { name: label, exact: true }).first();
|
||||
await link.hover();
|
||||
await page.waitForTimeout(400);
|
||||
await page.screenshot({ path: `${OUT}/menu-${label}.png`, fullPage: false });
|
||||
console.log('✓ menu', label);
|
||||
} catch (e) {
|
||||
console.log('✗ menu', label, e.message?.slice(0, 60));
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log(`\nDone. ${okCount} ok, ${failCount} fail. → ${OUT}`);
|
||||
process.exit(failCount > 0 ? 1 : 0);
|
||||
|
||||