macbook 에서 리눅스로 이동

This commit is contained in:
k3341095
2026-03-05 10:35:28 +09:00
commit ffd13c0fbb
83 changed files with 12262 additions and 0 deletions

5
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
.env
.env.local
node_modules/
*.db

15
backend/README.md Normal file
View File

@@ -0,0 +1,15 @@
# backend
# Setup
Follow these steps to run [Elysia.js](https://elysiajs.com) under [Bun](https://bun.sh):
1. Download packages
```bash
bun install
```
2. You're ready to go!
```bash
bun run main.ts
```

24
backend/main.ts Normal file
View File

@@ -0,0 +1,24 @@
import { Elysia } from "elysia";
import cors from "@elysiajs/cors";
const app = new Elysia().use(
cors({
origin: ["http://127.0.0.1:3000", "http://localhost:3000"],
credentials: true,
}),
);
app.get("/", () => ({
service: "landing-backend",
status: "ok",
}));
app.get("/health", () => ({
status: "ok",
}));
const port = Number(process.env.PORT ?? 4000);
const hostname = process.env.HOST ?? "127.0.0.1";
const server = app.listen({ port, hostname });
console.log(`Backend listening on ${server.server!.url}`);

18
backend/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "backend",
"type": "module",
"scripts": {
"dev": "bun run main.ts",
"start": "bun run main.ts",
"typecheck": "tsc"
},
"packageManager": "bun@1.3.9",
"devDependencies": {
"@types/bun": "1",
"typescript": "5"
},
"dependencies": {
"@elysiajs/cors": "^1.2.1",
"elysia": "1"
}
}

20
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"lib": [
"ESNext"
],
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"noUncheckedIndexedAccess": true,
"noEmit": true,
"types": [
"bun-types" // add Bun global
]
}
}

31
docs/api-spec.md Normal file
View File

@@ -0,0 +1,31 @@
# API 계약(1차)
## 공개 API
- `GET /api/public/route-by-host`
- Query: `host`, `path`
- Response:
- `status`: `ok | not_found`
- `page`: block json
- `campaignId`, `pageId`, `routeId`
- `POST /api/public/lead`
- Body: `campaignId`, `pageId`, `routeId`, `payload`, `sourceMeta`
## 관리자 API
- 인증: `POST /api/admin/auth/login` (현재는 역할 기반 데모 토큰 반환)
- 캠페인: `GET /api/admin/campaigns`, `POST /api/admin/campaigns`, `PUT /api/admin/campaigns/:id`
- 페이지: `GET /api/admin/pages`, `GET /api/admin/pages/:id`, `POST /api/admin/pages`, `PUT /api/admin/pages/:id`
- 라우트: `GET /api/admin/pages/:id/routes`, `POST /api/admin/pages/:id/routes`
- 조건: `GET /api/admin/pages/:id/conditions`, `POST /api/admin/pages/:id/conditions`, `PATCH /api/admin/pages/:id/conditions/:conditionId`, `DELETE /api/admin/pages/:id/conditions/:conditionId`
- 리드: `GET /api/admin/leads`
- 사용자: `GET /api/admin/users`, `POST /api/admin/users`, `PUT /api/admin/users/:id/role`
## 역할/권한(1차)
- 헤더: `x-user-role`
- 값: `ADMIN`, `LEAD_MANAGER`
- `ADMIN`: 관리자 API 전체 사용
- `LEAD_MANAGER`: 리드 조회(`/api/admin/leads`)만 사용
## 공통 에러
- 401 `{ error: "unauthorized" }`
- 403 `{ error: "forbidden" }`
- 422 `{ error: "validation", details: [...] }`

137
docs/database.md Normal file
View File

@@ -0,0 +1,137 @@
# DB 설계(1차)
## 핵심 엔티티
- Campaign(종목), LandingPage, LandingRoute, RouteCondition, Lead, User
- 역할: ADMIN / LEAD_MANAGER
## 엔티티 관계
- Campaign 1:N LandingPage
- Campaign 1:N LandingRoute
- LandingRoute 1:N RouteCondition
- LandingRoute N:1 LandingPage(기본 페이지)
- RouteCondition N:1 LandingPage(조건 매칭 페이지)
- LandingPage 1:N Lead
- Campaign 1:N Lead
## 권한 정책
- ADMIN: 모든 관리자 기능
- LEAD_MANAGER: 리드 조회만 가능
## Prisma 스키마 (SQLite 기준)
```prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
enum UserRole {
ADMIN
LEAD_MANAGER
}
model Campaign {
id String @id @default(cuid())
name String
description String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
landingPages LandingPage[]
routes LandingRoute[]
leads Lead[]
}
model LandingPage {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
name String
slug String?
blocks Json
isDefault Boolean @default(false)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
defaultRoutes LandingRoute[] @relation("RouteDefaultPage")
conditions RouteCondition[]
leads Lead[]
@@index([campaignId, isActive])
}
model LandingRoute {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
host String
path String
defaultPageId String
defaultPage LandingPage @relation("RouteDefaultPage", fields: [defaultPageId], references: [id], onDelete: Restrict)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
conditions RouteCondition[]
leads Lead[]
@@unique([host, path])
}
model RouteCondition {
id String @id @default(cuid())
routeId String
route LandingRoute @relation(fields: [routeId], references: [id], onDelete: Cascade)
pageId String
page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict)
label String
priority Int @default(0)
isActive Boolean @default(true)
startDate DateTime?
endDate DateTime?
timezone String @default("Asia/Seoul")
weekMask String @default("0000000")
startMinute Int?
endMinute Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Lead {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
pageId String
page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict)
routeId String?
route LandingRoute? @relation(fields: [routeId], references: [id], onDelete: SetNull)
payload Json
sourceMeta Json?
submittedAt DateTime @default(now())
@@index([campaignId, submittedAt])
}
model User {
id String @id @default(cuid())
email String @unique
name String?
password String
role UserRole @default(ADMIN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
### 참고
- `defaultRoutes``@relation("RouteDefaultPage")`로 라우트 기본 페이지 역참조입니다.
- 동일 엔티티에서 기본 라우트를 찾을 때는 `LandingRoute.defaultPageId`가 FK입니다.
- PostgreSQL 전환 시 datasource `provider`만 교체하지 않고 nullable/시간대 처리 포함 마이그레이션 검증이 필요하다.

View File

@@ -0,0 +1,29 @@
# 라우팅/조건 매칭(1차: exact)
## 라우팅 기본값
- `host + path` exact 매칭(정확 일치) 사용
- 기본 도메인: `aaa.com`
- 기본 라우트 path: `/`
- 예시: `aaa.com` + `/` , `aaa.com/google`
## 렌더 우선순위
1. `landingRoute` 조회 (`host`, `path`, `isActive`)
2. 해당 route의 `RouteCondition` 중 active 조건만 필터
3. 요일/시간/날짜 조건 일치 검사
4. `priority desc`, `updatedAt desc`로 정렬 후 첫 번째 사용
5. 조건 미매칭 시 기본 페이지 사용
## 호스트 정규화
- 요청 host는 소문자 변환, `www.` 제거, 포트(`:4000`, `:3000`) 분리 후 비교
- path는 정확 일치(`exact`) 방식
## 조건 스펙
- weekday: `weekMask` 7자리 문자열(일~토: `sun`~`sat`)에서 1이면 매칭
- time: `startMinute`~`endMinute` (0~1439)
- `start <= end` : 당일 범위
- `start > end` : 자정을 넘는 범위(예: 20:00~06:00)
- date: `startDate`, `endDate`
## 블록 정책(빌더)
- 블록형 구성 + 드래그 정렬
- 카카오 버튼 블록은 `kakaoSyncCode`를 페이지 블록 설정에 저장

37
docs/strategy.md Normal file
View File

@@ -0,0 +1,37 @@
# 랜딩 페이지 SaaS 전략 개요
작성일: 2026-03-01
## 제품 방향
- 광고 운영자가 랜딩 페이지를 빠르게 생성·수정·배포할 수 있는 SaaS
- 핵심 가치: 빠른 제작 + 안정적인 발행 + 팀 운영 + 리드 관리
## 기술 스택
- Nuxt 4 + Nuxt UI + Pinia
- Elysia (API)
- Prisma + SQLite(개발) + PostgreSQL(운영 전환 예정)
- Bun
## MVP 우선순위
1. 사용자/역할 관리
2. 캠페인/페이지 CRUD
3. 블록형 빌더
4. 도메인+경로 라우팅
5. 조건 탭(요일/시간) 기반 페이지 분기
6. 리드 조회
## 추후 확장(현재 1차 제외)
- 버전관리/롤백
- 예약 발행
- 내부 분석 대시보드
- 결제/구독
- 다국어/SEO 고도화
## 개발 규칙
- TypeScript strict
- 공식 생성 방식 준수
- Nuxt: 공식 nuxi 초기화/디렉토리
- Elysia: 공식 앱 초기화/미들웨어 등록
- Prisma: `prisma init` + schema + migrate
- Bun: 공식 실행 패턴

31
docs/ui-flow.md Normal file
View File

@@ -0,0 +1,31 @@
# 화면/라우트 설계(1차)
## 공개 라우트
- `/{path?}`: 공개 랜딩 진입점(호스트+경로로 렌더 대상 결정)
- `/_lead/success`, `/_lead/error`: 제출 결과 화면
- `/admin`: 관리자 홈
## 관리자 라우트(현재 구현)
- `/admin/login`: 역할 선택 로그인(데모 토큰)
- `/admin`: 대시보드
- `/admin/campaigns`: 캠페인 목록/생성
- `/admin/pages`: 페이지 목록/생성
- `/admin/pages/[id]/builder`: 페이지 블록 빌더
- `/admin/pages/[id]/routes`: 도메인 라우트 관리
- `/admin/pages/[id]/conditions`: 요일/시간 조건 관리
- `/admin/leads`: 리드 조회
- `/admin/builder/[id]`: 과거 호환용 리다이렉트 경로
## 1차 최소 화면
- 로그인
- 대시보드
- 캠페인 목록/생성
- 페이지 목록/생성/빌더 연결
- 리드 조회
## 다음 구현 우선순위
- 공개 노출 라우트(이미지/폼/버튼/푸터 블록 렌더)
- 블록 기반 랜딩 폼 제출 처리 개선(검증, 중복 제출)
- 조건 탭(요일/시간/기간 기반 룰 편집) 품질 개선
- 라우트/도메인 매핑 UI 고도화
- 사용자/역할 관리(리드관리자 역할 제한)

13
frontend/.editorconfig Executable file
View File

@@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

34
frontend/.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: ci
on: push
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [22]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm run lint
- name: Typecheck
run: pnpm run typecheck

24
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

21
frontend/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Nuxt UI Templates
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

60
frontend/README.md Normal file
View File

@@ -0,0 +1,60 @@
# Nuxt Starter Template
[![Nuxt UI](https://img.shields.io/badge/Made%20with-Nuxt%20UI-00DC82?logo=nuxt&labelColor=020420)](https://ui.nuxt.com)
Use this template to get started with [Nuxt UI](https://ui.nuxt.com) quickly.
- [Live demo](https://starter-template.nuxt.dev/)
- [Documentation](https://ui.nuxt.com/docs/getting-started/installation/nuxt)
<a href="https://starter-template.nuxt.dev/" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://ui.nuxt.com/assets/templates/nuxt/starter-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://ui.nuxt.com/assets/templates/nuxt/starter-light.png">
<img alt="Nuxt Starter Template" src="https://ui.nuxt.com/assets/templates/nuxt/starter-light.png" width="830" height="466">
</picture>
</a>
> The starter template for Vue is on https://github.com/nuxt-ui-templates/starter-vue.
## Quick Start
```bash [Terminal]
npm create nuxt@latest -- -t github:nuxt-ui-templates/starter
```
## Deploy your own
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-name=starter&repository-url=https%3A%2F%2Fgithub.com%2Fnuxt-ui-templates%2Fstarter&demo-image=https%3A%2F%2Fui.nuxt.com%2Fassets%2Ftemplates%2Fnuxt%2Fstarter-dark.png&demo-url=https%3A%2F%2Fstarter-template.nuxt.dev%2F&demo-title=Nuxt%20Starter%20Template&demo-description=A%20minimal%20template%20to%20get%20started%20with%20Nuxt%20UI.)
## Setup
Make sure to install the dependencies:
```bash
pnpm install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
pnpm dev
```
## Production
Build the application for production:
```bash
pnpm build
```
Locally preview production build:
```bash
pnpm preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

3
frontend/app/app.vue Normal file
View File

@@ -0,0 +1,3 @@
<template>
<NuxtPage />
</template>

View File

@@ -0,0 +1,17 @@
@import "tailwindcss";
@source "../..";
@theme static {
--font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
@layer base {
body {
margin: 0;
min-height: 100vh;
font-family: var(--font-sans);
background: #020617;
color: #e2e8f0;
}
}

View File

@@ -0,0 +1,151 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { type CampaignLabel } from '~/stores/pages'
interface Props {
campaigns: CampaignLabel[]
}
interface Emits {
(e: 'close'): void
(e: 'submit', payload: { name: string; campaignId: string; domain: string; routePath: string }): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const form = ref({
name: '메인 랜딩 페이지',
campaignId: '',
domain: 'ad-camp-temp.test',
routePath: '/'
})
const campaignChoices = computed(() =>
props.campaigns.map((campaign) => ({
value: campaign.id,
label: campaign.name
}))
)
if (campaignChoices.value.length > 0) {
form.value.campaignId = campaignChoices.value[0]?.value || ''
}
const onClose = () => {
emit('close')
}
const onSubmit = () => {
const payload = {
name: form.value.name.trim(),
campaignId: form.value.campaignId,
domain: form.value.domain.trim(),
routePath: form.value.routePath.trim()
}
if (!payload.name || !payload.campaignId || !payload.domain || !payload.routePath) {
return
}
emit('submit', payload)
form.value = {
name: '메인 랜딩 페이지',
campaignId: props.campaigns[0]?.id || '',
domain: 'ad-camp-temp.test',
routePath: '/'
}
}
const onBackdrop = (evt: MouseEvent) => {
if (evt.target === evt.currentTarget) {
onClose()
}
}
</script>
<template>
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4"
role="dialog"
aria-modal="true"
@click="onBackdrop"
>
<div class="w-full max-w-lg rounded-2xl border border-indigo-400/20 bg-slate-950 p-6 shadow-2xl shadow-black/60">
<div class="flex items-center justify-between">
<h2 class="text-2xl font-bold text-white"> 페이지 만들기</h2>
<button
type="button"
@click="onClose"
class="rounded-md border border-slate-700/70 px-2 py-1 text-xs font-semibold text-slate-300"
>
닫기
</button>
</div>
<p class="mt-2 text-sm text-slate-400">페이지명/도메인/경로를 입력하고 생성하면 즉시 페이지가 등록됩니다.</p>
<div class="mt-5 space-y-4">
<label class="block space-y-2">
<span class="text-sm text-slate-300">페이지명</span>
<input
v-model="form.name"
type="text"
placeholder="예: 메인 랜딩 페이지"
class="w-full rounded-xl border border-indigo-500/25 bg-slate-900 px-4 py-3 text-slate-100 outline-none focus:border-cyan-300"
/>
</label>
<label class="block space-y-2">
<span class="text-sm text-slate-300">프로젝트</span>
<select
v-model="form.campaignId"
class="w-full rounded-xl border border-indigo-500/25 bg-slate-900 px-4 py-3 text-slate-100 outline-none focus:border-cyan-300"
>
<option value="">프로젝트 선택</option>
<option v-for="campaign in campaignChoices" :key="campaign.value" :value="campaign.value">
{{ campaign.label }}
</option>
</select>
</label>
<label class="block space-y-2">
<span class="text-sm text-slate-300">도메인</span>
<input
v-model="form.domain"
type="text"
placeholder="예: summer.example.com"
class="w-full rounded-xl border border-indigo-500/25 bg-slate-900 px-4 py-3 text-slate-100 outline-none focus:border-cyan-300"
/>
</label>
<label class="block space-y-2">
<span class="text-sm text-slate-300">페이지 경로</span>
<input
v-model="form.routePath"
type="text"
placeholder="예: /, /offer, /google"
class="w-full rounded-xl border border-indigo-500/25 bg-slate-900 px-4 py-3 text-slate-100 outline-none focus:border-cyan-300"
/>
</label>
</div>
<div class="mt-6 flex justify-end gap-2">
<button
type="button"
@click="onClose"
class="rounded-md border border-slate-700 px-4 py-2 text-sm text-slate-300"
>
취소
</button>
<button
type="button"
@click="onSubmit"
class="rounded-md bg-gradient-to-r from-indigo-500 to-cyan-500 px-4 py-2 text-sm font-semibold text-white"
>
생성
</button>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,89 @@
<script setup lang="ts">
import { ref } from 'vue'
interface Emits {
(e: 'close'): void
(e: 'submit', payload: { name: string; campaignName: string }): void
}
const emit = defineEmits<Emits>()
const form = ref({
name: '',
campaignName: ''
})
const onClose = () => {
emit('close')
}
const onSubmit = () => {
const name = form.value.name.trim()
const campaignName = form.value.campaignName.trim()
if (!name || !campaignName) {
return
}
emit('submit', { name, campaignName })
form.value = { name: '', campaignName: '' }
}
</script>
<template>
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4" role="dialog" aria-modal="true">
<div class="w-full max-w-lg rounded-2xl border border-indigo-400/20 bg-slate-950 p-6 shadow-2xl shadow-black/60">
<div class="flex items-center justify-between">
<h2 class="text-2xl font-bold text-white"> 프로젝트 만들기</h2>
<button
type="button"
@click="onClose"
class="rounded-md border border-slate-700/70 px-2 py-1 text-xs font-semibold text-slate-300"
>
닫기
</button>
</div>
<p class="mt-2 text-sm text-slate-400">프로젝트 기본 정보만 입력해도 바로 저장됩니다.</p>
<div class="mt-5 space-y-4">
<label class="block space-y-2">
<span class="text-sm text-slate-300">프로젝트명</span>
<input
v-model="form.name"
type="text"
placeholder="예: 여름 프로모션 캠페인"
class="w-full rounded-xl border border-indigo-500/25 bg-slate-900 px-4 py-3 text-slate-100 outline-none focus:border-cyan-300"
/>
</label>
<label class="block space-y-2">
<span class="text-sm text-slate-300">캠페인 표시명</span>
<input
v-model="form.campaignName"
type="text"
placeholder="예: 캠페인 1"
class="w-full rounded-xl border border-indigo-500/25 bg-slate-900 px-4 py-3 text-slate-100 outline-none focus:border-cyan-300"
/>
</label>
</div>
<div class="mt-6 flex justify-end gap-2">
<button
type="button"
@click="onClose"
class="rounded-md border border-slate-700 px-4 py-2 text-sm text-slate-300"
>
취소
</button>
<button
type="button"
@click="onSubmit"
class="rounded-md bg-gradient-to-r from-indigo-500 to-cyan-500 px-4 py-2 text-sm font-semibold text-white"
>
생성
</button>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,41 @@
import { defineComponent, h } from 'vue';
export default defineComponent({
name: 'Button',
props: {
type: {
type: String,
default: 'button',
},
variant: {
type: String,
default: 'default',
validator: (value: string) => ['default', 'outline', 'ghost'].includes(value),
},
className: {
type: String,
default: '',
},
},
setup(props, { slots }) {
const base = 'inline-flex items-center justify-center rounded-md px-4 py-2 font-medium transition';
const styles: Record<string, string> = {
default: 'bg-cyan-500 text-black hover:bg-cyan-400',
outline: 'border border-slate-600 text-slate-100 bg-transparent hover:bg-slate-900',
ghost: 'text-slate-100 hover:bg-slate-800',
};
return () =>
h(
'button',
{
type: props.type as 'button' | 'submit' | 'reset',
class: `${base} ${styles[props.variant as keyof typeof styles]} ${props.className}`.trim(),
},
slots.default ? slots.default() : undefined,
);
},
});
export { Button };

View File

@@ -0,0 +1,50 @@
<template>
<div class="min-h-screen bg-slate-950 text-slate-100">
<div class="flex min-h-screen">
<aside class="w-64 border-r border-indigo-400/20 bg-gradient-to-b from-slate-900/95 via-slate-950 to-slate-950">
<div class="border-b border-slate-800/70 px-5 py-4">
<h1 class="text-sm font-bold tracking-[0.18em] text-transparent bg-gradient-to-r from-cyan-300 to-indigo-300 bg-clip-text">LANDING ADMIN</h1>
<p class="mt-1 text-xs text-slate-400">캠페인·페이지 콘솔</p>
</div>
<nav class="p-3">
<NuxtLink
to="/admin"
class="mb-1 flex rounded-md px-3 py-2 text-sm text-slate-200 transition hover:bg-slate-800/70 hover:text-cyan-200"
active-class="bg-indigo-500/15 text-cyan-200 ring-1 ring-cyan-300/30"
exact-active-class="bg-indigo-500/15 text-cyan-200 ring-1 ring-cyan-300/30"
>
대시보드
</NuxtLink>
<NuxtLink
to="/admin/projects"
class="mb-1 flex rounded-md px-3 py-2 text-sm text-slate-200 transition hover:bg-slate-800/70 hover:text-cyan-200"
active-class="bg-indigo-500/15 text-cyan-200 ring-1 ring-cyan-300/30"
>
프로젝트
</NuxtLink>
<NuxtLink
to="/admin/pages"
class="mb-1 flex rounded-md px-3 py-2 text-sm text-slate-200 transition hover:bg-slate-800/70 hover:text-cyan-200"
active-class="bg-indigo-500/15 text-cyan-200 ring-1 ring-cyan-300/30"
>
페이지 관리
</NuxtLink>
<NuxtLink
to="/admin/leads"
class="mb-1 flex rounded-md px-3 py-2 text-sm text-slate-200 transition hover:bg-slate-800/70 hover:text-cyan-200"
active-class="bg-indigo-500/15 text-cyan-200 ring-1 ring-cyan-300/30"
>
리드 조회
</NuxtLink>
</nav>
</aside>
<main class="min-w-0 flex-1 bg-slate-950 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.12),_transparent_42%)] bg-[radial-gradient(circle_at_bottom_right,_rgba(168,85,247,0.10),_transparent_40%)] p-6">
<div class="mx-auto max-w-6xl">
<NuxtPage />
</div>
</main>
</div>
</div>
</template>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useDashboardStore } from '~/stores/dashboard'
const dashboardStore = useDashboardStore()
const { kpiCards, trendBars, runningProjects } = storeToRefs(dashboardStore)
</script>
<template>
<div class="space-y-4">
<header class="rounded-xl border border-slate-700/80 bg-gradient-to-r from-slate-900 via-indigo-950/70 to-slate-900 px-5 py-4">
<p class="text-xs uppercase tracking-[0.18em] text-cyan-300">대시보드</p>
<div class="mt-2 flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold text-slate-100">운영 대시보드</h1>
<p class="mt-1 text-sm text-slate-400">진행중 프로젝트, 전체 프로젝트, 신규 리드를 가로형 위젯으로 확인하세요.</p>
</div>
<NuxtLink
to="/admin/projects"
class="rounded-md border border-cyan-300/30 bg-cyan-500/10 px-3 py-1.5 text-sm font-semibold text-cyan-100 hover:bg-cyan-500/20"
>
프로젝트 보기
</NuxtLink>
</div>
</header>
<section class="grid gap-3 xl:grid-cols-[2fr_1fr]">
<div class="grid grid-cols-3 gap-3">
<article
v-for="card in kpiCards"
:key="card.label"
class="rounded-xl border bg-gradient-to-br from-slate-900 to-slate-950 p-5"
:class="[card.border]"
>
<p class="text-xs uppercase tracking-[0.16em] text-slate-200">{{ card.label }}</p>
<div class="mt-3 flex items-center gap-3">
<p class="text-3xl font-black" :class="card.color">{{ card.value }}</p>
<span class="inline-flex h-2.5 w-2.5 rounded-full bg-gradient-to-r" :class="card.band"></span>
</div>
<p class="mt-2 text-sm text-slate-400">{{ card.note }}</p>
</article>
</div>
<section class="rounded-xl border border-slate-800 bg-gradient-to-b from-slate-900 to-indigo-950/50 p-5">
<h2 class="text-sm font-semibold">리드 추이(샘플)</h2>
<div class="mt-3 h-36 rounded-lg border border-cyan-300/20 bg-slate-950/80">
<div class="flex h-full items-end gap-2 px-4 pb-4">
<span v-for="bar in trendBars" :key="bar" class="w-full bg-gradient-to-t from-cyan-300 to-indigo-300/80" :style="{ height: `${bar}%` }"></span>
</div>
</div>
</section>
</section>
<section class="grid gap-3 xl:grid-cols-[1fr_1fr]">
<article class="rounded-xl border border-slate-800 bg-gradient-to-b from-slate-900 to-slate-950 p-5">
<h2 class="text-lg font-semibold">진행중 프로젝트 목록</h2>
<p class="mt-1 text-sm text-slate-400">운영 중인 핵심 프로젝트</p>
<div class="mt-4 overflow-hidden rounded-lg border border-slate-800">
<table class="w-full text-left text-sm">
<thead class="bg-slate-900">
<tr class="text-slate-300">
<th class="px-4 py-3">프로젝트</th>
<th class="px-4 py-3">도메인</th>
<th class="px-4 py-3">상태</th>
<th class="px-4 py-3 text-right">리드</th>
</tr>
</thead>
<tbody>
<tr v-for="project in runningProjects" :key="project.domain" class="border-t border-slate-800 text-slate-200">
<td class="px-4 py-3">{{ project.name }}</td>
<td class="px-4 py-3">{{ project.domain }}</td>
<td class="px-4 py-3">
<span class="inline-flex items-center gap-1 rounded-full bg-indigo-900/40 px-2.5 py-0.5 text-xs font-medium text-indigo-200">
{{ project.status }}
</span>
</td>
<td class="px-4 py-3 text-right">{{ project.leads }}</td>
</tr>
</tbody>
</table>
</div>
</article>
<article class="rounded-xl border border-slate-800 bg-gradient-to-b from-slate-900 to-indigo-950/40 p-5">
<h2 class="text-lg font-semibold">빠른 액션</h2>
<p class="mt-1 text-sm text-slate-400">바로 이동해 바로 시작</p>
<div class="mt-4 flex flex-col gap-2">
<NuxtLink to="/admin/projects" class="rounded-md bg-gradient-to-r from-indigo-600 to-cyan-500 px-4 py-2 text-sm font-semibold text-white"> 프로젝트 만들기</NuxtLink>
<NuxtLink to="/admin/pages" class="rounded-md border border-indigo-300/30 bg-indigo-950/20 px-4 py-2 text-sm text-indigo-100 hover:bg-indigo-900/30">페이지 바로가기</NuxtLink>
<NuxtLink to="/admin/leads" class="rounded-md border border-cyan-300/30 bg-cyan-950/20 px-4 py-2 text-sm text-cyan-100 hover:bg-cyan-900/30">리드 조회</NuxtLink>
</div>
</article>
</section>
</div>
</template>

View File

@@ -0,0 +1,213 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
interface LeadRecord {
id: string
name: string
phone: string
email: string
campaign: string
page: string
channel: string
status: '새리드' | '검토중' | '완료'
createdAt: string
}
const leads = ref<LeadRecord[]>([
{
id: 'ld-1001',
name: '김하늘',
phone: '010-1111-2222',
email: 'haneul@example.com',
campaign: 'Q3 마케팅 캠페인',
page: '메인 랜딩',
channel: 'Google',
status: '새리드',
createdAt: '2026-03-04T09:12:00.000Z'
},
{
id: 'ld-1002',
name: '이준수',
phone: '010-2222-3333',
email: 'junsoo@example.com',
campaign: '블랙프라이데이 2023',
page: '구글 전용 랜딩',
channel: 'Meta',
status: '검토중',
createdAt: '2026-03-03T17:44:00.000Z'
},
{
id: 'ld-1003',
name: '박소영',
phone: '010-3333-4444',
email: 'soyoung@example.com',
campaign: 'Product Hunt 런칭',
page: '리타겟 랜딩',
channel: 'Instagram',
status: '완료',
createdAt: '2026-03-02T21:13:00.000Z'
},
{
id: 'ld-1004',
name: '최민재',
phone: '010-4444-5555',
email: 'minjae@example.com',
campaign: 'SaaS 웨비나 시리즈',
page: '오퍼 페이지 B',
channel: 'Naver',
status: '새리드',
createdAt: '2026-03-01T11:08:00.000Z'
},
{
id: 'ld-1005',
name: '정연우',
phone: '010-5555-6666',
email: 'yeonwoo@example.com',
campaign: 'Q3 마케팅 캠페인',
page: '메인 랜딩',
channel: 'Google',
status: '검토중',
createdAt: '2026-03-01T03:03:00.000Z'
}
])
const searchQuery = ref('')
const statusFilter = ref<'all' | LeadRecord['status']>('all')
const channelFilter = ref<'all' | string>('all')
const campaignFilter = ref<'all' | string>('all')
const channelOptions = ['Google', 'Meta', 'Instagram', 'Naver']
const campaigns = computed(() => {
const names = [...new Set(leads.value.map((lead) => lead.campaign))]
return names
})
const filteredLeads = computed(() => {
const query = searchQuery.value.trim().toLowerCase()
return leads.value
.filter((lead) => {
const target = `${lead.name} ${lead.phone} ${lead.email} ${lead.campaign} ${lead.page} ${lead.channel} ${lead.status}`.toLowerCase()
const matchesQuery = query === '' || target.includes(query)
const matchesStatus = statusFilter.value === 'all' || lead.status === statusFilter.value
const matchesChannel = channelFilter.value === 'all' || lead.channel === channelFilter.value
const matchesCampaign = campaignFilter.value === 'all' || lead.campaign === campaignFilter.value
return matchesQuery && matchesStatus && matchesChannel && matchesCampaign
})
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
})
const statusLabel = (status: LeadRecord['status']) => {
if (status === '완료') return '완료'
if (status === '검토중') return '검토중'
return '새 리드'
}
const statusClass = (status: LeadRecord['status']) => {
if (status === '완료') {
return 'text-emerald-200 bg-emerald-400/10'
}
if (status === '검토중') {
return 'text-amber-200 bg-amber-400/10'
}
return 'text-indigo-200 bg-indigo-400/10'
}
</script>
<template>
<div class="space-y-5">
<header class="rounded-xl border border-slate-700/80 bg-gradient-to-r from-slate-900 via-slate-900 to-indigo-950/70 px-5 py-4 shadow-[0_16px_40px_rgba(0,0,0,0.35)]">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<p class="text-sm text-slate-400"> <span class="px-2">></span> 리드 조회</p>
<h1 class="mt-1 text-3xl font-black tracking-tight text-slate-100">리드 조회</h1>
<p class="mt-1 text-sm text-slate-400">캠페인/페이지 기반으로 리드를 확인하고 상태를 관리하세요.</p>
</div>
<button class="rounded-xl bg-gradient-to-r from-indigo-500 to-cyan-500 px-5 py-2.5 text-base font-semibold text-white shadow-lg shadow-cyan-950/40 transition hover:from-indigo-400 hover:to-cyan-400">
리드 엑셀 내보내기
</button>
</div>
</header>
<section class="rounded-2xl border border-indigo-400/20 bg-gradient-to-br from-slate-900/80 via-slate-950 to-slate-900/90 p-5 shadow-[0_12px_30px_rgba(0,0,0,0.3)]">
<div class="flex flex-nowrap items-end justify-between gap-4">
<label class="min-w-0 space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">검색</span>
<div class="relative">
<span class="pointer-events-none absolute inset-y-0 left-3 flex items-center text-cyan-300"></span>
<input
v-model="searchQuery"
type="text"
placeholder="이름, 전화번호, 이메일, 캠페인, 페이지"
class="w-full min-w-[420px] rounded-xl border border-indigo-500/20 bg-slate-950/80 px-9 py-3 text-sm text-slate-100 outline-none ring-0 transition focus:border-cyan-400 focus:shadow-[0_0_0_1px_rgba(103,232,249,0.35)]"
/>
</div>
</label>
<div class="ml-auto flex shrink-0 items-end gap-3">
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">상태</span>
<select v-model="statusFilter" class="rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400">
<option value="all">전체</option>
<option value="새리드"> 리드</option>
<option value="검토중">검토중</option>
<option value="완료">완료</option>
</select>
</label>
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">채널</span>
<select v-model="channelFilter" class="rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400">
<option value="all">전체</option>
<option v-for="ch in channelOptions" :key="ch" :value="ch">
{{ ch }}
</option>
</select>
</label>
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">캠페인</span>
<select v-model="campaignFilter" class="rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400">
<option value="all">전체 캠페인</option>
<option v-for="name in campaigns" :key="name" :value="name">{{ name }}</option>
</select>
</label>
</div>
</div>
</section>
<section class="overflow-hidden rounded-2xl border border-slate-800 bg-slate-950/80">
<table class="w-full text-left text-sm">
<thead class="bg-slate-900 text-slate-300">
<tr>
<th class="px-4 py-3">이름</th>
<th class="px-4 py-3">연락처</th>
<th class="px-4 py-3">이메일</th>
<th class="px-4 py-3">캠페인</th>
<th class="px-4 py-3">페이지</th>
<th class="px-4 py-3">채널</th>
<th class="px-4 py-3">상태</th>
<th class="px-4 py-3">수집일</th>
</tr>
</thead>
<tbody>
<tr v-for="lead in filteredLeads" :key="lead.id" class="border-t border-slate-800 text-slate-200">
<td class="px-4 py-3">{{ lead.name }}</td>
<td class="px-4 py-3">{{ lead.phone }}</td>
<td class="px-4 py-3 text-cyan-200">{{ lead.email }}</td>
<td class="px-4 py-3">{{ lead.campaign }}</td>
<td class="px-4 py-3">{{ lead.page }}</td>
<td class="px-4 py-3">{{ lead.channel }}</td>
<td class="px-4 py-3">
<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold tracking-[0.07em]" :class="statusClass(lead.status)">
{{ statusLabel(lead.status) }}
</span>
</td>
<td class="px-4 py-3">{{ new Date(lead.createdAt).toLocaleString() }}</td>
</tr>
</tbody>
</table>
</section>
</div>
</template>

View File

@@ -0,0 +1,375 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { usePagesStore } from '~/stores/pages'
type BuilderBlockType = 'html' | 'image' | 'form' | 'kakao'
type BuilderBlock = {
id: string
type: BuilderBlockType
label: string
html?: string
src?: string
alt?: string
}
const route = useRoute()
const router = useRouter()
const pagesStore = usePagesStore()
const { campaignOptions } = storeToRefs(pagesStore)
const pageId = computed(() => String(route.params.id || ''))
const selectedPage = computed(() => pagesStore.pages.find((page) => page.id === pageId.value))
const pageTitle = ref('Landing Page')
const metaDescription = ref('landing page description')
const metaKeywords = ref('landing, ads')
const iconUrl = ref('https://avatars.githubusercontent.com/u/9919?s=200&v=4')
const headCode = ref('<meta property="og:type" content="website" />')
const bodyScript = ref("console.log('landing admin builder loaded')")
const footerHtml = ref('')
const isDragOver = ref(false)
const imageInputRef = ref<HTMLInputElement | null>(null)
const htmlDraft = ref('<p>새 HTML 블록입니다.</p>')
const imageAlt = ref('이미지')
const generatedHtml = ref('')
const isSaved = ref(false)
const objectUrlMap = ref<Record<string, string>>({})
const blockList = ref<BuilderBlock[]>([
{ id: `b-${Date.now()}`, type: 'html', label: '커스텀 HTML', html: '<p>커스텀 HTML 블록입니다.</p>' },
])
const campaignName = computed(() => {
if (!selectedPage.value) return '-'
return campaignOptions.value.find((campaign) => campaign.id === selectedPage.value?.campaignId)?.name || '-'
})
const pageUrl = computed(() => {
if (!selectedPage.value) return ''
return `https://${selectedPage.value.domain}${selectedPage.value.routePath}`
})
const previewHeader = computed(() => selectedPage.value?.name || '새 페이지')
const finalHeadHtml = computed(() => {
const pieces = [
'<meta charset="UTF-8" />',
`<title>${escapeHtml(pageTitle.value)}</title>`,
`<meta name="description" content="${escapeHtml(metaDescription.value)}" />`,
`<meta name="keywords" content="${escapeHtml(metaKeywords.value)}" />`,
`<link rel="icon" href="${escapeHtml(iconUrl.value)}" />`,
headCode.value.trim(),
]
return pieces.filter(Boolean).join('\n ')
})
const blockHtmls = computed(() => blockList.value.map((block) => {
if (block.type === 'image' && block.src) {
return `<img src="${escapeHtml(block.src)}" alt="${escapeHtml(block.alt || '')}" style="max-width:100%;border-radius:12px;display:block;" />`
}
return block.html ? block.html : '<p>빈 HTML</p>'
}))
const previewBlockHtmls = computed(() => blockList.value.map((block) => {
if (block.type === 'image' && block.src) {
return `<img src="${escapeHtml(block.src)}" alt="${escapeHtml(block.alt || '')}" style="max-width:320px; width:100%; max-height:220px; object-fit:contain;display:block;" />`
}
return block.html ? block.html : '<p>빈 HTML</p>'
}))
const finalBodyHtml = computed(() => {
const bodyOpen = '<' + 'script>'
const bodyClose = '<' + '/script>'
const safeBodyScript = bodyScript.value.replace(/<\/script/gi, '<\\/script')
const body = [
...blockHtmls.value,
footerHtml.value,
`${bodyOpen}${safeBodyScript}${bodyClose}`,
].join('\n')
return `<!doctype html>
<html>
<head>
${finalHeadHtml.value}
</head>
<body>
<div style="padding:16px;">${body}</div>
</body>
</html>`
})
const previewBodyHtml = computed(() => {
const body = [
...previewBlockHtmls.value,
footerHtml.value,
].join('\n')
return `<div style=\"padding:16px;\">${body}</div>`
})
const addHtmlBlock = () => {
const draft = htmlDraft.value.trim() || '<p>빈 HTML</p>'
blockList.value = [
{
id: `b-html-${Date.now()}`,
type: 'html',
label: `HTML 블록 ${blockList.value.length + 1}`,
html: draft,
},
...blockList.value,
]
htmlDraft.value = '<p>새 HTML 블록입니다.</p>'
}
const addImageFromFiles = (files: FileList | null) => {
if (!files || files.length === 0) return
const toAdd = Array.from(files)
const createdBlocks: BuilderBlock[] = toAdd.map((file, idx) => {
const localUrl = URL.createObjectURL(file)
const blockId = `b-image-${Date.now()}-${idx}`
objectUrlMap.value[blockId] = localUrl
return {
id: blockId,
type: 'image',
label: `이미지 블록 ${blockList.value.length + idx + 1}`,
src: localUrl,
alt: imageAlt.value.trim() || '이미지',
}
})
blockList.value = [...createdBlocks, ...blockList.value]
imageAlt.value = '이미지'
}
const addImageFromUpload = (evt: Event) => {
const target = evt.target as HTMLInputElement
const targetFiles = target.files
addImageFromFiles(targetFiles)
target.value = ''
}
const openImagePicker = () => {
imageInputRef.value?.click()
}
const onImageDragOver = (evt: DragEvent) => {
evt.preventDefault()
isDragOver.value = true
}
const onImageDragLeave = (evt: DragEvent) => {
evt.preventDefault()
isDragOver.value = false
}
const onImageDrop = (evt: DragEvent) => {
evt.preventDefault()
isDragOver.value = false
addImageFromFiles(evt.dataTransfer?.files || null)
}
const moveUp = (index: number) => {
if (index === 0) return
const next = [...blockList.value]
;[next[index - 1], next[index]] = [next[index], next[index - 1]]
blockList.value = next
}
const moveDown = (index: number) => {
if (index === blockList.value.length - 1) return
const next = [...blockList.value]
;[next[index], next[index + 1]] = [next[index + 1], next[index]]
blockList.value = next
}
const removeBlock = (id: string) => {
if (objectUrlMap.value[id]) {
URL.revokeObjectURL(objectUrlMap.value[id])
delete objectUrlMap.value[id]
}
blockList.value = blockList.value.filter((block) => block.id !== id)
}
const saveBuilder = () => {
generatedHtml.value = finalBodyHtml.value
isSaved.value = true
setTimeout(() => {
isSaved.value = false
}, 1200)
}
const copyFinalHtml = async () => {
if (!generatedHtml.value) {
generatedHtml.value = finalBodyHtml.value
}
await navigator.clipboard?.writeText(generatedHtml.value)
}
const backToList = () => {
router.push('/admin/pages')
}
function escapeHtml(value: string) {
return value
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;')
}
</script>
<template>
<section class="space-y-5">
<header class="rounded-xl border border-slate-700/80 bg-slate-900 px-5 py-4">
<div class="flex flex-wrap items-center justify-between gap-2">
<div>
<p class="text-xs text-slate-400"> &gt; 페이지 &gt; 빌더</p>
<h1 class="mt-1 text-2xl font-black tracking-tight text-white">페이지 빌더</h1>
<p class="mt-1 text-sm text-slate-400">선택한 페이지의 블록을 관리하고 HTML을 생성합니다.</p>
</div>
<div class="flex items-center gap-2">
<NuxtLink to="/admin/pages" class="rounded-md border border-slate-700 px-3 py-2 text-sm">목록으로</NuxtLink>
<button type="button" @click="backToList" class="rounded-md border border-slate-700 px-3 py-2 text-sm">닫기</button>
<button type="button" @click="saveBuilder" class="rounded-md bg-gradient-to-r from-indigo-600 to-cyan-500 px-4 py-2 text-sm font-medium text-white">저장</button>
</div>
</div>
</header>
<section v-if="selectedPage" class="grid gap-4 lg:grid-cols-[1.2fr_1fr]">
<article class="rounded-2xl border border-slate-800 bg-slate-950 p-5">
<div class="mb-4">
<h2 class="text-xl font-bold text-white">{{ previewHeader }}</h2>
<p class="mt-1 text-sm text-slate-400">{{ campaignName }}</p>
<p class="mt-1 text-sm text-slate-300">{{ pageUrl }}</p>
</div>
<div class="mx-auto w-[360px] rounded-xl border border-slate-700 bg-black/20 p-4">
<div class="rounded-lg bg-slate-900/80 p-2 min-h-[120px]" v-html="previewBodyHtml"></div>
<div v-if="blockHtmls.length === 0" class="rounded-lg border border-slate-700 bg-slate-900/80 p-2 mt-2 text-xs text-slate-300">블록이 없습니다.</div>
</div>
</article>
<article class="rounded-2xl border border-slate-800 bg-slate-950 p-5 text-sm">
<h2 class="text-lg font-semibold text-white">빌더 설정</h2>
<p class="mt-1 text-slate-400">HTML/이미지 블록과 헤더/아이콘/Body Script/푸터 HTML을 설정하세요.</p>
<div class="mt-4 space-y-2">
<label class="block space-y-1">
<span class="text-slate-400">페이지 제목</span>
<input v-model="pageTitle" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2" />
</label>
<label class="block space-y-1">
<span class="text-slate-400">메타 설명</span>
<textarea v-model="metaDescription" rows="2" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2"></textarea>
</label>
<label class="block space-y-1">
<span class="text-slate-400">메타 키워드</span>
<input v-model="metaKeywords" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2" />
</label>
<label class="block space-y-1">
<span class="text-slate-400">파비콘 URL</span>
<input v-model="iconUrl" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2" />
</label>
<label class="block space-y-1">
<span class="text-slate-400">HEAD 추가 코드</span>
<textarea v-model="headCode" rows="3" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2"></textarea>
</label>
<label class="block space-y-1">
<span class="text-slate-400">BODY Script</span>
<textarea v-model="bodyScript" rows="3" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2"></textarea>
</label>
<label class="block space-y-1">
<span class="text-slate-400">Footer HTML</span>
<textarea v-model="footerHtml" rows="3" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2"></textarea>
</label>
</div>
<div class="mt-4 space-y-2">
<h3 class="font-semibold text-white">블록 추가</h3>
<label class="block space-y-1">
<span class="text-slate-400">HTML 블록</span>
<textarea v-model="htmlDraft" rows="3" class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2"></textarea>
</label>
<button type="button" @click="addHtmlBlock" class="mt-1 rounded-md bg-cyan-900/60 px-3 py-2 text-sm text-white">HTML 블록 추가</button>
<div
class="mt-3 rounded-lg border-2 border-dashed border-slate-700 bg-slate-900/40 p-5 text-center text-slate-300"
:class="isDragOver ? 'border-cyan-400 bg-slate-900' : 'border-slate-700'"
@dragover.prevent="onImageDragOver"
@dragleave.prevent="onImageDragLeave"
@drop.prevent="onImageDrop"
@click="openImagePicker"
>
<p class="text-sm text-slate-200 font-semibold">이미지 파일을 드래그해서 올려주세요</p>
<p class="mt-1 text-xs text-slate-500">갯수 제한 없이 번에 여러 추가됩니다.</p>
<span class="text-xs text-slate-500 mt-2 block">참고: 업로드한 파일은 현재 페이지 미리보기/블록으로 즉시 반영됩니다.</span>
<input
ref="imageInputRef"
type="file"
accept="image/*"
multiple
class="hidden"
@change="addImageFromUpload"
/>
</div>
</div>
<div class="mt-4 space-y-2">
<div v-for="(block, idx) in blockList" :key="block.id" class="rounded-md border border-slate-700 bg-slate-900 px-3 py-2">
<div class="mb-1 flex items-start justify-between gap-2">
<div class="flex min-w-0 items-center gap-2">
<img
v-if="block.type === 'image' && block.src"
:src="block.src"
:alt="block.alt || '이미지'"
class="h-12 w-16 rounded-none object-cover"
/>
<div class="min-w-0">
<p class="text-slate-100">{{ block.label }}</p>
<p class="text-xs text-slate-400">{{ block.type }}</p>
<p class="text-xs text-slate-300" v-if="block.type === 'html'">{{ block.html }}</p>
<p class="text-xs text-slate-300" v-else-if="block.type === 'image'">{{ block.alt || '이미지 블록' }}</p>
<p class="text-xs text-slate-300" v-else>{{ block.html || '사용자 블록' }}</p>
</div>
</div>
<div class="flex items-center gap-2">
<button type="button" class="rounded-md border border-slate-700 px-2 py-1 text-xs" @click="moveUp(idx)"></button>
<button type="button" class="rounded-md border border-slate-700 px-2 py-1 text-xs" @click="moveDown(idx)"></button>
<button type="button" class="rounded-md border border-red-500/50 px-2 py-1 text-xs text-red-200" @click="removeBlock(block.id)">삭제</button>
</div>
</div>
</div>
</div>
<p class="mt-4 text-xs text-slate-400">{{ blockList.length }} 블록</p>
</article>
</section>
<section v-else class="rounded-2xl border border-dashed border-slate-700 bg-slate-900 p-6 text-slate-300">
<p>선택한 페이지를 찾을 없습니다.</p>
<NuxtLink to="/admin/pages" class="mt-4 inline-flex rounded-md border border-slate-700 px-3 py-1.5 text-sm text-slate-100">페이지 목록으로 이동</NuxtLink>
</section>
<section v-if="generatedHtml" class="rounded-2xl border border-slate-700 bg-slate-900 p-4">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-slate-100">생성 HTML</h3>
<button type="button" @click="copyFinalHtml" class="rounded-md border border-cyan-300/40 px-3 py-1.5 text-xs text-cyan-100">클립보드 복사</button>
</div>
<pre class="mt-3 max-h-64 overflow-auto rounded-lg bg-slate-950 p-3 text-xs text-slate-300">{{ generatedHtml }}</pre>
</section>
<p v-if="isSaved" class="rounded-md border border-emerald-300/40 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-200">저장(HTML 생성)되었습니다.</p>
</section>
</template>

View File

@@ -0,0 +1,224 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { usePagesStore } from '~/stores/pages'
import { useVariantsStore } from '~/stores/variants'
const route = useRoute()
const router = useRouter()
const pagesStore = usePagesStore()
const variantsStore = useVariantsStore()
const pageId = computed(() => String(route.params.id || ''))
const page = computed(() => pagesStore.pages.find((item) => item.id === pageId.value) ?? null)
const variants = computed(() => variantsStore.listByPage(pageId.value))
const hasPage = computed(() => page.value !== null)
const totalLeads = computed(() => variants.value.reduce((sum, item) => sum + item.leadCount, 0))
const totalTraffic = computed(() => variants.value.reduce((sum, item) => sum + item.trafficWeight, 0))
const newVariantName = ref('')
const newVariantDescription = ref('')
const newVariantWeight = ref(10)
const pageTitle = computed(() => {
if (!page.value) {
return ''
}
return `${page.value.name} 변형 관리`
})
const backToPages = () => {
router.push('/admin/pages')
}
const toBuilder = () => {
if (hasPage.value) {
router.push(`/admin/pages/${pageId.value}/builder`)
}
}
const createVariant = () => {
const name = newVariantName.value.trim()
if (!hasPage.value || name.length === 0) {
return
}
variantsStore.createVariant({
pageId: pageId.value,
name,
description: newVariantDescription.value,
trafficWeight: Number(newVariantWeight.value) || 10
})
newVariantName.value = ''
newVariantDescription.value = ''
newVariantWeight.value = 10
}
const removeVariant = (variantId: string) => {
variantsStore.removeVariant(pageId.value, variantId)
}
const toggleVariant = (variantId: string) => {
variantsStore.toggleActive(pageId.value, variantId)
}
</script>
<template>
<div class="space-y-5">
<header class="rounded-xl border border-slate-700/80 bg-gradient-to-r from-slate-900 via-indigo-950/70 to-slate-900 px-5 py-4">
<p class="text-xs uppercase tracking-[0.18em] text-cyan-300">페이지</p>
<div class="mt-2 flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold text-slate-100">{{ hasPage ? pageTitle : '페이지를 찾을 수 없습니다' }}</h1>
<p v-if="page" class="mt-1 text-sm text-slate-400">
{{ page.domain }}{{ page.routePath }} · {{ page.leadCount }} 리드
</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button
type="button"
class="rounded-md border border-cyan-300/30 bg-cyan-500/10 px-3 py-1.5 text-sm font-semibold text-cyan-100 hover:bg-cyan-500/20"
@click="toBuilder"
>
빌더 열기
</button>
<button
type="button"
class="rounded-md border border-slate-700 px-3 py-1.5 text-sm text-slate-300 hover:bg-slate-900"
@click="backToPages"
>
페이지 목록
</button>
</div>
</div>
</header>
<div v-if="!hasPage" class="rounded-xl border border-slate-800 bg-slate-900/70 px-6 py-8">
<h2 class="text-xl font-bold text-slate-100">해당 페이지를 찾을 없습니다.</h2>
<p class="mt-1 text-sm text-slate-300">ID가 변경되었거나 삭제된 페이지일 있습니다.</p>
<button
type="button"
class="mt-4 rounded-md bg-gradient-to-r from-indigo-600 to-cyan-500 px-4 py-2 text-sm font-semibold text-white"
@click="backToPages"
>
페이지 목록으로 이동
</button>
</div>
<template v-else>
<section class="grid gap-4 md:grid-cols-2">
<article class="rounded-xl border border-slate-800 bg-slate-900/70 p-5">
<h2 class="text-sm font-semibold text-slate-200">변형 메트릭</h2>
<div class="mt-4 grid grid-cols-2 gap-3">
<div class="rounded-lg border border-slate-700 bg-slate-950/60 p-3">
<p class="text-xs text-slate-400"> 변형 </p>
<p class="mt-2 text-2xl font-black text-slate-100">{{ variants.length }}</p>
</div>
<div class="rounded-lg border border-slate-700 bg-slate-950/60 p-3">
<p class="text-xs text-slate-400">누적 리드</p>
<p class="mt-2 text-2xl font-black text-slate-100">{{ totalLeads }}</p>
</div>
<div class="rounded-lg border border-slate-700 bg-slate-950/60 p-3">
<p class="text-xs text-slate-400"> 트래픽 비율</p>
<p class="mt-2 text-2xl font-black text-slate-100">{{ totalTraffic }}%</p>
</div>
</div>
</article>
<article class="rounded-xl border border-slate-800 bg-slate-900/70 p-5">
<h2 class="text-sm font-semibold text-slate-200"> 변형 추가</h2>
<div class="mt-4 space-y-3">
<label class="block space-y-2">
<span class="text-xs font-medium text-slate-300">변형 이름</span>
<input
v-model="newVariantName"
type="text"
placeholder="예: 주말 자극형"
class="w-full rounded-lg border border-indigo-400/30 bg-slate-950/80 px-3 py-2 text-sm text-slate-100 outline-none"
/>
</label>
<label class="block space-y-2">
<span class="text-xs font-medium text-slate-300">메모</span>
<input
v-model="newVariantDescription"
type="text"
placeholder="변형 설명"
class="w-full rounded-lg border border-indigo-400/30 bg-slate-950/80 px-3 py-2 text-sm text-slate-100 outline-none"
/>
</label>
<label class="block space-y-2">
<span class="text-xs font-medium text-slate-300">트래픽 비율 (%)</span>
<input
v-model.number="newVariantWeight"
type="number"
min="1"
max="100"
class="w-full rounded-lg border border-indigo-400/30 bg-slate-950/80 px-3 py-2 text-sm text-slate-100 outline-none"
/>
</label>
<button
type="button"
class="w-full rounded-lg bg-gradient-to-r from-indigo-600 to-cyan-500 px-4 py-2 text-sm font-semibold text-white"
@click="createVariant"
>
변형 생성
</button>
</div>
</article>
</section>
<section class="space-y-3">
<div class="rounded-xl border border-slate-800 bg-slate-900/70 px-4 py-3 text-sm font-semibold text-slate-100">
등록 변형 목록
</div>
<article
v-for="variant in variants"
:key="variant.id"
class="rounded-xl border border-slate-800 bg-slate-900/70 p-5"
>
<div class="flex flex-wrap items-start justify-between gap-2">
<div>
<h3 class="text-lg font-bold text-slate-100">{{ variant.name }}</h3>
<p class="mt-1 text-sm text-slate-400">{{ variant.description || '메모 없음' }}</p>
</div>
<button
type="button"
@click="toggleVariant(variant.id)"
:class="variant.isActive ? 'bg-emerald-500/20 text-emerald-100' : 'bg-slate-700 text-slate-300'"
class="rounded-md border border-slate-700 px-3 py-1.5 text-xs font-semibold"
>
{{ variant.isActive ? '활성' : '비활성' }}
</button>
</div>
<div class="mt-3 grid gap-3 md:grid-cols-3">
<div class="rounded-lg border border-slate-700 bg-slate-950/60 p-3">
<p class="text-xs text-slate-400">상태</p>
<p class="mt-1 text-base font-semibold text-slate-100">
{{ variant.isActive ? '노출' : '중단' }}
</p>
</div>
<div class="rounded-lg border border-slate-700 bg-slate-950/60 p-3">
<p class="text-xs text-slate-400">트래픽 비율</p>
<p class="mt-1 text-base font-semibold text-slate-100">{{ variant.trafficWeight }}%</p>
</div>
<div class="rounded-lg border border-slate-700 bg-slate-950/60 p-3">
<p class="text-xs text-slate-400">리드 </p>
<p class="mt-1 text-base font-semibold text-slate-100">{{ variant.leadCount }}</p>
</div>
</div>
<div class="mt-4 flex justify-end">
<button
type="button"
class="rounded-md border border-red-300/40 bg-red-900/20 px-3 py-1.5 text-sm text-red-100"
@click="removeVariant(variant.id)"
>
삭제
</button>
</div>
</article>
</section>
</template>
</div>
</template>

View File

@@ -0,0 +1,273 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { usePagesStore, type PageStatus, type PageSortBy } from '~/stores/pages'
import { useVariantsStore } from '~/stores/variants'
import CreatePageModal from '~/components/admin/CreatePageModal.vue'
import { useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pagesStore = usePagesStore()
const variantsStore = useVariantsStore()
const { filteredPages, searchQuery, campaignFilter, statusFilter, sortBy, campaignOptions } = storeToRefs(pagesStore)
const pages = computed(() =>
filteredPages.value.map((page) => ({
...page,
variantCount: variantsStore.countByPage(page.id)
}))
)
const showCreatePage = ref(false)
const initialCampaign = computed(() => {
const value = route.query.campaignId
if (typeof value === 'string' && value.length > 0) {
return value
}
return 'all'
})
watch(
() => initialCampaign.value,
(next) => {
pagesStore.setCampaignFilter(next)
},
{ immediate: true }
)
const statusOptions = [
{ value: 'all', label: '전체' },
{ value: 'live', label: '라이브' },
{ value: 'draft', label: '초안' },
{ value: 'paused', label: '일시정지' }
] as const
const sortOptions: Array<{ value: PageSortBy; label: string }> = [
{ value: 'updated', label: '최근 수정순' },
{ value: 'name', label: '이름 오름차순' },
{ value: 'leads', label: '리드' },
{ value: 'visitors', label: '방문자' },
{ value: 'variants', label: '변형 수' }
]
const campaignItems = computed(() => [
{ value: 'all', label: '전체 캠페인' },
...campaignOptions.value.map((campaign) => ({
value: campaign.id,
label: campaign.name
}))
])
const statusLabel = (status: PageStatus) => {
const { label } = pagesStore.statusClass(status)
return label
}
const statusClass = (status: PageStatus) => {
const { chipClass } = pagesStore.statusClass(status)
return chipClass
}
const statusDotClass = (status: PageStatus) => {
const { dotClass } = pagesStore.statusClass(status)
return dotClass
}
const openCreatePage = () => {
showCreatePage.value = true
}
const closeCreatePage = () => {
showCreatePage.value = false
}
const submitCreatePage = (payload: { name: string; campaignId: string; domain: string; routePath: string }) => {
pagesStore.createPage(payload)
showCreatePage.value = false
}
const openBuilder = (pageId: string) => {
router.push(`/admin/pages/${pageId}/builder`)
}
const onDelete = (pageId: string) => {
pagesStore.removePage(pageId)
}
const campaignLabel = (campaignId: string) => pagesStore.campaignName(campaignId)
const pageUrl = (domain: string, routePath: string) => `https://${domain}${routePath}`
</script>
<template>
<div class="space-y-5">
<header class="rounded-xl border border-slate-700/80 bg-gradient-to-r from-slate-900 via-slate-900 to-indigo-950/70 px-5 py-4 shadow-[0_16px_40px_rgba(0,0,0,0.35)]">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<p class="text-sm text-slate-400"> <span class="px-2">></span> 페이지</p>
<h1 class="mt-1 text-3xl font-black tracking-tight text-slate-100">페이지 관리</h1>
<p class="mt-1 text-sm text-slate-400">캠페인 기준으로 페이지를 관리하고 운영하세요.</p>
</div>
<button
type="button"
@click="openCreatePage"
class="rounded-xl bg-gradient-to-r from-indigo-500 to-cyan-500 px-5 py-2.5 text-base font-semibold text-white shadow-lg shadow-cyan-950/40 transition hover:from-indigo-400 hover:to-cyan-400"
>
+ 페이지 만들기
</button>
</div>
</header>
<section class="rounded-2xl border border-indigo-400/20 bg-gradient-to-br from-slate-900/80 via-slate-950 to-slate-900/90 p-5 shadow-[0_12px_30px_rgba(0,0,0,0.3)]">
<div class="flex flex-nowrap items-end justify-between gap-4">
<label class="min-w-0 space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">검색</span>
<div class="relative">
<span class="pointer-events-none absolute inset-y-0 left-3 flex items-center text-cyan-300"></span>
<input
v-model="searchQuery"
type="text"
placeholder="페이지명, 도메인, 경로로 검색"
class="w-full min-w-[520px] rounded-xl border border-indigo-500/20 bg-slate-950/80 px-9 py-3 text-sm text-slate-100 outline-none ring-0 transition focus:border-cyan-400 focus:shadow-[0_0_0_1px_rgba(103,232,249,0.35)]"
/>
</div>
</label>
<div class="ml-auto flex shrink-0 items-end gap-3">
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">캠페인</span>
<select
v-model="campaignFilter"
class="shrink-0 rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400"
>
<option
v-for="campaign in campaignItems"
:key="campaign.value"
:value="campaign.value"
>
{{ campaign.label }}
</option>
</select>
</label>
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">상태</span>
<select
v-model="statusFilter"
class="shrink-0 rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400"
>
<option v-for="status in statusOptions" :key="status.value" :value="status.value">
{{ status.label }}
</option>
</select>
</label>
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">정렬</span>
<select
v-model="sortBy"
class="shrink-0 rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400"
>
<option v-for="option in sortOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</label>
</div>
</div>
</section>
<section class="space-y-4">
<article
v-for="page in pages"
:key="page.id"
class="relative overflow-hidden rounded-2xl border border-slate-800 bg-gradient-to-b from-slate-900/95 to-slate-950/95 p-5 shadow-lg shadow-black/20 transition hover:-translate-y-0.5 hover:border-cyan-300/40"
>
<div class="flex items-start justify-between gap-2">
<div>
<h2 class="text-xl font-bold text-slate-100">{{ page.name }}</h2>
<p class="mt-1 text-sm text-slate-400">{{ campaignLabel(page.campaignId) }}</p>
<p class="mt-1 text-sm text-slate-300">
{{ pageUrl(page.domain, page.routePath) }}
</p>
</div>
<span class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-semibold tracking-[0.08em]" :class="statusClass(page.status)" style="font-size: 9px; line-height: 1.1;">
<span class="h-1.5 w-1.5 rounded-full" :class="statusDotClass(page.status)" />
{{ statusLabel(page.status) }}
</span>
</div>
<div class="mt-4 grid grid-cols-4 gap-3">
<div class="rounded-xl border border-slate-800 bg-slate-950/80 p-3">
<p class="text-xs tracking-wider text-slate-400">도메인</p>
<p class="mt-1 text-lg font-bold text-slate-100">{{ page.domain }}</p>
</div>
<div class="rounded-xl border border-slate-800 bg-slate-950/80 p-3">
<p class="text-xs tracking-wider text-slate-400">리드</p>
<p class="mt-1 text-lg font-bold text-slate-100">{{ page.leadCount }}</p>
</div>
<div class="rounded-xl border border-slate-800 bg-slate-950/80 p-3">
<p class="text-xs tracking-wider text-slate-400">방문자</p>
<p class="mt-1 text-lg font-bold text-slate-100">{{ page.visitorCount }}</p>
</div>
<div class="rounded-xl border border-slate-800 bg-slate-950/80 p-3">
<p class="text-xs tracking-wider text-slate-400">변형</p>
<p class="mt-1 text-lg font-bold text-slate-100">{{ page.variantCount }}</p>
</div>
</div>
<div class="mt-4 flex items-center justify-between border-t border-slate-700/60 pt-4">
<p class="text-sm text-slate-400">마지막 수정: {{ new Date(page.updatedAt).toLocaleString() }}</p>
<div class="flex items-center gap-2">
<button
type="button"
class="rounded-md border border-slate-700/80 bg-slate-900 px-3 py-1.5 text-sm text-slate-200 transition hover:bg-slate-800/70"
>
미리보기
</button>
<NuxtLink
:to="`/admin/pages/${page.id}/variants`"
class="rounded-md border border-indigo-400/40 bg-indigo-950/50 px-3 py-1.5 text-sm text-indigo-100 transition hover:bg-indigo-900/50"
>
변형 관리
</NuxtLink>
<button
type="button"
@click="openBuilder(page.id)"
class="rounded-md bg-gradient-to-r from-indigo-600 to-cyan-500 px-3 py-1.5 text-sm font-medium text-white hover:from-indigo-500 hover:to-cyan-400"
>
빌더 열기
</button>
<button
type="button"
@click="onDelete(page.id)"
class="rounded-md border border-red-300/30 bg-red-900/30 px-3 py-1.5 text-sm text-red-100 transition hover:bg-red-900/50"
>
삭제
</button>
</div>
</div>
</article>
<article class="flex min-h-[220px] items-center justify-center rounded-2xl border border-dashed border-cyan-300/30 bg-gradient-to-b from-slate-900 to-indigo-950/60 p-5">
<div class="text-center">
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-r from-indigo-400/20 to-cyan-400/20 text-2xl text-cyan-200">+</div>
<p class="text-2xl font-bold text-slate-100">페이지가 없습니다</p>
<p class="mt-2 text-slate-400">필터를 조정하거나 페이지를 먼저 만들어보세요.</p>
<button
type="button"
class="mx-auto mt-4 rounded-md bg-gradient-to-r from-indigo-600 to-cyan-500 px-4 py-2 text-sm font-semibold text-white"
@click="openCreatePage"
>
바로 만들기
</button>
</div>
</article>
</section>
<CreatePageModal
v-if="showCreatePage"
:campaigns="campaignOptions"
@close="closeCreatePage"
@submit="submitCreatePage"
/>
</div>
</template>

View File

@@ -0,0 +1,259 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useProjectsStore, type ProjectStatus } from '~/stores/projects'
import { usePagesStore } from '~/stores/pages'
import { ref } from 'vue'
import CreateProjectModal from '~/components/admin/CreateProjectModal.vue'
const projectsStore = useProjectsStore()
const pagesStore = usePagesStore()
const { filteredProjects, searchQuery, statusFilter, sortBy } = storeToRefs(projectsStore)
const projects = filteredProjects
const showCreateProject = ref(false)
const statusOptions = [
{ value: 'all', label: '전체' },
{ value: 'active', label: '진행중' },
{ value: 'archived', label: '종료' },
{ value: 'draft', label: '임시저장' }
] as const
const sortOptions = [
{ value: 'newest', label: '최신순' },
{ value: 'oldest', label: '오래된순' },
{ value: 'most-leads', label: '리드 많은순' },
{ value: 'most-variants', label: '변형 많은순' }
] as const
const statusLabel = (status: ProjectStatus) => {
if (status === 'active') {
return '진행중'
}
if (status === 'archived') {
return '종료'
}
return '임시저장'
}
const statusClass = (status: ProjectStatus) => {
if (status === 'active') {
return 'text-emerald-200 bg-emerald-400/10'
}
if (status === 'archived') {
return 'text-slate-300 bg-slate-500/10'
}
return 'text-amber-200 bg-amber-400/10'
}
const statusDotClass = (status: ProjectStatus) => {
if (status === 'active') {
return 'bg-emerald-300'
}
if (status === 'archived') {
return 'bg-slate-400'
}
return 'bg-amber-300'
}
const cardAccent = (status: ProjectStatus) => {
if (status === 'active') {
return 'before:bg-emerald-400'
}
if (status === 'archived') {
return 'before:bg-slate-400'
}
return 'before:bg-amber-300'
}
const getVariantPageId = (projectId: string) => {
return pagesStore.pages.find((page) => page.campaignId === projectId)?.id ?? ''
}
const openCreateModal = () => {
showCreateProject.value = true
}
const closeCreateModal = () => {
showCreateProject.value = false
}
const submitCreateProject = (payload: { name: string; campaignName: string }) => {
projectsStore.createProject(payload)
showCreateProject.value = false
}
</script>
<template>
<div class="space-y-5">
<header class="rounded-xl border border-slate-700/80 bg-gradient-to-r from-slate-900 via-slate-900 to-indigo-950/70 px-5 py-4 shadow-[0_16px_40px_rgba(0,0,0,0.35)]">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<p class="text-sm text-slate-400"> <span class="px-2">></span> 프로젝트</p>
<h1 class="mt-1 text-3xl font-black tracking-tight text-slate-100">프로젝트 그룹</h1>
</div>
<button
type="button"
@click="openCreateModal"
class="rounded-xl bg-gradient-to-r from-indigo-500 to-cyan-500 px-5 py-2.5 text-base font-semibold text-white shadow-lg shadow-cyan-950/40 transition hover:from-indigo-400 hover:to-cyan-400"
>
+ 프로젝트 만들기
</button>
</div>
</header>
<section class="rounded-2xl border border-indigo-400/20 bg-gradient-to-br from-slate-900/80 via-slate-950 to-slate-900/90 p-5 shadow-[0_12px_30px_rgba(0,0,0,0.3)]">
<div class="flex flex-nowrap items-end justify-between gap-4">
<label class="min-w-0 space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">검색</span>
<div class="relative">
<span class="pointer-events-none absolute inset-y-0 left-3 flex items-center text-cyan-300"></span>
<input
type="text"
placeholder="이름 또는 태그로 검색"
v-model="searchQuery"
class="w-full min-w-[520px] rounded-xl border border-indigo-500/20 bg-slate-950/80 px-9 py-3 text-sm text-slate-100 outline-none ring-0 transition focus:border-cyan-400 focus:shadow-[0_0_0_1px_rgba(103,232,249,0.35)]"
/>
</div>
</label>
<div class="ml-auto flex shrink-0 items-end gap-3">
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">상태</span>
<select
v-model="statusFilter"
class="shrink-0 rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400"
>
<option
v-for="status in statusOptions"
:key="status.value"
:value="status.value"
>
{{ status.label }}
</option>
</select>
</label>
<label class="flex flex-col space-y-2">
<span class="px-1 text-xs font-semibold tracking-[0.12em] text-cyan-200">정렬</span>
<select
v-model="sortBy"
class="shrink-0 rounded-xl border border-indigo-500/25 bg-slate-950/80 px-5 py-3 text-sm font-medium text-slate-200 outline-none transition hover:border-cyan-400"
>
<option v-for="option in sortOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</label>
<button
type="button"
class="shrink-0 rounded-xl border border-cyan-300/30 bg-gradient-to-r from-cyan-500/20 via-indigo-500/20 to-purple-500/20 px-5 py-3 text-sm font-semibold whitespace-nowrap text-cyan-50 transition hover:bg-slate-800 hover:border-cyan-200/50"
>
필터
</button>
</div>
</div>
</section>
<section class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<article
v-for="project in projects"
:key="project.id"
class="relative overflow-hidden rounded-2xl border border-slate-800 bg-gradient-to-b from-slate-900/95 to-slate-950/95 p-5 shadow-lg shadow-black/20 transition hover:-translate-y-0.5 hover:border-cyan-300/40 before:absolute before:bottom-0 before:left-0 before:top-0 before:w-[3px] before:rounded-l-2xl"
:class="cardAccent(project.status)"
>
<div class="flex items-start justify-between gap-2">
<div>
<h2 class="text-xl font-bold text-slate-100">{{ project.title }}</h2>
<p class="mt-1 text-sm text-slate-400">{{ project.subtitle }}</p>
</div>
<span class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-semibold tracking-[0.08em]" :class="statusClass(project.status)" style="font-size: 10px; line-height: 1.1;">
<span class="h-1.5 w-1.5 rounded-full" :class="statusDotClass(project.status)" />
{{ statusLabel(project.status) }}
</span>
</div>
<div class="mt-4 gap-3" style="display:grid;grid-template-columns:1fr 1fr;">
<div class="min-w-0 rounded-xl border border-slate-800 bg-slate-950/80 p-3">
<p class="text-xs tracking-wider text-slate-400"> 리드</p>
<p class="mt-1 text-3xl font-black text-slate-100">{{ project.leads }}</p>
</div>
<div class="min-w-0 rounded-xl border border-slate-800 bg-slate-950/80 p-3">
<p class="text-xs tracking-wider text-slate-400">{{ project.metricLabel }}</p>
<p class="mt-1 text-3xl font-black text-slate-100">{{ project.metricValue }}</p>
<p class="mt-1 text-sm" :class="project.trendUp ? 'text-emerald-300' : 'text-slate-400'">{{ project.trend }}</p>
</div>
</div>
<div
class="flex items-center justify-between border-t border-slate-700/60 pt-4"
style="margin-top: 28px;"
>
<p class="text-sm text-slate-400">{{ project.variants }} 변형 테스트</p>
<div class="flex items-center gap-2">
<NuxtLink
:to="`/admin/pages?campaignId=${project.id}`"
class="rounded-md border border-slate-700/80 bg-slate-900 px-3 py-1.5 text-sm text-slate-200 transition hover:bg-slate-800/70"
>
페이지 관리
</NuxtLink>
<NuxtLink
v-if="getVariantPageId(project.id)"
:to="`/admin/pages/${getVariantPageId(project.id)}/variants`"
class="rounded-md bg-gradient-to-r from-indigo-600 to-cyan-500 px-3 py-1.5 text-sm font-medium text-white hover:from-indigo-500 hover:to-cyan-400"
>
변형 관리
</NuxtLink>
<button
v-else
type="button"
class="rounded-md bg-slate-700 px-3 py-1.5 text-sm text-slate-300"
disabled
>
변형 없음
</button>
</div>
</div>
</article>
<article
class="flex min-h-[320px] items-center justify-center rounded-2xl border border-dashed border-cyan-300/30 bg-gradient-to-b from-slate-900 to-indigo-950/60 p-5"
>
<div class="text-center">
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-r from-indigo-400/20 to-cyan-400/20 text-2xl text-cyan-200">+</div>
<p class="text-2xl font-bold text-slate-100"> 그룹 만들기</p>
<p class="mt-2 text-slate-400"> A/B 테스트나 캠페인을 시작하세요</p>
<button
type="button"
class="mx-auto mt-4 rounded-md bg-gradient-to-r from-indigo-600 to-cyan-500 px-4 py-2 text-sm font-semibold text-white"
@click="openCreateModal"
>
바로 생성하기
</button>
</div>
</article>
</section>
<footer class="mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-slate-800 pt-4">
<p class="text-slate-400"> 12 1~4 표시</p>
<div class="flex items-center gap-2">
<button class="rounded-lg border border-slate-700 px-3 py-1.5 text-slate-300 hover:bg-slate-900">이전</button>
<button class="rounded-lg border border-indigo-300/30 bg-indigo-900/50 px-3 py-1.5 text-cyan-100">1</button>
<button class="rounded-lg border border-slate-700 px-3 py-1.5 text-slate-300 hover:bg-slate-900">2</button>
<button class="rounded-lg border border-slate-700 px-3 py-1.5 text-slate-300 hover:bg-slate-900">3</button>
<button class="rounded-lg border border-slate-700 px-3 py-1.5 text-slate-300 hover:bg-slate-900">다음</button>
</div>
</footer>
<CreateProjectModal
v-if="showCreateProject"
@close="closeCreateModal"
@submit="submitCreateProject"
/>
</div>
</template>

View File

@@ -0,0 +1,90 @@
<template>
<div class="min-h-screen bg-slate-950 text-slate-100">
<section class="relative overflow-hidden bg-gradient-to-b from-slate-950 via-slate-900 to-slate-950">
<div class="pointer-events-none absolute -left-28 top-[-130px] h-72 w-72 rounded-full bg-cyan-400/20 blur-3xl"></div>
<div class="pointer-events-none absolute right-[-100px] bottom-[-120px] h-80 w-80 rounded-full bg-indigo-400/15 blur-3xl"></div>
<div class="mx-auto flex min-h-screen max-w-6xl flex-col justify-between px-6 py-10 md:px-10">
<header class="flex items-center justify-between">
<p class="text-xs font-bold tracking-[0.2em] text-slate-300">LANDING MANAGER</p>
<NuxtLink
to="/admin"
class="rounded-md border border-slate-700 px-4 py-2 text-sm text-slate-200 transition hover:bg-slate-800"
>
관리자 바로가기
</NuxtLink>
</header>
<main class="grid gap-10 py-16 md:py-24 lg:grid-cols-2 lg:items-center">
<div class="space-y-6">
<p class="inline-flex rounded-full border border-cyan-400/30 bg-cyan-400/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.2em] text-cyan-200">
광고 랜딩 운영 시작점
</p>
<h1 class="text-4xl font-black leading-tight md:text-5xl">
캠페인 기반 랜딩 페이지를
<span class="text-cyan-300">빠르게</span> 운영하세요
</h1>
<p class="max-w-xl text-lg text-slate-300">
캠페인, 페이지, 도메인 매핑, 조건 분기까지 곳에서 관리하는 어드민 구조를
페이지 단위로 점진적으로 구성합니다.
</p>
<div class="flex flex-wrap gap-3">
<NuxtLink
to="/admin"
class="rounded-md bg-cyan-400 px-4 py-2 font-semibold text-slate-950 transition hover:bg-cyan-300"
>
관리자 대시보드 시작
</NuxtLink>
<NuxtLink
to="/"
class="rounded-md border border-slate-700 px-4 py-2 text-slate-200 transition hover:bg-slate-800"
>
가이드 보기
</NuxtLink>
</div>
</div>
<section class="rounded-2xl border border-slate-700 bg-slate-900/60 p-6 backdrop-blur">
<h2 class="text-sm font-bold uppercase tracking-[0.15em] text-cyan-300">현재 설계 범위</h2>
<p class="mt-2 text-slate-200">1단계: 기본 화면 + 관리 기본 페이지를 하나씩 생성</p>
<ul class="mt-4 space-y-3 text-sm text-slate-300">
<li class="rounded-md border border-slate-800 p-3">1) (현재 페이지)</li>
<li class="rounded-md border border-slate-800 p-3">2) 관리자 시작 페이지(다음)</li>
<li class="rounded-md border border-slate-800 p-3">3) 캠페인 목록</li>
<li class="rounded-md border border-slate-800 p-3">4) 페이지 상세/빌더</li>
</ul>
</section>
</main>
</div>
</section>
<section class="mx-auto max-w-6xl px-6 pb-16 md:px-10">
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<article class="rounded-xl border border-slate-700 bg-slate-900 p-5">
<p class="text-xs uppercase text-slate-400">목표</p>
<h3 class="mt-1 text-xl font-bold">광고 집행용 랜딩 운영</h3>
<p class="mt-2 text-sm text-slate-300">
유입 페이지가 많아져도 관리가 쉬운 구조로 캠페인 단위 관리 흐름을 만든다.
</p>
</article>
<article class="rounded-xl border border-slate-700 bg-slate-900 p-5">
<p class="text-xs uppercase text-slate-400">기능</p>
<h3 class="mt-1 text-xl font-bold">도메인 분기 조건 분기</h3>
<p class="mt-2 text-sm text-slate-300">
호스트/경로 분기 + 요일/시간 조건 분기를 기반으로 보여줄 페이지를 제어한다.
</p>
</article>
<article class="rounded-xl border border-slate-700 bg-slate-900 p-5">
<p class="text-xs uppercase text-slate-400">철학</p>
<h3 class="mt-1 text-xl font-bold">페이지는 작게, 운영은 빠르게</h3>
<p class="mt-2 text-sm text-slate-300">
번에 완성보다, 페이지 하나씩 완성하면서 안정적으로 확장한다.
</p>
</article>
</div>
</section>
</div>
</template>

View File

@@ -0,0 +1,69 @@
import { defineStore } from 'pinia'
export interface KpiCard {
label: string
value: string
note: string
color: string
band: string
border: string
}
export interface RunningProject {
name: string
domain: string
status: string
leads: number
}
export const useDashboardStore = defineStore('admin-dashboard', {
state: () => ({
kpiCards: [
{
label: '진행중 프로젝트',
value: '5',
note: '최근 7일 내 변경 3건',
color: 'text-cyan-300',
band: 'from-cyan-300/80 to-cyan-200/20',
border: 'border-cyan-300/25'
},
{
label: '전체 프로젝트',
value: '18',
note: '캠페인 기준 총 18개',
color: 'text-fuchsia-200',
band: 'from-fuchsia-300/80 to-fuchsia-200/20',
border: 'border-fuchsia-300/25'
},
{
label: '새로운 리드',
value: '24',
note: '오늘 신규 유입',
color: 'text-emerald-300',
band: 'from-emerald-300/80 to-emerald-200/20',
border: 'border-emerald-300/25'
}
],
trendBars: [12, 20, 16, 26, 18, 24],
runningProjects: [
{
name: '여름 프로모션',
domain: 'summer.ad-camp.kr',
status: '운영중',
leads: 42
},
{
name: '주말 특가 랜딩',
domain: 'weekend.offer.kr',
status: '테스트',
leads: 13
},
{
name: '리타겟팅 A',
domain: 'retarget.example.com',
status: '준비중',
leads: 0
}
] as RunningProject[]
})
})

View File

@@ -0,0 +1,242 @@
import { defineStore } from 'pinia'
export type PageStatus = 'live' | 'draft' | 'paused'
export type PageSortBy =
| 'updated'
| 'name'
| 'leads'
| 'visitors'
| 'variants'
export interface CampaignLabel {
id: string
name: string
}
export interface AdminPage {
id: string
campaignId: string
name: string
domain: string
routePath: string
status: PageStatus
leadCount: number
visitorCount: number
variantCount: number
updatedAt: string
}
const campaigns: CampaignLabel[] = [
{ id: 'p1', name: 'Q3 마케팅 캠페인' },
{ id: 'p2', name: '블랙프라이데이 2023' },
{ id: 'p3', name: 'SaaS 웨비나 시리즈' },
{ id: 'p4', name: 'Product Hunt 론칭' }
]
const pageSource: AdminPage[] = [
{
id: 'lp01',
campaignId: 'p1',
name: '메인 랜딩',
domain: 'summer.ad-camp.kr',
routePath: '/',
status: 'live',
leadCount: 124,
visitorCount: 2400,
variantCount: 2,
updatedAt: '2026-03-04T09:12:00.000Z'
},
{
id: 'lp02',
campaignId: 'p1',
name: '오퍼 페이지 B',
domain: 'summer.ad-camp.kr',
routePath: '/offer',
status: 'draft',
leadCount: 16,
visitorCount: 420,
variantCount: 1,
updatedAt: '2026-03-03T18:05:00.000Z'
},
{
id: 'lp03',
campaignId: 'p2',
name: '구글 전용 랜딩',
domain: 'weekend.offer.kr',
routePath: '/google',
status: 'live',
leadCount: 88,
visitorCount: 1210,
variantCount: 3,
updatedAt: '2026-03-01T13:40:00.000Z'
},
{
id: 'lp04',
campaignId: 'p2',
name: '일반 버전',
domain: 'weekend.offer.kr',
routePath: '/',
status: 'paused',
leadCount: 22,
visitorCount: 540,
variantCount: 1,
updatedAt: '2026-02-28T06:25:00.000Z'
},
{
id: 'lp05',
campaignId: 'p4',
name: '리타겟 랜딩',
domain: 'retarget.example.com',
routePath: '/',
status: 'draft',
leadCount: 0,
visitorCount: 40,
variantCount: 1,
updatedAt: '2026-03-02T22:17:00.000Z'
}
]
const statusRank: Record<PageStatus, number> = {
live: 0,
draft: 1,
paused: 2
}
const statusLabel = (status: PageStatus) => {
if (status === 'live') {
return '라이브'
}
if (status === 'paused') {
return '일시정지'
}
return '초안'
}
export const usePagesStore = defineStore('admin-pages', {
state: () => ({
pages: pageSource,
campaigns,
searchQuery: '',
campaignFilter: 'all' as 'all' | string,
statusFilter: 'all' as 'all' | PageStatus,
sortBy: 'updated' as PageSortBy
}),
getters: {
filteredPages: (state): AdminPage[] => {
const query = state.searchQuery.trim().toLowerCase()
const result = state.pages
.filter((page) => {
const text = `${page.name} ${page.domain} ${page.routePath}`.toLowerCase()
const searchOk = query === '' || text.includes(query)
const campaignOk = state.campaignFilter === 'all' || page.campaignId === state.campaignFilter
const statusOk = state.statusFilter === 'all' || page.status === state.statusFilter
return searchOk && campaignOk && statusOk
})
.sort((a, b) => {
if (state.sortBy === 'name') {
return a.name.localeCompare(b.name)
}
if (state.sortBy === 'leads') {
return b.leadCount - a.leadCount
}
if (state.sortBy === 'visitors') {
return b.visitorCount - a.visitorCount
}
if (state.sortBy === 'variants') {
return b.variantCount - a.variantCount
}
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
})
return result
},
campaignOptions: (state): CampaignLabel[] => state.campaigns
},
actions: {
setSearchQuery(value: string) {
this.searchQuery = value
},
setCampaignFilter(value: 'all' | string) {
this.campaignFilter = value
},
setStatusFilter(value: 'all' | PageStatus) {
this.statusFilter = value
},
setSortBy(value: PageSortBy) {
this.sortBy = value
},
removePage(pageId: string) {
this.pages = this.pages.filter((page) => page.id !== pageId)
},
createQuickPage() {
const campaign = this.campaigns[this.pages.length % this.campaigns.length]
this.createPage({
name: `새 페이지 ${this.pages.length + 1}`,
campaignId: campaign.id,
domain: 'ad-camp-temp.test',
routePath: `/lp-${this.pages.length + 1}`
})
},
createPage(payload: {
name: string
campaignId: string
domain: string
routePath: string
}) {
const normalizedRoute = payload.routePath.trim().startsWith('/') ? payload.routePath.trim() : `/${payload.routePath.trim()}`
const normalizedDomain = payload.domain.trim()
const newPage: AdminPage = {
id: `lp-${Date.now()}`,
campaignId: payload.campaignId,
name: payload.name.trim(),
domain: normalizedDomain,
routePath: normalizedRoute,
status: 'draft',
leadCount: 0,
visitorCount: 0,
variantCount: 1,
updatedAt: new Date().toISOString()
}
this.pages.unshift(newPage)
},
statusClass(status: PageStatus) {
if (status === 'live') {
return {
label: statusLabel(status),
chipClass: 'text-emerald-200 bg-emerald-400/10',
dotClass: 'bg-emerald-300'
}
}
if (status === 'paused') {
return {
label: statusLabel(status),
chipClass: 'text-slate-300 bg-slate-500/10',
dotClass: 'bg-slate-400'
}
}
return {
label: statusLabel(status),
chipClass: 'text-amber-200 bg-amber-400/10',
dotClass: 'bg-amber-300'
}
},
campaignName(campaignId: string) {
return (
this.campaigns.find((campaign) => campaign.id === campaignId)?.name || campaignId || '미지정'
)
},
previewUrl(page: AdminPage) {
return `https://${page.domain}${page.routePath}`
}
}
})

View File

@@ -0,0 +1,156 @@
import { defineStore } from 'pinia'
export type ProjectStatus = 'active' | 'archived' | 'draft'
export type ProjectSortBy = 'newest' | 'oldest' | 'most-leads' | 'most-variants'
export type ProjectStatusFilter = 'all' | ProjectStatus
export interface ProjectCard {
id: string
title: string
subtitle: string
status: ProjectStatus
leads: string
metricLabel: string
metricValue: string
trend: string
trendUp: boolean
variants: number
createdAt: string
}
const projectSource: ProjectCard[] = [
{
id: 'p1',
title: 'Q3 마케팅 캠페인',
subtitle: '2일 전 생성',
status: 'active',
leads: '1,240',
metricLabel: '전환율',
metricValue: '4.8%',
trend: '+12%',
trendUp: true,
variants: 3,
createdAt: '2026-03-01T09:00:00.000Z'
},
{
id: 'p2',
title: '블랙프라이데이 2023',
subtitle: '2023년 12월 1일 종료',
status: 'archived',
leads: '5,100',
metricLabel: '방문자',
metricValue: '42.5k',
trend: '종료',
trendUp: false,
variants: 3,
createdAt: '2023-12-01T03:00:00.000Z'
},
{
id: 'p3',
title: 'SaaS 웨비나 시리즈',
subtitle: '4시간 전 수정됨',
status: 'draft',
leads: '0',
metricLabel: '노출 수',
metricValue: '12k',
trend: '+18%',
trendUp: true,
variants: 2,
createdAt: '2026-03-04T01:20:00.000Z'
},
{
id: 'p4',
title: 'Product Hunt 런칭',
subtitle: '2주간 운영중',
status: 'active',
leads: '892',
metricLabel: '클릭률',
metricValue: '2.1%',
trend: '-2%',
trendUp: false,
variants: 1,
createdAt: '2026-02-12T16:30:00.000Z'
}
]
const toLeadNumber = (value: string) => {
const trimmed = value.trim().toLowerCase().replace(/,/g, '')
const numeric = Number(trimmed.replace('k', ''))
if (Number.isNaN(numeric)) {
return 0
}
return trimmed.endsWith('k') ? numeric * 1000 : numeric
}
export const useProjectsStore = defineStore('admin-projects', {
state: () => ({
projects: projectSource,
searchQuery: '',
statusFilter: 'all' as ProjectStatusFilter,
sortBy: 'newest' as ProjectSortBy
}),
getters: {
filteredProjects: (state): ProjectCard[] => {
const query = state.searchQuery.trim().toLowerCase()
const list = state.projects
.filter((project) => {
const target = `${project.title} ${project.subtitle}`.toLowerCase()
const matchQuery = query === '' || target.includes(query)
const matchStatus =
state.statusFilter === 'all' || project.status === state.statusFilter
return matchQuery && matchStatus
})
.sort((a, b) => {
if (state.sortBy === 'oldest') {
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
}
if (state.sortBy === 'most-leads') {
return toLeadNumber(b.leads) - toLeadNumber(a.leads)
}
if (state.sortBy === 'most-variants') {
return b.variants - a.variants
}
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
})
return list
}
},
actions: {
createProject(input: { name: string; campaignName: string }) {
const now = new Date().toISOString()
const id = `p-${Date.now().toString(36)}`
this.projects.unshift({
id,
title: input.name.trim(),
subtitle: `${input.campaignName.trim()}에서 생성`,
status: 'draft',
leads: '0',
metricLabel: '전환율',
metricValue: '-',
trend: '신규',
trendUp: true,
variants: 0,
createdAt: now
})
},
setSearchQuery(value: string) {
this.searchQuery = value
},
setStatusFilter(value: ProjectStatusFilter) {
this.statusFilter = value
},
setSortBy(value: ProjectSortBy) {
this.sortBy = value
}
}
})

View File

@@ -0,0 +1,238 @@
import { defineStore } from 'pinia'
export interface PageVariant {
id: string
pageId: string
name: string
description: string
trafficWeight: number
isActive: boolean
leadCount: number
updatedAt: string
}
export interface CreateVariantInput {
pageId: string
name: string
description: string
trafficWeight: number
}
export interface UpdateVariantInput {
id: string
pageId: string
name?: string
description?: string
trafficWeight?: number
isActive?: boolean
leadCount?: number
}
type VariantMap = Record<string, PageVariant[]>
const toVariantIso = (value = 0) => {
const date = new Date()
date.setSeconds(date.getSeconds() + value)
return date.toISOString()
}
const variantSeed: VariantMap = {
lp01: [
{
id: 'v-lp01-a',
pageId: 'lp01',
name: '일반형',
description: '현재 운영 중인 기본 랜딩 버전',
trafficWeight: 70,
isActive: true,
leadCount: 76,
updatedAt: toVariantIso(-1200)
},
{
id: 'v-lp01-b',
pageId: 'lp01',
name: '강한 메시지형',
description: 'CTA 문구를 자극적으로 강화한 변형',
trafficWeight: 30,
isActive: true,
leadCount: 48,
updatedAt: toVariantIso(-900)
}
],
lp02: [
{
id: 'v-lp02-a',
pageId: 'lp02',
name: '기본 오퍼',
description: '초안 상태 테스트용 템플릿',
trafficWeight: 100,
isActive: true,
leadCount: 16,
updatedAt: toVariantIso(-2000)
}
],
lp03: [
{
id: 'v-lp03-a',
pageId: 'lp03',
name: '구글 버전 A',
description: '광고 채널별로 분기되는 기본 버전',
trafficWeight: 50,
isActive: true,
leadCount: 58,
updatedAt: toVariantIso(-1300)
},
{
id: 'v-lp03-b',
pageId: 'lp03',
name: '구글 버전 B',
description: '강조 문구/이미지를 변경한 실험군',
trafficWeight: 30,
isActive: true,
leadCount: 22,
updatedAt: toVariantIso(-800)
},
{
id: 'v-lp03-c',
pageId: 'lp03',
name: '구글 버전 C',
description: '폼 위치를 상단으로 이동한 변형',
trafficWeight: 20,
isActive: true,
leadCount: 8,
updatedAt: toVariantIso(-500)
}
],
lp04: [
{
id: 'v-lp04-a',
pageId: 'lp04',
name: '저녁 타겟형',
description: '기본 버전',
trafficWeight: 100,
isActive: false,
leadCount: 22,
updatedAt: toVariantIso(-700)
}
],
lp05: [
{
id: 'v-lp05-a',
pageId: 'lp05',
name: '리타겟 기본형',
description: '리마케팅 유입용 기본 템플릿',
trafficWeight: 100,
isActive: true,
leadCount: 0,
updatedAt: toVariantIso(-1000)
}
]
}
const clampTrafficWeight = (value: number) => {
if (!Number.isFinite(value)) {
return 1
}
if (value < 1) {
return 1
}
if (value > 100) {
return 100
}
return Math.round(value)
}
export const useVariantsStore = defineStore('admin-variants', {
state: () => ({
variantsByPage: variantSeed as VariantMap
}),
getters: {
listByPage: (state) => (pageId: string) => {
const list = state.variantsByPage[pageId] ?? []
return [...list].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
},
countByPage: (state) => (pageId: string) => {
return state.variantsByPage[pageId]?.length ?? 0
},
totalLeadsByPage: (state) => (pageId: string) => {
return (state.variantsByPage[pageId] ?? []).reduce((sum, item) => sum + item.leadCount, 0)
}
},
actions: {
createVariant(payload: CreateVariantInput) {
const normalizedName = payload.name.trim()
const pageId = payload.pageId.trim()
const description = payload.description.trim()
if (pageId.length === 0 || normalizedName.length === 0) {
return
}
const pageVariants = this.variantsByPage[pageId] ?? []
const newVariant: PageVariant = {
id: `v-${Date.now().toString(36)}`,
pageId,
name: normalizedName,
description,
trafficWeight: clampTrafficWeight(payload.trafficWeight),
isActive: true,
leadCount: 0,
updatedAt: new Date().toISOString()
}
this.variantsByPage[pageId] = [newVariant, ...pageVariants]
},
updateVariant(payload: UpdateVariantInput) {
const pageId = payload.pageId.trim()
const variants = this.variantsByPage[pageId]
if (!variants) {
return
}
const idx = variants.findIndex((item) => item.id === payload.id)
if (idx === -1) {
return
}
const target = variants[idx]
variants[idx] = {
...target,
...payload,
updatedAt: new Date().toISOString(),
pageId
}
},
toggleActive(pageId: string, variantId: string) {
const variants = this.variantsByPage[pageId]
if (!variants) {
return
}
const idx = variants.findIndex((item) => item.id === variantId)
if (idx === -1) {
return
}
variants[idx] = {
...variants[idx],
isActive: !variants[idx].isActive,
updatedAt: new Date().toISOString()
}
},
removeVariant(pageId: string, variantId: string) {
const variants = this.variantsByPage[pageId]
if (!variants) {
return
}
this.variantsByPage[pageId] = variants.filter((variant) => variant.id !== variantId)
}
}
})

1934
frontend/bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(
// Your custom configs here
)

View File

@@ -0,0 +1,505 @@
# Landing Admin Page Log
관리자 페이지를 새로 만들거나 기능을 추가할 때마다 페이지별로 기록합니다.
## 기록 규칙
- 작성 시각
- 페이지 경로
- 목적
- 라우트
- 구성 항목
- 비고/다음 작업
## 2026-03-03
- 페이지: `/` 홈 랜딩
- 타입: 기본 페이지(비관리자)
- 노트: 초기 기본 레이아웃을 마크업으로 구성했고, 이후 `/admin` 계열부터 페이지 단위 기록은 여기서 계속 누적.
## 2026-03-03
- 페이지: `/admin` 대시보드
- 타입: 관리자 진입점 + 공통 레이아웃
- 라우트: `/admin`, `/admin/index`
- 구성:
- 사이드바 고정형 레이아웃 (`pages/admin.vue`) 추가
- 대시보드 메인 카드 3종(진행중 프로젝트, 전체 프로젝트, 새로운 리드)
- 진행중 프로젝트 테이블 요약 섹션 추가
- 비고:
- 1단계 MVP에서 API 연동 없이 정적 상태로 구성
- 다음 작업: `/admin/projects`, `/admin/pages`, `/admin/leads` 페이지 순차 추가
## 2026-03-03
- 페이지: `/admin` 대시보드(레이아웃 개선)
- 타입: 관리자 대시보드 UI 개선
- 라우트: `/admin`
- 구성:
- 상단 헤더 + 우측 액션 버튼을 1줄 배치
- KPI 3개 카드 + 추이 카드/미니 차트 구간을 2열로 구성하여 세로 몰림 완화
- 하단 목록/빠른 액션 영역을 2열로 나눠 가로형 화면 비중 확대
- 비고: 사용자 요청 반영(“너무 세로로 구성되어 있음” 해결)
## 2026-03-03
- 페이지: `/admin` 대시보드 KPI 라인 정렬
- 타입: 카드 배치 조정
- 라우트: `/admin`
- 구성:
- KPI 카드 3개(진행중 프로젝트/전체 프로젝트/새로운 리드)를 같은 라인(grid-cols-3)으로 고정 배치
- 비고: 동일 라인 요청 반영
## 2026-03-04
- 페이지: `/admin/projects`
- 타입: 프로젝트 그룹 보드
- 라우트: `/admin/projects`
- 구성:
- 상단 브레드크럼 + 타이틀 + `Create New Group` 버튼
- 검색/상태/정렬/필터 바
- 카드형 프로젝트 목록(상태 배지, 메트릭 2열, 하단 액션 버튼)
- `Create New Group` 대시드 카드
- 하단 페이지네이션 바
- 비고:
- 사용자 제공 레퍼런스 이미지 기반으로 다크 보드 스타일 구성
- 현재는 정적 더미 데이터 기반, 이후 API 연동 예정
## 2026-03-04
- 페이지: `/admin/projects` 상태 표시/디자인 톤 보정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 상태 표시를 큰 배지에서 `작은 dot + label` 형태로 변경
- 카드 두께/폰트 크기/메트릭 박스 크기 축소로 덜 투박하게 정리
- `Create New Group` 원형 아이콘 크기 축소
- 비고: 사용자 피드백(원형 과대, 전체 투박함) 반영
## 2026-03-04
- 페이지: `/admin/projects` 메트릭 정렬/상태 텍스트 크기 조정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 카드 내 `TOTAL LEADS`와 우측 메트릭 박스를 `flex` 기반 좌우 정렬로 고정
- 상태 라벨 폰트 크기 축소(`text-[10px]`) 및 패딩 축소
- 비고: 사용자 피드백(좌우 배치/상태 글자 크기) 반영
## 2026-03-04
- 페이지: `/admin/projects` 메트릭 50:50 폭 고정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 카드 메트릭 박스 2개를 `w-1/2 basis-1/2`로 고정하여 정확히 반반 분할
- 비고: 사용자 피드백(한쪽 쏠림) 반영
## 2026-03-04
- 페이지: `/admin/projects` 메트릭 반반 분할 방식 보정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- `flex` 분할을 `grid grid-cols-2`로 변경해 gap 포함 상태에서도 정확히 50:50 유지
- 비고: 사용자 피드백(여전히 반반 아님) 재반영
## 2026-03-04
- 페이지: `/admin/projects` 메트릭 2열 강제 고정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 메트릭 래퍼에 인라인 `display:grid; grid-template-columns: 1fr 1fr;` 적용
- Tailwind 클래스 해석/브레이크포인트 영향 없이 무조건 좌우 반반 유지
- 비고: 사용자 피드백(여전히 위아래 배치됨) 재반영
## 2026-03-04
- 페이지: `/admin/projects` 하단 액션/상태 라벨 조정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 카드 하단 `Variants Tested` 라인과 버튼 간격을 늘려 버튼 위쪽 마진 추가
- 상태 배지 텍스트를 더 작은 크기로 축소
- 비고: 사용자 요청 반영(“tested copy url variants 버튼 윗쪽 마진, 상태 표시 더 작게")
## 2026-03-04
- 페이지: `/admin/projects` 하단 액션 여백 추가 강화
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 하단 액션 래퍼(`Variants/Copy URL` 버튼 행) 상단 마진을 기존 대비 더 크게(`mt-6`) 상향
- 비고: “그대로 딱 붙어있다”는 피드백 반영
## 2026-03-04
- 페이지: `/admin/projects` 하단 액션 강제 간격 적용
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 하단 버튼 행에 유틸 클래스 기반 `mt-`을 제거하고 `style="margin-top: 48px;"` 인라인 마진 강제 적용
- "tested copy url variants" 구간 분리를 즉시 체감 가능한 수치로 조정
- 비고: 변경사항이 반영 안됐다는 피드백에 대한 재반영
## 2026-03-04
- 페이지: `/admin/projects` 하단 액션 간격 과다 완화
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 하단 액션 행 인라인 마진을 `48px`에서 `24px`로 축소
- 비고: “너무 많이 떨어짐” 피드백 반영
## 2026-03-04
- 페이지: `/admin/projects` 하단 액션 간격 재상향 조정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 하단 액션 행 인라인 마진을 `24px`에서 `28px`로 증가
- 비고: “좀더 올려줘” 피드백 반영
## 2026-03-04
- 페이지: `/admin/projects` 하단 간격 보장 방식 변경
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- `Variants Tested` 블록과 하단 액션 행 사이에 고정 높이 공백 블록(`h-6`) 추가
- 유틸 클래스/인라인 마진 조합과 함께 간격이 누락되지 않도록 구조적으로 보강
- 비고: 간격 변경이 화면에 반영되지 않는 피드백의 재반영
## 2026-03-04
- 페이지: `/admin/projects` 페이지네이션 위치 조정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 페이지네이션(`footer`) 위쪽에 `mt-4` 여백 추가
- 비고: 카드 본문보다 약간 아래쪽으로 페이지네이션을 띄우는 요청 반영
## 2026-03-04
- 페이지: `/admin/projects` 상태 라벨 크기 추가 축소
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 상태 배지 텍스트 사이즈를 `text-[9px]`에서 `text-[8px]`로 축소
- 비고: `ACTIVE/ARCHIVED/DRAFT` 라벨이 더 작게 보이도록 조정
## 2026-03-04
- 페이지: `/admin/projects` 상태 라벨 직접 크기 강제 적용
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 상태 배지에 `style="font-size: 8px; line-height: 1;"` 직접 지정
- 비고: Tailwind 유틸이 반영 안될 때도 체감되도록 강제 적용
## 2026-03-04
- 페이지: `/admin/projects` 상태 라벨 크기 상향 조정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 상태 배지 인라인 폰트 크기를 `8px`에서 `10px`로, `line-height``1.1`로 조정
- 비고: “너무 작다” 피드백 반영
## 2026-03-04
- 페이지: `/admin/projects` 검색/필터 패널 디자인 개선
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 검색 영역을 입력 박스 + 라벨 + 통일된 필터 칩 스타일의 카드형 패널로 교체
- 검색창에 아이콘 유사 요소 및 focus 보더/그림자 적용
- 상태/정렬/필터 버튼을 동일한 반원각/톤으로 정돈
- 비고: “위쪽 검색하는 부분 이쁘게” 요청 반영
## 2026-03-04
- 페이지: `/admin/projects` 검색/필터 가로 고정 정렬
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 검색/필터 블록을 `flex-nowrap` 1줄 레이아웃으로 변경
- 버튼에 `shrink-0`/`whitespace-nowrap` 적용해 라인 분리 없이 가로 유지
- 검색 입력 최소 너비 지정으로 한 줄 안정성 강화
- 비고: “가로로 생겨야지” 요청 반영
## 2026-03-04
- 페이지: `/admin/projects` 검색/필터 패널 패딩 보정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 검색 패널 `p-4``p-5`, 내부 간격을 `gap-4`로 강화
- 검색 라벨 간격 및 입력창 패딩(`px-9 py-3`) 확장
- 버튼 패딩(`px-5 py-3`) 상향으로 시각적 여유 확보
- 비고: “패딩 주고 보기 이쁘게” 요청 반영
## 2026-03-04
- 페이지: `/admin/projects` 검색/필터 정렬 조정
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 검색 입력 최소 너비를 `360px`로 증가
- 상단 블록을 `justify-between` 처리 후 버튼 그룹에 `ml-auto` 적용해 Status~Filter를 오른쪽 정렬
- `Status / Sort / Filter`를 한 묶음으로 묶어 오른쪽 정렬 유지
- 비고: “검색란 넓히고 status부터는 오른쪽 정렬” 요청 반영
## 2026-03-04
- 페이지: `/admin/projects` Sort/Filter 컨트롤 타입 정리
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- `Status`를 select 박스로 유지
- `Sort by`를 select 박스로 전환
- `Filter`는 버튼으로 유지
## 2026-03-04
- 페이지: `/admin/projects` Filter 버튼 모양 정리
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- `Filter` 버튼의 그라디언트 스타일 제거 후, 다른 입력 컨트롤과 톤을 맞춘 플랫 보더 스타일로 통일
## 2026-03-04
- 페이지: `/admin/projects` 검색 입력 폭 추가 확대
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- 검색 input 최소 너비를 `520px`로 상향
- 비고: “검색박스 좀 길게” 요청 반영
## 2026-03-04
- 페이지: `/admin/projects` Status 컨트롤 select 전환
- 타입: UI polish
- 라우트: `/admin/projects`
- 구성:
- `Status: All` 버튼을 라벨 + `select` 박스로 변경
- 옵션: All / Active / Archived / Draft
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 관리자 페이지 편집
- 라우트: `/admin/pages/:id/builder`
- 구성:
- `빌더 열기` 버튼을 페이지 목록에서 해당 route로 이동하도록 연결
- 동적 라우트 페이지 추가
- 페이지 메타(이름/캠페인/도메인/경로/상태) 표시
- 모바일 폭 기준 미리보기 카드형 캔버스 + 블록 리스트 영역 구성
- 블록 추가/위/아래 이동/삭제 인터랙션 추가
- 빈 상태 시 “페이지를 찾을 수 없음” 대응 처리
## 2026-03-04
- 페이지: `/admin/pages` 새 페이지 생성 기능
- 타입: UI/기능 개선
- 라우트: `/admin/pages`
- 구성:
- `CreatePageModal` 컴포넌트 추가
- 입력 필드: 페이지명, 프로젝트, 도메인, 경로
- 배경 클릭/닫기 버튼/취소 버튼으로 모달 종료
- `/admin/pages` 생성 버튼과 빈 상태 카드의 “바로 만들기”에 모달 연동
- `pages` store에 `createPage` 액션 추가
- 수동 입력 기반 페이지 생성(상태: `draft`, 리드/방문자/버전=0/0/1)
- 기존 `createQuickPage`는 기본값 기반 호출에서 `createPage`로 내부 위임
## 2026-03-04
- 페이지: `/admin/pages` 페이지 관리
- 타입: 관리자 페이지 기능 확장
- 라우트: `/admin/pages`
- 구성:
- Pinia 기반 `usePagesStore` 추가 (`frontend/app/stores/pages.ts`)
- 페이지 목록 UI/검색/필터/Campaign/정렬/생성/삭제 기능 구현
- 캠페인 카드에서 `페이지 관리` 버튼 추가 (`/admin/pages?campaignId=<projectId>`)
- `/admin` 좌측 사이드바 링크(`/admin/pages`)와 동작 연동
- 비고:
- 현재는 더미 데이터 기반 MVP 운영 화면
- 빌더 연동은 다음 단계에서 라우트 연계
## 2026-03-04
- 페이지: `/admin/pages`, `/admin/projects`
- 타입: 한글 로컬라이즈
- 라우트: `/admin/pages`, `/admin/projects`
- 구성:
- `/admin/pages` 제목/버튼/라벨/컬럼/상태/안내문구 한글 전환
- `/admin/projects` 영문 노출 라벨 일괄 한국어 전환
- 페이지 관리 스토어 캠페인명, 샘플 페이지명, 상태 라벨을 한글로 조정
- 비고:
- 영문 코드는 값 자체 유지(`live`, `draft` 등), 화면 텍스트만 한글화
## 2026-03-04
- 페이지: `/admin/leads`
- 타입: 리드 조회 페이지 추가
- 라우트: `/admin/leads`
- 구성:
- 검색, 상태, 채널, 캠페인 필터 지원
- 더미 리드 테이블(이름/연락처/이메일/캠페인/페이지/채널/상태/수집일) 표시
- 엑셀 내보내기 버튼(미구현 액션, UI 우선) 추가
- 비고:
- 사이드바 링크는 기존 `/admin` 레이아웃에 이미 존재해 바로 접근 가능
## 2026-03-04
- 페이지: `/admin/projects`
- 타입: 새 프로젝트 생성 모달 컴포넌트
- 라우트: `/admin/projects`
- 구성:
- 공용 컴포넌트 `CreateProjectModal` 신규 생성 (`frontend/app/components/admin/CreateProjectModal.vue`)
- 프로젝트명, 캠페인 표시명 입력폼 + 닫기/생성 액션
- 프로젝트 목록 페이지에서 모달 오픈/닫기 상태 관리 및 제출 이벤트 처리 추가
- `useProjectsStore``createProject` 액션 추가 (`frontend/app/stores/projects.ts`)
- 새 프로젝트 생성 버튼 및 새 그룹 카드에서 모달 오픈 연동
- 비고:
- 현재는 더미 데이터 스토어에 `unshift`로 즉시 반영되는 MVP 동작
## 2026-03-04
- 페이지: `/admin`, `/admin/projects`, `/admin/pages`
- 타입: 프로젝트/대시보드 라벨 완전 한글화
- 라우트: `/admin`, `/admin/index`, `/admin/projects`, `/admin/pages`
- 구성:
- 레이아웃 타이틀을 `Campaign & Page Console`에서 `캠페인·페이지 콘솔`로 변경
- 대시보드 라벨 `Dashboard``대시보드`로 변경
- 프로젝트 스토어 샘플 데이터(`title/subtitle/metricLabel`) 한글화
- `CONV. RATE``전환율`
- `CTR``클릭률`
- `Final``종료`
## 2026-03-04
- 페이지: `/admin` Pinia 런타임
- 타입: 상태 관리 안정화
- 라우트: `/admin`, `/admin/projects`
- 구성:
- `frontend/app/plugins/pinia.ts`(수동 `createPinia()` 주입 플러그인) 제거
- `@pinia/nuxt` 공식 모듈만 사용하도록 정리
- `useDashboardStore`, `useProjectsStore`를 setup 내에서 호출하되 Pinia 인스턴스가 항상 Nuxt 플러그인으로 주입되도록 정리
- 비고:
- `[🍍] getActivePinia()` 경고는 커스텀 플러그인과 공식 모듈 중복 등록 가능성이 원인이라 판단
- 수정 후 dev 서버 재시작 필요
## 2026-03-04
- 설정: Pinia 전역 상태 관리 연동
- 타입: 플랫폼 인프라
- 라우트: 전체 관리자 페이지
- 구성:
- `@pinia/nuxt``pinia` 의존성 추가
- `nuxt.config.ts``@pinia/nuxt` 모듈 등록
- 비고: 요청 반영으로 Pinia 기반 관리 준비
## 2026-03-04
- 페이지: `/admin/projects` 프로젝트 데이터 스토어 마이그레이션
- 타입: 상태 관리
- 라우트: `/admin/projects`
- 구성:
- `app/stores/projects.ts` 신규 추가
- 검색/상태필터/정렬 state 및 `filteredProjects` getter 적용
- 상단 검색/Status/Sort 컨트롤을 store 바인딩(`v-model`) 처리
## 2026-03-04
- 페이지: `/admin` 대시보드 데이터 스토어 마이그레이션
- 타입: 상태 관리
- 라우트: `/admin`
- 구성:
- `app/stores/dashboard.ts` 신규 추가
- KPI 카드, 리드 추이, 진행중 프로젝트 목록을 스토어로 이동
- `admin/index.vue``storeToRefs(useDashboardStore())` 연동
## 2026-03-04
- 스타일: 관리자 전체 컬러 톤 강화
- 타입: UI polish
- 라우트: `/admin/*`
- 구성:
- 사이드바/네비게이션에 그라데이션 및 포인트 색상(시안/인디고) 적용
- 메인 영역 배경에 라이트 누출형 radial 컬러 레이어 적용
- 대시보드/프로젝트 페이지의 헤더, 검색, 필터, 카드, 버튼에 컬러 그라데이션 추가
- 비고: “너무 칙칙한데 컬러 좀 넣어줘” 요청 반영
## 2026-03-04
- 페이지: `/admin/pages`
- 타입: 라우팅 구조 보정
- 라우트: `/admin/pages`
- 구성:
- 페이지 목록 파일을 `frontend/app/pages/admin/pages.vue`에서 `frontend/app/pages/admin/pages/index.vue`로 이동
- 하위 경로인 `/admin/pages/:id/builder` 라우트 충돌 가능성 해소
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 빌더 기능 확장
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 블록 타입을 `html`, `image`로 단일화
- 이미지 파일 업로드 시 업로드 즉시 `이미지` 블록 생성
- 이미지 URL로 블록 생성 기능 추가
- 블록 순서 이동(위/아래) 및 삭제 유지
- 헤더 설정 구간 추가: 페이지 제목, 메타 설명/키워드, 파비콘, head 추가코드, body script
- 저장 버튼에서 메타 + 블록을 조합한 최종 HTML 생성 및 클립보드 복사 영역 제공
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 컴파일 오류 대응 리팩터링
- 라우트: `/admin/pages/:id/builder`
- 구성:
- `Invalid end tag` 원인 분리를 위해 빌더 컴포넌트를 전체 재작성
- `script` 태그 문자열 조합을 안전 분할(`'<' + 'script>'` / `'</' + 'script>'`) 방식으로 정리
- 핵심 편집/미리보기/블록 추가/정렬/삭제/삭제/클립보드 복사 흐름 유지
- 기존 템플릿/스크립트 충돌 의심 소지를 줄이는 형태로 재구성
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: UI/UX 조정
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 헤더 제목/본문 문구/버튼 문구 입력 영역 제거(요청 반영)
- 최종 생성 HTML에서 기본 문구 블록 제거, 블록 기반 렌더링 중심으로 정리
- 이미지 업로드에 `multiple` 적용 및 드래그앤드롭 지원 추가
- 드롭/선택된 이미지들을 한 번에 최대 10개까지 등록하도록 처리
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 업로드 UX 축소
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 로컬 업로드 라벨/이미지 URL 입력 UI 제거
- 이미지 업로드를 드래그앤드롭 박스 1개와 내부 파일 입력으로만 처리
- 별도 로컬/URL 선택 패널 UI 제거 후 업로드 영역 단일화
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 미리보기 정합성
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 미리보기 패널이 `space-y` 래핑 때문에 생기던 블록 간 간격을 제거
- 실제 최종 랜딩 렌더(body) 문자열과 동일한 DOM 기준으로 미리보기 렌더링 적용
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 블록 구성 변경
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 폼 블록/카톡 싱크 버튼 블록 생성 버튼을 제거
- footer HTML 입력 필드 추가 (헤더/메타/바디스크립트 설정과 함께 저장/미리보기에 반영)
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 미리보기 UX
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 실제 랜딩 생성 데이터는 유지한 상태로, 미리보기 전용 이미지 렌더 방식을 분리
- 미리보기에서만 이미지 최대 너비/높이 제한을 적용해 썸네일처럼 작게 표시
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 이미지 블록 목록 UX
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 이미지 블록 미리보기에서 둥근 모서리 제거(`border-radius:0`)
- 이미지 블록 목록에서 blob URL 텍스트 제거
- 이미지 블록 목록 항목 좌측에 썸네일 이미지 미리보기 표시
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 블록 종류 확장
- 라우트: `/admin/pages/:id/builder`
- 구성:
- `폼 블록` 추가 기능(기본 폼 HTML 생성)
- `카카오 싱크 버튼 블록` 추가 기능(카카오 버튼 샘플 HTML 생성)
- `html 블록` 옆에 두 액션 버튼 배치(폼/카톡 싱크)
## 2026-03-04
- 페이지: `/admin/pages/[id]/builder`
- 타입: 이미지 업로드 정책 변경
- 라우트: `/admin/pages/:id/builder`
- 구성:
- 이미지 업로드 최대 장수 제한 제거
- 드롭/선택한 모든 이미지 파일을 일괄 등록
- UI 문구에서 “최대 10장” 표현 제거
## 2026-03-04
- 페이지: `/admin/pages/[id]/variants`
- 타입: 변형(variant) 관리 페이지
- 라우트: `/admin/pages/:id/variants`
- 구성:
- Pinia 스토어 `frontend/app/stores/variants.ts` 신규 추가
- 페이지별 변형 목록/생성/삭제/활성-비활성 토글 기능
- 페이지 목록(`/admin/pages`) 카드에 “변형 관리” 바로가기 추가
- 프로젝트 카드의 “변형 관리” 버튼을 페이지별 변형 페이지로 연결
- 변형 메트릭(총 변형 수, 누적 리드, 총 트래픽 비율), 변형명/설명/비율/리드 수 UI 구성
- 비고:
- 페이지별 변형 개수는 `variants` 스토어 기준으로 실시간 계산되어 표시

21
frontend/nuxt.config.ts Normal file
View File

@@ -0,0 +1,21 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
import tailwindcss from '@tailwindcss/vite'
export default defineNuxtConfig({
modules: ['@pinia/nuxt', 'shadcn-nuxt'],
shadcn: {
prefix: 'Ui',
},
devtools: {
enabled: true,
},
css: ['~/assets/css/main.css'],
vite: {
plugins: [tailwindcss()],
},
compatibilityDate: '2025-01-15'
});

28
frontend/package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "frontend",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev --port 3000",
"preview": "nuxt preview --port 3000",
"postinstall": "nuxt prepare",
"lint": "eslint .",
"typecheck": "nuxt typecheck"
},
"packageManager": "bun@1.3.9",
"dependencies": {
"@pinia/nuxt": "^0.11.3",
"@tailwindcss/vite": "^4.1.18",
"nuxt": "^4.3.1",
"pinia": "^3.0.4",
"shadcn-nuxt": "^2.2.0",
"tailwindcss": "^4.1.18"
},
"devDependencies": {
"@nuxt/eslint": "^1.15.1",
"eslint": "^10.0.0",
"typescript": "^5.9.3",
"vue-tsc": "^3.2.4"
}
}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

13
frontend/renovate.json Normal file
View File

@@ -0,0 +1,13 @@
{
"extends": [
"github>nuxt/renovate-config-nuxt"
],
"lockFileMaintenance": {
"enabled": true
},
"packageRules": [{
"matchDepTypes": ["resolutions"],
"enabled": false
}],
"postUpdateOptions": ["pnpmDedupe"]
}

10
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{ "path": "./.nuxt/tsconfig.app.json" },
{ "path": "./.nuxt/tsconfig.server.json" },
{ "path": "./.nuxt/tsconfig.shared.json" },
{ "path": "./.nuxt/tsconfig.node.json" }
]
}

View File

@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Dashboard Admin Login</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"card-dark": "#1a2230", // Custom darker shade for card
"border-dark": "#2a3345", // Custom border color
},
fontFamily: {
"display": ["Inter", "sans-serif"]
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
backgroundImage: {
'grid-pattern': "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke='rgb(255 255 255 / 0.03)'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e\")",
}
},
},
}
</script>
<style>/* Custom checkbox style adjustment */
.checkbox-custom:checked {
background-image: url(https://lh3.googleusercontent.com/aida-public/AB6AXuDSksUUmfaRgCz4Hpv8QOgSJuURlYyWJdMxep5O2mOI1J8cFghoERv2sqEtPQM4HgA3bChqOyi8Uyb_B2BHlSFCqg0-9GKppQ1jlR8pPtI9b6bWJQNGyDlTnJILk4BHUhDYbvs1AbnxsD076ph3QnH9-2SwOqraayE8Qs6qZ8jZQByK36ZcvkN4qd92SfnYVXu_9iupdaINHfP4OYmq_p9qF6kqTGiT_whJaqlDcllYeU18nLOAQszGkMszrWzXH-UM5EXd7zg1i-vm)
}</style>
</head>
<body class="font-display bg-background-light dark:bg-background-dark min-h-screen flex flex-col relative overflow-x-hidden antialiased">
<!-- Background Pattern & Gradient Overlay -->
<div class="absolute inset-0 z-0 bg-grid-pattern pointer-events-none"></div>
<div class="absolute inset-0 z-0 bg-gradient-to-br from-primary/10 via-transparent to-transparent pointer-events-none opacity-50"></div>
<!-- Main Content Layout -->
<main class="relative z-10 flex-grow flex items-center justify-center p-4 sm:p-6 lg:p-8">
<!-- Login Card -->
<div class="w-full max-w-[480px] bg-white dark:bg-card-dark rounded-xl shadow-2xl border border-gray-200 dark:border-border-dark overflow-hidden">
<!-- Card Header -->
<div class="px-8 pt-10 pb-6 text-center">
<div class="mx-auto h-12 w-12 bg-primary/20 rounded-lg flex items-center justify-center mb-6 text-primary">
<span class="material-symbols-outlined text-3xl">analytics</span>
</div>
<h1 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white mb-2">
LandingLab
</h1>
<p class="text-gray-500 dark:text-gray-400 text-sm">
Welcome back! Please enter your details to access your dashboard.
</p>
</div>
<!-- Login Form -->
<form class="px-8 pb-10 flex flex-col gap-5">
<!-- Email Field -->
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="email">
Email address
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
<span class="material-symbols-outlined text-[20px]">mail</span>
</div>
<input class="block w-full h-12 pl-10 pr-3 rounded-lg border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-[#111318] text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-shadow sm:text-sm" id="email" placeholder="name@company.com" type="email"/>
</div>
</div>
<!-- Password Field -->
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="password">
Password
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
<span class="material-symbols-outlined text-[20px]">lock</span>
</div>
<input class="block w-full h-12 pl-10 pr-10 rounded-lg border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-[#111318] text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-shadow sm:text-sm" id="password" placeholder="Enter your password" type="password"/>
<button class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer" type="button">
<span class="material-symbols-outlined text-[20px]">visibility</span>
</button>
</div>
</div>
<!-- Remember & Forgot Password -->
<div class="flex items-center justify-between">
<label class="flex items-center gap-2 cursor-pointer">
<input class="checkbox-custom h-4 w-4 rounded border-gray-300 dark:border-gray-600 bg-white dark:bg-[#111318] text-primary focus:ring-primary focus:ring-offset-0 focus:ring-2 dark:focus:ring-offset-card-dark" type="checkbox"/>
<span class="text-sm font-medium text-gray-600 dark:text-gray-400">Remember for 30 days</span>
</label>
<a class="text-sm font-medium text-primary hover:text-blue-500 transition-colors" href="#">
Forgot password?
</a>
</div>
<!-- Submit Button -->
<button class="w-full h-12 bg-primary hover:bg-blue-600 text-white font-semibold rounded-lg shadow-lg shadow-primary/30 transition-all duration-200 transform active:scale-[0.98] flex items-center justify-center gap-2" type="button">
<span>Sign in</span>
<span class="material-symbols-outlined text-[20px]">arrow_forward</span>
</button>
<!-- SSO Divider (Optional Enterprise Touch) -->
<div class="relative py-2">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-200 dark:border-gray-700"></div>
</div>
<div class="relative flex justify-center text-xs uppercase">
<span class="bg-white dark:bg-card-dark px-2 text-gray-400">Or continue with</span>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<button class="flex items-center justify-center gap-2 h-10 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-[#111318] hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" type="button">
<img alt="Google Logo" class="w-5 h-5" data-alt="Google G colorful logo" src="https://lh3.googleusercontent.com/aida-public/AB6AXuA7LCYf6vbP__6MGSgOfuOyZ7dWFz2c_aF7KuMdAeKQqGvYiREHzQnXKUVuchWo-ALw6yx7UU8RZE-3-MS4-Jf2an2NCpTAslhS4BVbNzJuvmc59oXd1NBxw7h8erIi-nDXsP9fhD4SyAzedQ8I90JQDB9eRxWmtMKy9OerRqX-S4ZHDbaia714WoB0qPgkOpqs0yjJHWO7OeR9Z5NXrLEdyCA7aJZAD22kB29d4UhCgdgVVN0VCx8oPvlLZEg7vWQbYUvfcv-1edYa"/>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Google</span>
</button>
<button class="flex items-center justify-center gap-2 h-10 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-[#111318] hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" type="button">
<span class="material-symbols-outlined text-gray-900 dark:text-white text-[22px]">hub</span>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">SSO</span>
</button>
</div>
</form>
</div>
<!-- Footer Links -->
<div class="absolute bottom-6 w-full text-center">
<p class="text-xs text-gray-500 dark:text-gray-500">
© 2024 LandingLab Inc.
<a class="hover:text-gray-900 dark:hover:text-gray-300 ml-2 transition-colors" href="#">Privacy Policy</a>
<span class="mx-1">·</span>
<a class="hover:text-gray-900 dark:hover:text-gray-300 transition-colors" href="#">Terms of Service</a>
</p>
</div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

View File

@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>랜딩-마스터 관리자 로그인</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"card-dark": "#1a2230",
"border-dark": "#2a3345",
},
fontFamily: {
"display": ["Inter", "sans-serif"]
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
backgroundImage: {
'grid-pattern': "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke='rgb(255 255 255 / 0.03)'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e\")",
}
},
},
}
</script>
<style>
.checkbox-custom:checked {
background-image: url(https://lh3.googleusercontent.com/aida-public/AB6AXuDSksUUmfaRgCz4Hpv8QOgSJuURlYyWJdMxep5O2mOI1J8cFghoERv2sqEtPQM4HgA3bChqOyi8Uyb_B2BHlSFCqg0-9GKppQ1jlR8pPtI9b6bWJQNGyDlTnJILk4BHUhDYbvs1AbnxsD076ph3QnH9-2SwOqraayE8Qs6qZ8jZQByK36ZcvkN4qd92SfnYVXu_9iupdaINHfP4OYmq_p9qF6kqTGiT_whJaqlDcllYeU18nLOAQszGkMszrWzXH-UM5EXd7zg1i-vm)
}
</style>
</head>
<body class="font-display bg-background-light dark:bg-background-dark min-h-screen flex flex-col relative overflow-x-hidden antialiased">
<div class="absolute inset-0 z-0 bg-grid-pattern pointer-events-none"></div>
<div class="absolute inset-0 z-0 bg-gradient-to-br from-primary/10 via-transparent to-transparent pointer-events-none opacity-50"></div>
<main class="relative z-10 flex-grow flex items-center justify-center p-4 sm:p-6 lg:p-8">
<div class="w-full max-w-[480px] bg-white dark:bg-card-dark rounded-xl shadow-2xl border border-gray-200 dark:border-border-dark overflow-hidden">
<div class="px-8 pt-10 pb-6 text-center">
<div class="mx-auto h-12 w-12 bg-primary/20 rounded-lg flex items-center justify-center mb-6 text-primary">
<span class="material-symbols-outlined text-3xl">analytics</span>
</div>
<h1 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white mb-2">
랜딩-마스터
</h1>
<p class="text-gray-500 dark:text-gray-400 text-sm">
다시 오신 것을 환영합니다! 대시보드 접속을 위해 정보를 입력해주세요.
</p>
</div>
<form class="px-8 pb-10 flex flex-col gap-5">
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="email">
이메일 주소
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
<span class="material-symbols-outlined text-[20px]">mail</span>
</div>
<input class="block w-full h-12 pl-10 pr-3 rounded-lg border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-[#111318] text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-shadow sm:text-sm" id="email" placeholder="name@company.com" type="email"/>
</div>
</div>
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="password">
비밀번호
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
<span class="material-symbols-outlined text-[20px]">lock</span>
</div>
<input class="block w-full h-12 pl-10 pr-10 rounded-lg border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-[#111318] text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-shadow sm:text-sm" id="password" placeholder="비밀번호를 입력하세요" type="password"/>
<button class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer" type="button">
<span class="material-symbols-outlined text-[20px]">visibility</span>
</button>
</div>
</div>
<div class="flex items-center justify-between">
<label class="flex items-center gap-2 cursor-pointer">
<input class="checkbox-custom h-4 w-4 rounded border-gray-300 dark:border-gray-600 bg-white dark:bg-[#111318] text-primary focus:ring-primary focus:ring-offset-0 focus:ring-2 dark:focus:ring-offset-card-dark" type="checkbox"/>
<span class="text-sm font-medium text-gray-600 dark:text-gray-400">30일 동안 로그인 유지</span>
</label>
<a class="text-sm font-medium text-primary hover:text-blue-500 transition-colors" href="#">
비밀번호를 잊으셨나요?
</a>
</div>
<button class="w-full h-12 bg-primary hover:bg-blue-600 text-white font-semibold rounded-lg shadow-lg shadow-primary/30 transition-all duration-200 transform active:scale-[0.98] flex items-center justify-center gap-2" type="button">
<span>로그인</span>
<span class="material-symbols-outlined text-[20px]">arrow_forward</span>
</button>
<div class="relative py-2">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-200 dark:border-gray-700"></div>
</div>
<div class="relative flex justify-center text-xs uppercase">
<span class="bg-white dark:bg-card-dark px-2 text-gray-400">또는 다음으로 계속하기</span>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<button class="flex items-center justify-center gap-2 h-10 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-[#111318] hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" type="button">
<img alt="Google Logo" class="w-5 h-5" src="https://lh3.googleusercontent.com/aida-public/AB6AXuA7LCYf6vbP__6MGSgOfuOyZ7dWFz2c_aF7KuMdAeKQqGvYiREHzQnXKUVuchWo-ALw6yx7UU8RZE-3-MS4-Jf2an2NCpTAslhS4BVbNzJuvmc59oXd1NBxw7h8erIi-nDXsP9fhD4SyAzedQ8I90JQDB9eRxWmtMKy9OerRqX-S4ZHDbaia714WoB0qPgkOpqs0yjJHWO7OeR9Z5NXrLEdyCA7aJZAD22kB29d4UhCgdgVVN0VCx8oPvlLZEg7vWQbYUvfcv-1edYa"/>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">구글 로그인</span>
</button>
<button class="flex items-center justify-center gap-2 h-10 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-[#111318] hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" type="button">
<span class="material-symbols-outlined text-gray-900 dark:text-white text-[22px]">hub</span>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">SSO 로그인</span>
</button>
</div>
</form>
</div>
<div class="absolute bottom-6 w-full text-center">
<p class="text-xs text-gray-500 dark:text-gray-500">
© 2024 LandingMaster Inc.
<a class="hover:text-gray-900 dark:hover:text-gray-300 ml-2 transition-colors" href="#">개인정보처리방침</a>
<span class="mx-1">·</span>
<a class="hover:text-gray-900 dark:hover:text-gray-300 transition-colors" href="#">이용약관</a>
</p>
</div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

View File

@@ -0,0 +1,405 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Group Details &amp; Settings</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"primary-hover": "#1d64f0",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1a202c",
"surface-dark-lighter": "#2d3748",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Custom scrollbar for dark mode */
.dark ::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.dark ::-webkit-scrollbar-track {
background: #111318;
}
.dark ::-webkit-scrollbar-thumb {
background: #374151;
border-radius: 4px;
}
.dark ::-webkit-scrollbar-thumb:hover {
background: #4b5563;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased">
<div class="flex h-screen w-full overflow-hidden">
<!-- Side Navigation -->
<div class="hidden lg:flex w-64 flex-col bg-[#111318] border-r border-slate-800 shrink-0">
<div class="flex h-full flex-col justify-between p-4">
<div class="flex flex-col gap-4">
<!-- User Profile / Team -->
<div class="flex gap-3 items-center mb-2">
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 shrink-0 border border-slate-700" data-alt="Company Logo Abstract" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCHbPUXlbWtVB4VpZY-3HUUkFWv74JV9ZCOJXzOUua1k1Eft_cOO1m3r3EB6bD2MQBjK3dyo57n43Dfe_LC5GaiRWZLjy19VuxOnEgoiPuPimUUvUrhPjim2urCnzEHD_VF1afU1kIRmExhrx5nG-rktd7DAuJJK9NxP93vmyBLA_Vz76Q-7AMpPUhrBc8aShyQ_kJrcXDswwCvM1GQr9Kfy-Gvs8bh0zrdVfg_Wy7ynELoWixJpjJyaVKrDTtMxZkn4Gg3auxKFni2");'></div>
<div class="flex flex-col overflow-hidden">
<h1 class="text-white text-sm font-semibold leading-tight truncate">Acme Corp</h1>
<p class="text-slate-400 text-xs font-normal leading-normal truncate">Pro Plan</p>
</div>
</div>
<!-- Navigation Links -->
<nav class="flex flex-col gap-1">
<a class="flex items-center gap-3 px-3 py-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors" href="#">
<span class="material-symbols-outlined text-[20px]">dashboard</span>
<span class="text-sm font-medium">Dashboard</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 bg-primary/10 text-primary rounded-lg transition-colors" href="#">
<span class="material-symbols-outlined text-[20px] fill-1">layers</span>
<span class="text-sm font-medium">Groups</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors" href="#">
<span class="material-symbols-outlined text-[20px]">group</span>
<span class="text-sm font-medium">Leads</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors" href="#">
<span class="material-symbols-outlined text-[20px]">bar_chart</span>
<span class="text-sm font-medium">Analytics</span>
</a>
</nav>
</div>
<!-- Bottom Links -->
<div class="flex flex-col gap-1 mt-auto">
<a class="flex items-center gap-3 px-3 py-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors" href="#">
<span class="material-symbols-outlined text-[20px]">settings</span>
<span class="text-sm font-medium">Settings</span>
</a>
</div>
</div>
</div>
<!-- Main Content Area -->
<main class="flex-1 flex flex-col h-full overflow-y-auto bg-background-light dark:bg-background-dark relative">
<!-- Header Area -->
<header class="w-full px-6 py-6 border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-[#111318]">
<!-- Breadcrumbs -->
<div class="flex items-center gap-2 mb-4 text-sm">
<a class="text-slate-500 hover:text-primary transition-colors" href="#">Dashboard</a>
<span class="text-slate-600 dark:text-slate-600">/</span>
<a class="text-slate-500 hover:text-primary transition-colors" href="#">Groups</a>
<span class="text-slate-600 dark:text-slate-600">/</span>
<span class="text-slate-900 dark:text-white font-medium">Summer Campaign 2024</span>
</div>
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div class="flex flex-col gap-2">
<div class="flex items-center gap-3">
<h1 class="text-2xl md:text-3xl font-bold text-slate-900 dark:text-white tracking-tight">Summer Campaign 2024</h1>
<span class="inline-flex items-center rounded-full bg-emerald-500/10 px-2 py-1 text-xs font-medium text-emerald-500 ring-1 ring-inset ring-emerald-500/20">
Active
</span>
</div>
<p class="text-slate-500 dark:text-slate-400 text-sm max-w-2xl">
Manage settings, A/B tests, and view performance metrics for this landing page group.
</p>
</div>
<div class="flex items-center gap-3 shrink-0">
<!-- Toggle Switch -->
<label class="inline-flex items-center cursor-pointer mr-2">
<input checked="" class="sr-only peer" type="checkbox" value=""/>
<div class="relative w-11 h-6 bg-slate-700 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary/50 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
<span class="ms-3 text-sm font-medium text-slate-900 dark:text-slate-300">Live</span>
</label>
<button class="bg-primary hover:bg-primary-hover text-white px-4 py-2 rounded-lg text-sm font-semibold shadow-lg shadow-primary/20 transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-[18px]">save</span>
Save Changes
</button>
</div>
</div>
<!-- Tabs -->
<div class="mt-8 border-b border-slate-200 dark:border-slate-800">
<nav aria-label="Tabs" class="-mb-px flex space-x-8">
<a aria-current="page" class="border-primary text-primary whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium" href="#">
General Info
</a>
<a class="border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium" href="#">
Scripts
</a>
<a class="border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium" href="#">
Completion Page
</a>
<a class="border-transparent text-slate-500 hover:border-slate-300 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium" href="#">
Stats Summary
</a>
</nav>
</div>
</header>
<!-- Scrollable Content -->
<div class="p-6 max-w-7xl mx-auto w-full space-y-6 pb-20">
<!-- Performance Snapshot -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Stat Card 1 -->
<div class="bg-white dark:bg-[#1a202c] rounded-xl border border-slate-200 dark:border-slate-800 p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-slate-500 dark:text-slate-400">Total Visitors</h3>
<span class="material-symbols-outlined text-slate-400 text-[20px]">visibility</span>
</div>
<div class="flex items-baseline gap-2">
<span class="text-3xl font-bold text-slate-900 dark:text-white">12,450</span>
<span class="text-xs font-medium text-emerald-500 flex items-center">
<span class="material-symbols-outlined text-[14px]">arrow_upward</span> 12%
</span>
</div>
</div>
<!-- Stat Card 2 -->
<div class="bg-white dark:bg-[#1a202c] rounded-xl border border-slate-200 dark:border-slate-800 p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-slate-500 dark:text-slate-400">Leads Generated</h3>
<span class="material-symbols-outlined text-slate-400 text-[20px]">group_add</span>
</div>
<div class="flex items-baseline gap-2">
<span class="text-3xl font-bold text-slate-900 dark:text-white">856</span>
<span class="text-xs font-medium text-emerald-500 flex items-center">
<span class="material-symbols-outlined text-[14px]">arrow_upward</span> 5.2%
</span>
</div>
</div>
<!-- Stat Card 3 -->
<div class="bg-white dark:bg-[#1a202c] rounded-xl border border-slate-200 dark:border-slate-800 p-5 shadow-sm relative overflow-hidden group">
<div class="absolute inset-0 bg-primary/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-center justify-between mb-4 relative z-10">
<h3 class="text-sm font-medium text-primary">Conversion Rate</h3>
<span class="material-symbols-outlined text-primary text-[20px]">trending_up</span>
</div>
<div class="flex items-baseline gap-2 relative z-10">
<span class="text-3xl font-bold text-primary">6.8%</span>
<span class="text-xs font-medium text-slate-500 dark:text-slate-400">Avg. industry 4.2%</span>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left Column: Configuration -->
<div class="lg:col-span-2 space-y-6">
<!-- Basic Settings -->
<section class="bg-white dark:bg-[#1a202c] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm">
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-800 flex justify-between items-center">
<h2 class="text-lg font-semibold text-slate-900 dark:text-white">Configuration</h2>
<button class="text-slate-400 hover:text-white transition-colors">
<span class="material-symbols-outlined">more_horiz</span>
</button>
</div>
<div class="p-6 space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-span-1">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" for="group-name">Internal Group Name</label>
<input class="block w-full rounded-lg border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white shadow-sm focus:border-primary focus:ring-primary sm:text-sm px-4 py-2.5" id="group-name" name="group-name" type="text" value="Summer Campaign 2024"/>
<p class="mt-1 text-xs text-slate-500">Used for internal organization only.</p>
</div>
<div class="col-span-1">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" for="domain">Domain</label>
<select class="block w-full rounded-lg border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white shadow-sm focus:border-primary focus:ring-primary sm:text-sm px-4 py-2.5" id="domain" name="domain">
<option>landing.acmecorp.com</option>
<option>promo.acmecorp.com</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" for="slug">URL Slug</label>
<div class="flex rounded-lg shadow-sm">
<span class="inline-flex items-center rounded-l-lg border border-r-0 border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-700/50 px-3 text-slate-500 dark:text-slate-400 sm:text-sm">
landing.acmecorp.com/
</span>
<input class="block w-full min-w-0 flex-1 rounded-none rounded-r-lg border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:border-primary focus:ring-primary sm:text-sm px-4 py-2.5" id="slug" name="slug" type="text" value="summer-sale-2024"/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">Group Tags</label>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1 rounded-md bg-slate-100 dark:bg-slate-700/50 px-2 py-1 text-xs font-medium text-slate-600 dark:text-slate-300 ring-1 ring-inset ring-slate-500/10">
Seasonal
<button class="group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-slate-500/20" type="button">
<span class="sr-only">Remove</span>
<svg class="h-3.5 w-3.5 stroke-slate-600/50 group-hover:stroke-slate-600/75 dark:stroke-slate-300/50 dark:group-hover:stroke-slate-300/75" viewbox="0 0 14 14">
<path d="M4 4l6 6m0-6l-6 6"></path>
</svg>
</button>
</span>
<span class="inline-flex items-center gap-1 rounded-md bg-indigo-50 dark:bg-indigo-900/20 px-2 py-1 text-xs font-medium text-indigo-700 dark:text-indigo-400 ring-1 ring-inset ring-indigo-700/10">
Q3 Marketing
<button class="group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-indigo-600/20" type="button">
<span class="sr-only">Remove</span>
<svg class="h-3.5 w-3.5 stroke-indigo-700/50 group-hover:stroke-indigo-700/75 dark:stroke-indigo-400/50 dark:group-hover:stroke-indigo-400/75" viewbox="0 0 14 14">
<path d="M4 4l6 6m0-6l-6 6"></path>
</svg>
</button>
</span>
<button class="inline-flex items-center gap-1 rounded-md border border-dashed border-slate-300 dark:border-slate-600 px-2 py-1 text-xs font-medium text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:border-slate-400 transition-colors">
<span class="material-symbols-outlined text-[14px]">add</span>
Add Tag
</button>
</div>
</div>
</div>
</section>
<!-- A/B Testing Control -->
<section class="bg-white dark:bg-[#1a202c] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm">
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-800 flex justify-between items-center">
<div class="flex items-center gap-2">
<h2 class="text-lg font-semibold text-slate-900 dark:text-white">A/B Testing</h2>
<span class="inline-flex items-center rounded-md bg-blue-400/10 px-2 py-1 text-xs font-medium text-blue-400 ring-1 ring-inset ring-blue-400/30">Running</span>
</div>
<button class="text-sm font-medium text-primary hover:text-primary-hover">
Manage Variants
</button>
</div>
<div class="p-6">
<p class="text-sm text-slate-500 dark:text-slate-400 mb-8">
Adjust the traffic distribution between your variants. Changes apply immediately to new visitors.
</p>
<!-- Visual Slider -->
<div class="relative mb-8 pt-6">
<div class="flex justify-between text-sm font-medium mb-2">
<div class="text-slate-900 dark:text-white">Variant A <span class="text-slate-500 font-normal">(Control)</span></div>
<div class="text-slate-900 dark:text-white">Variant B <span class="text-slate-500 font-normal">(Test)</span></div>
</div>
<!-- Range Slider Wrapper -->
<div class="relative h-4 w-full rounded-full bg-slate-200 dark:bg-slate-700 overflow-hidden flex">
<div class="h-full bg-slate-500 dark:bg-slate-500" style="width: 50%"></div>
<div class="h-full bg-primary" style="width: 50%"></div>
<!-- Draggable Handle (Visual Only for UI) -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 bg-white border-4 border-slate-100 dark:border-[#1a202c] rounded-full shadow-lg cursor-ew-resize z-10 flex items-center justify-center">
<div class="w-1.5 h-1.5 bg-slate-400 rounded-full mx-px"></div>
<div class="w-1.5 h-1.5 bg-slate-400 rounded-full mx-px"></div>
</div>
</div>
<div class="flex justify-between mt-3">
<div class="text-2xl font-bold text-slate-700 dark:text-slate-300">50%</div>
<div class="text-2xl font-bold text-primary">50%</div>
</div>
</div>
<!-- Variants List -->
<div class="space-y-3">
<div class="flex items-center justify-between p-3 rounded-lg bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700">
<div class="flex items-center gap-3">
<div class="w-2 h-10 bg-slate-500 rounded-full"></div>
<div>
<div class="text-sm font-medium text-slate-900 dark:text-white">Main Landing Page (Original)</div>
<div class="text-xs text-slate-500">Last edited 2 days ago</div>
</div>
</div>
<div class="flex items-center gap-6">
<div class="text-right">
<div class="text-xs text-slate-500">Conv. Rate</div>
<div class="text-sm font-semibold text-slate-900 dark:text-white">6.2%</div>
</div>
<button class="text-slate-400 hover:text-white">
<span class="material-symbols-outlined">edit</span>
</button>
</div>
</div>
<div class="flex items-center justify-between p-3 rounded-lg bg-blue-50/10 dark:bg-primary/5 border border-primary/30">
<div class="flex items-center gap-3">
<div class="w-2 h-10 bg-primary rounded-full"></div>
<div>
<div class="text-sm font-medium text-slate-900 dark:text-white">Hero Section Redesign V2</div>
<div class="text-xs text-slate-500">Last edited 4 hours ago</div>
</div>
</div>
<div class="flex items-center gap-6">
<div class="text-right">
<div class="text-xs text-slate-500">Conv. Rate</div>
<div class="text-sm font-semibold text-primary">7.4%</div>
</div>
<button class="text-slate-400 hover:text-white">
<span class="material-symbols-outlined">edit</span>
</button>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- Right Column: Info & Actions -->
<div class="lg:col-span-1 space-y-6">
<!-- Quick Actions -->
<div class="bg-white dark:bg-[#1a202c] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm p-6">
<h3 class="text-sm font-semibold text-slate-900 dark:text-white uppercase tracking-wider mb-4">Actions</h3>
<div class="flex flex-col gap-3">
<button class="flex items-center justify-between w-full px-4 py-3 text-sm font-medium text-slate-700 dark:text-slate-200 bg-slate-50 dark:bg-slate-800/50 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors border border-slate-200 dark:border-slate-700">
<span>Preview Live Page</span>
<span class="material-symbols-outlined text-[18px]">open_in_new</span>
</button>
<button class="flex items-center justify-between w-full px-4 py-3 text-sm font-medium text-slate-700 dark:text-slate-200 bg-slate-50 dark:bg-slate-800/50 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors border border-slate-200 dark:border-slate-700">
<span>Duplicate Group</span>
<span class="material-symbols-outlined text-[18px]">content_copy</span>
</button>
<button class="flex items-center justify-between w-full px-4 py-3 text-sm font-medium text-slate-700 dark:text-slate-200 bg-slate-50 dark:bg-slate-800/50 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-colors border border-slate-200 dark:border-slate-700">
<span>Reset Stats</span>
<span class="material-symbols-outlined text-[18px]">restart_alt</span>
</button>
</div>
</div>
<!-- Integration Status -->
<div class="bg-white dark:bg-[#1a202c] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm p-6">
<h3 class="text-sm font-semibold text-slate-900 dark:text-white uppercase tracking-wider mb-4">Integrations</h3>
<div class="space-y-4">
<div class="flex items-start gap-3">
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[#F48024] text-white font-bold text-xs">
GA
</div>
<div>
<p class="text-sm font-medium text-slate-900 dark:text-white">Google Analytics 4</p>
<p class="text-xs text-emerald-500 flex items-center gap-1 mt-0.5">
<span class="h-1.5 w-1.5 rounded-full bg-emerald-500"></span> Connected
</p>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[#635BFF] text-white font-bold text-xs">
S
</div>
<div>
<p class="text-sm font-medium text-slate-900 dark:text-white">Stripe Checkout</p>
<p class="text-xs text-slate-500 flex items-center gap-1 mt-0.5">
Not configured
</p>
</div>
</div>
<div class="pt-2">
<a class="text-xs font-medium text-primary hover:text-primary-hover flex items-center gap-1" href="#">
Manage integrations <span class="material-symbols-outlined text-[14px]">arrow_forward</span>
</a>
</div>
</div>
</div>
<!-- Danger Zone -->
<div class="bg-red-50 dark:bg-red-900/10 rounded-xl border border-red-200 dark:border-red-900/30 shadow-sm p-6">
<h3 class="text-sm font-semibold text-red-700 dark:text-red-400 uppercase tracking-wider mb-4">Danger Zone</h3>
<p class="text-sm text-red-600/80 dark:text-red-400/80 mb-4">
Deleting this group will permanently remove all associated stats and configurations.
</p>
<button class="w-full px-4 py-2 text-sm font-medium text-red-700 dark:text-red-400 bg-white dark:bg-red-950 hover:bg-red-50 dark:hover:bg-red-900/50 rounded-lg border border-red-200 dark:border-red-800 transition-colors">
Delete Group
</button>
</div>
</div>
</div>
</div>
</main>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

View File

@@ -0,0 +1,378 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Groups Dashboard - Acme Corp</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"card-dark": "#1c1f27",
"border-dark": "#282e39",
"text-secondary": "#9da6b9",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"sans": ["Inter", "sans-serif"],
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Custom scrollbar for dark mode */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #111318;
}
::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3b4354;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white font-display overflow-hidden">
<div class="flex h-screen w-full overflow-hidden">
<!-- SideNavBar Component -->
<div class="hidden md:flex flex-col w-64 h-full border-r border-border-dark bg-[#111318] flex-shrink-0">
<div class="flex h-full flex-col justify-between p-4">
<div class="flex flex-col gap-4">
<!-- User Profile -->
<div class="flex gap-3 items-center px-2 py-1">
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 flex-shrink-0 border border-border-dark" data-alt="Profile picture of a business professional" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDOQ3K2IaZ3q70LT08mZ02d1p9CiO5Pr-gqqTE6m1XKF8ntNNhuFjUid9cI5Ooc_Hyq0HhbuSy4T4S9uWo8i0KUmHoNJASbcigmWzpe_V6J7hFxYGkMZGOrrDNJn21X4MIapiFAiDrdCxKoFWET1GvcmvSypznw6Hx1ZsG8LXYKdpVxl5L3O48VVyKpXA1vyBlvK8lQ9YZYpgPRRbihsjGgb-3AvhYCe7GUdnbVPfqrCwJikKMqcJojAxhe01XubTAkGH4oX6DXBNNT");'></div>
<div class="flex flex-col overflow-hidden">
<h1 class="text-white text-base font-medium leading-normal truncate">Acme Corp</h1>
<p class="text-text-secondary text-xs font-normal leading-normal truncate">Pro Plan</p>
</div>
</div>
<!-- Navigation -->
<div class="flex flex-col gap-2 mt-4">
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-text-secondary hover:bg-[#282e39] hover:text-white transition-colors group" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="300">dashboard</span>
<p class="text-sm font-medium leading-normal">Dashboard</p>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg bg-[#282e39] text-white" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="600">folder_copy</span>
<p class="text-sm font-medium leading-normal">Groups</p>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-text-secondary hover:bg-[#282e39] hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="300">bar_chart</span>
<p class="text-sm font-medium leading-normal">Analytics</p>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-text-secondary hover:bg-[#282e39] hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="300">settings</span>
<p class="text-sm font-medium leading-normal">Settings</p>
</a>
</div>
</div>
<!-- Upgrade Button -->
<button class="flex w-full cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-primary hover:bg-blue-600 transition-colors text-white text-sm font-bold leading-normal tracking-[0.015em] shadow-lg shadow-blue-900/20">
<span class="truncate">Upgrade</span>
</button>
</div>
</div>
<!-- Main Content Area -->
<div class="flex-1 flex flex-col h-full overflow-hidden relative bg-background-light dark:bg-background-dark">
<!-- Top Header & Breadcrumbs -->
<header class="w-full flex-shrink-0 border-b border-border-dark bg-[#111318]/50 backdrop-blur-sm z-10">
<div class="flex items-center justify-between px-6 py-4">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2 text-sm text-text-secondary">
<a class="hover:text-white transition-colors" href="#">Home</a>
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
<span class="text-white font-medium">Groups</span>
</div>
<h2 class="text-white text-2xl font-bold leading-tight tracking-tight">Landing Page Groups</h2>
</div>
<div class="flex items-center gap-4">
<button class="flex items-center justify-center size-10 rounded-full hover:bg-[#282e39] text-text-secondary transition-colors md:hidden">
<span class="material-symbols-outlined">menu</span>
</button>
<button class="hidden md:flex items-center justify-center size-10 rounded-full hover:bg-[#282e39] text-text-secondary transition-colors relative">
<span class="material-symbols-outlined">notifications</span>
<span class="absolute top-2 right-2 size-2 bg-red-500 rounded-full border-2 border-[#111318]"></span>
</button>
<button class="flex items-center gap-2 h-10 px-4 bg-primary hover:bg-blue-600 text-white rounded-lg text-sm font-bold shadow-lg shadow-primary/20 transition-all active:scale-95">
<span class="material-symbols-outlined text-[20px]">add</span>
<span class="hidden sm:inline">Create New Group</span>
</button>
</div>
</div>
</header>
<!-- Scrollable Content -->
<main class="flex-1 overflow-y-auto p-6 scroll-smooth">
<div class="max-w-[1400px] mx-auto flex flex-col gap-6">
<!-- Search & Filters Toolbar -->
<div class="flex flex-col md:flex-row gap-4 items-start md:items-center justify-between bg-card-dark border border-border-dark p-3 rounded-xl shadow-sm">
<!-- Search -->
<div class="relative w-full md:max-w-md">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</div>
<input class="w-full pl-10 pr-4 py-2.5 bg-[#111318] border border-border-dark rounded-lg text-white placeholder-text-secondary focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary text-sm transition-colors" placeholder="Search groups by name or tag..." type="text"/>
</div>
<!-- Filters -->
<div class="flex flex-wrap items-center gap-3 w-full md:w-auto">
<div class="relative group">
<select class="appearance-none bg-[#111318] border border-border-dark text-white text-sm rounded-lg pl-3 pr-10 py-2.5 focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors w-full md:w-40">
<option>Status: All</option>
<option>Active</option>
<option>Draft</option>
<option>Archived</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none text-text-secondary">
<span class="material-symbols-outlined text-[20px]">expand_more</span>
</div>
</div>
<div class="relative group">
<select class="appearance-none bg-[#111318] border border-border-dark text-white text-sm rounded-lg pl-3 pr-10 py-2.5 focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors w-full md:w-40">
<option>Sort by: Newest</option>
<option>Sort by: Oldest</option>
<option>Sort by: Leads</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none text-text-secondary">
<span class="material-symbols-outlined text-[20px]">sort</span>
</div>
</div>
<button class="p-2.5 bg-[#111318] border border-border-dark rounded-lg text-text-secondary hover:text-white hover:border-gray-500 transition-colors">
<span class="material-symbols-outlined text-[20px] block">filter_list</span>
</button>
</div>
</div>
<!-- Groups Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
<!-- Card 1: Active -->
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-green-500"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">Q3 Marketing Campaign</h3>
<span class="flex h-2 w-2 rounded-full bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]"></span>
</div>
<p class="text-text-secondary text-sm">Created 2 days ago</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<div class="flex gap-4 mb-5 pl-2">
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">Total Leads</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">1,240</span>
<span class="text-green-500 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">trending_up</span>
12%
</span>
</div>
</div>
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">Conv. Rate</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">4.8%</span>
<span class="text-green-500 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">arrow_upward</span>
0.5%
</span>
</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<div class="flex items-center -space-x-2">
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-gray-700 flex items-center justify-center text-xs text-white bg-cover bg-center" data-alt="User avatar 1" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuDqoqeHYVkVTJDf-Q7zsOv8X8D65RM3DXNDoHvetGo_lHPYxTu4qu2rCJabIYEyWG_FMkkzekDUCT24ktGAyx30S2CLmQ8n5aVpYAlFTvT5XHe8ZzaS4XwuZBBgcgyPUNsrUuDhfk27yG2QhhO8msf5oEW_El1hIoV67mgLXJ9j9Ge4R0WboUQJAlwkbeEaZS3hY9wNxST3-WMd0ZZOUJ3_HPjNXpZehqQij1NuESZNlDO73zAzBdOgxd8YA-XtjpETGctExSTUk2vE');"></div>
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-gray-700 flex items-center justify-center text-xs text-white bg-cover bg-center" data-alt="User avatar 2" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuDuT3BYk-99iL5gljI_I1MYQZSkjlxPwEv65196Z2MzrNG8PHOOxOwvDPDITzRWVZLwVA0EZE11bMf8CytEhVeE70zI03WNi_5FcFa0PEHyvYI2zf3Lx-RDa_YpwMqtdLDrd5wsbLCCV2PdHiTkPGqatIE6LW2Qt66R0gh2KR8PAwfc1ewX26q4pptcbzI4CuVDaFPdMG42htObTnNvQl73G4WB7PJIsY33C5bXc1RvahfXCPSBd5OaKT9m-rlrqnkrPoO1Ro71impA');"></div>
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-[#282e39] flex items-center justify-center text-[10px] text-white font-medium">+2</div>
</div>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors border border-transparent hover:border-gray-600">
<span class="material-symbols-outlined text-[16px]">content_copy</span>
Copy URL
</button>
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-primary/20 hover:bg-primary/30 text-primary hover:text-blue-300 transition-colors border border-transparent">
<span class="material-symbols-outlined text-[16px]">tune</span>
Variants
</button>
</div>
</div>
</div>
<!-- Card 2: Inactive -->
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden opacity-90">
<div class="absolute top-0 left-0 w-1 h-full bg-gray-600"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">Black Friday 2023</h3>
<span class="px-2 py-0.5 rounded-full bg-gray-800 border border-gray-700 text-gray-400 text-[10px] font-bold uppercase tracking-wider">Archived</span>
</div>
<p class="text-text-secondary text-sm">Ended Dec 1, 2023</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<div class="flex gap-4 mb-5 pl-2">
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">Total Leads</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">5,100</span>
<span class="text-text-secondary text-xs font-medium mb-1">Final</span>
</div>
</div>
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">Visitors</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">42.5k</span>
<span class="text-text-secondary text-xs font-medium mb-1">Final</span>
</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<span class="text-text-secondary text-xs">3 Variants Tested</span>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors border border-transparent hover:border-gray-600">
<span class="material-symbols-outlined text-[16px]">analytics</span>
Results
</button>
</div>
</div>
</div>
<!-- Card 3: Active with Image Preview -->
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-yellow-500"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">SaaS Webinar Series</h3>
<span class="flex h-2 w-2 rounded-full bg-yellow-500 shadow-[0_0_8px_rgba(234,179,8,0.6)]"></span>
<span class="text-yellow-500 text-[10px] font-bold uppercase tracking-wider ml-1">Draft</span>
</div>
<p class="text-text-secondary text-sm">Last edited 4 hours ago</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<!-- Preview Image Area -->
<div class="w-full h-32 mb-4 rounded-lg bg-center bg-cover relative group/preview cursor-pointer" data-alt="SaaS dashboard analytics preview" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuDuobmXG8vCN3hj-ToqKXoZS3DiTVyvZmv7cBmE3lL2ysV0RFQaj0NV7VqqsbQe3elZnCvYx46g1tOpYGqbhfYMlXGi7KBf0ovxlb3t-QdmZIhWjTCq4zWtjkndwlZFYy5TnJLHKPTh11yyX3oyX5_vSPosi1CdjsIclJKA8LPN5H6ZrS6_B6XjNbUofAtefq4xM7WFAAFH_9EGHSFYEJsK6p5n_ZwvwgBL9rBtFpbEqc35sNfp-zC2gG8Frx6uYPJ7VbBYskVxTdrb');">
<div class="absolute inset-0 bg-black/40 group-hover/preview:bg-black/20 transition-all flex items-center justify-center">
<button class="bg-black/60 backdrop-blur-sm text-white px-3 py-1 rounded-full text-xs font-medium flex items-center gap-1 hover:bg-primary transition-colors">
<span class="material-symbols-outlined text-[14px]">visibility</span>
Preview
</button>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<span class="text-text-secondary text-xs">0 Leads collected</span>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-primary hover:bg-blue-600 transition-colors shadow-sm shadow-blue-900/50">
<span class="material-symbols-outlined text-[16px]">play_arrow</span>
Publish
</button>
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors">
<span class="material-symbols-outlined text-[16px]">edit</span>
Edit
</button>
</div>
</div>
</div>
<!-- Card 4: Active -->
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-green-500"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">Product Hunt Launch</h3>
<span class="flex h-2 w-2 rounded-full bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]"></span>
</div>
<p class="text-text-secondary text-sm">Active for 2 weeks</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<div class="flex gap-4 mb-5 pl-2">
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">Total Leads</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">892</span>
<span class="text-red-400 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">trending_down</span>
2%
</span>
</div>
</div>
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">Impressions</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">12k</span>
<span class="text-green-500 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">arrow_upward</span>
18%
</span>
</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<div class="flex items-center -space-x-2">
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-gray-700 flex items-center justify-center text-xs text-white bg-cover bg-center" data-alt="User avatar 3" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBv1oso4LXTCgoKwjlnaDnVkUoSxqrgbeirpDAkbmVhq6hk7Q35Cer9mzaNoOdHP7bOi0rtHc31IMVmYy-KamZlBDXHgdTlMXmh_wDC4hjtjp_lmuMcD7476H9nswf9BYI93AhAutnRB44cQGecZ659jvrVihlP1jlx9ZE09c7-xx8bqPx9woTR5hlyoRpjPbTV7Hysa5stoKW0NpWA45s6zL5uExlSlkyfaI4lvoaLdOjpALDZ2_fxjfXbD88aGGqBdsuo5ahkbtaG');"></div>
</div>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors border border-transparent hover:border-gray-600">
<span class="material-symbols-outlined text-[16px]">content_copy</span>
Copy URL
</button>
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-primary/20 hover:bg-primary/30 text-primary hover:text-blue-300 transition-colors border border-transparent">
<span class="material-symbols-outlined text-[16px]">tune</span>
Variants
</button>
</div>
</div>
</div>
<!-- Card 5: New Placeholder -->
<div class="bg-[#111318] border-2 border-dashed border-border-dark rounded-xl p-5 flex flex-col items-center justify-center text-center gap-3 hover:border-gray-600 hover:bg-[#1c1f27] transition-all cursor-pointer group min-h-[200px]">
<div class="size-12 rounded-full bg-[#282e39] group-hover:bg-primary/20 flex items-center justify-center transition-colors">
<span class="material-symbols-outlined text-text-secondary group-hover:text-primary text-[28px]">add</span>
</div>
<div>
<h3 class="text-white text-lg font-medium">Create New Group</h3>
<p class="text-text-secondary text-sm mt-1">Start a new A/B test or campaign</p>
</div>
</div>
</div>
<!-- Pagination / Footer -->
<div class="flex items-center justify-between pt-6 border-t border-border-dark mt-4">
<p class="text-text-secondary text-sm">Showing 1 to 4 of 12 groups</p>
<div class="flex gap-2">
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] disabled:opacity-50 text-sm font-medium transition-colors">Previous</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-white bg-[#282e39] hover:bg-[#3b4354] text-sm font-medium transition-colors">1</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] text-sm font-medium transition-colors">2</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] text-sm font-medium transition-colors">3</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] text-sm font-medium transition-colors">Next</button>
</div>
</div>
</div>
</main>
</div>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@@ -0,0 +1,364 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>랜딩 페이지 그룹 목록 - Acme Corp</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"card-dark": "#1c1f27",
"border-dark": "#282e39",
"text-secondary": "#9da6b9",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"sans": ["Inter", "sans-serif"],
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style>
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #111318;
}
::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3b4354;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white font-display overflow-hidden">
<div class="flex h-screen w-full overflow-hidden">
<div class="hidden md:flex flex-col w-64 h-full border-r border-border-dark bg-[#111318] flex-shrink-0">
<div class="flex h-full flex-col justify-between p-4">
<div class="flex flex-col gap-4">
<div class="flex gap-3 items-center px-2 py-1">
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 flex-shrink-0 border border-border-dark" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDOQ3K2IaZ3q70LT08mZ02d1p9CiO5Pr-gqqTE6m1XKF8ntNNhuFjUid9cI5Ooc_Hyq0HhbuSy4T4S9uWo8i0KUmHoNJASbcigmWzpe_V6J7hFxYGkMZGOrrDNJn21X4MIapiFAiDrdCxKoFWET1GvcmvSypznw6Hx1ZsG8LXYKdpVxl5L3O48VVyKpXA1vyBlvK8lQ9YZYpgPRRbihsjGgb-3AvhYCe7GUdnbVPfqrCwJikKMqcJojAxhe01XubTAkGH4oX6DXBNNT");'></div>
<div class="flex flex-col overflow-hidden">
<h1 class="text-white text-base font-medium leading-normal truncate">Acme Corp</h1>
<p class="text-text-secondary text-xs font-normal leading-normal truncate">Pro Plan</p>
</div>
</div>
<div class="flex flex-col gap-2 mt-4">
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-text-secondary hover:bg-[#282e39] hover:text-white transition-colors group" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="300">dashboard</span>
<p class="text-sm font-medium leading-normal">대시보드</p>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg bg-[#282e39] text-white" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="600">folder_copy</span>
<p class="text-sm font-medium leading-normal">그룹</p>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-text-secondary hover:bg-[#282e39] hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="300">bar_chart</span>
<p class="text-sm font-medium leading-normal">통계 및 분석</p>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-text-secondary hover:bg-[#282e39] hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined text-[24px]" data-weight="300">settings</span>
<p class="text-sm font-medium leading-normal">설정</p>
</a>
</div>
</div>
<button class="flex w-full cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-primary hover:bg-blue-600 transition-colors text-white text-sm font-bold leading-normal tracking-[0.015em] shadow-lg shadow-blue-900/20">
<span class="truncate">업그레이드</span>
</button>
</div>
</div>
<div class="flex-1 flex flex-col h-full overflow-hidden relative bg-background-light dark:bg-background-dark">
<header class="w-full flex-shrink-0 border-b border-border-dark bg-[#111318]/50 backdrop-blur-sm z-10">
<div class="flex items-center justify-between px-6 py-4">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2 text-sm text-text-secondary">
<a class="hover:text-white transition-colors" href="#"></a>
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
<span class="text-white font-medium">그룹</span>
</div>
<h2 class="text-white text-2xl font-bold leading-tight tracking-tight">랜딩 페이지 그룹</h2>
</div>
<div class="flex items-center gap-4">
<button class="flex items-center justify-center size-10 rounded-full hover:bg-[#282e39] text-text-secondary transition-colors md:hidden">
<span class="material-symbols-outlined">menu</span>
</button>
<button class="hidden md:flex items-center justify-center size-10 rounded-full hover:bg-[#282e39] text-text-secondary transition-colors relative">
<span class="material-symbols-outlined">notifications</span>
<span class="absolute top-2 right-2 size-2 bg-red-500 rounded-full border-2 border-[#111318]"></span>
</button>
<button class="flex items-center gap-2 h-10 px-4 bg-primary hover:bg-blue-600 text-white rounded-lg text-sm font-bold shadow-lg shadow-primary/20 transition-all active:scale-95">
<span class="material-symbols-outlined text-[20px]">add</span>
<span class="hidden sm:inline">새 그룹 생성</span>
</button>
</div>
</div>
</header>
<main class="flex-1 overflow-y-auto p-6 scroll-smooth">
<div class="max-w-[1400px] mx-auto flex flex-col gap-6">
<div class="flex flex-col md:flex-row gap-4 items-start md:items-center justify-between bg-card-dark border border-border-dark p-3 rounded-xl shadow-sm">
<div class="relative w-full md:max-w-md">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</div>
<input class="w-full pl-10 pr-4 py-2.5 bg-[#111318] border border-border-dark rounded-lg text-white placeholder-text-secondary focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary text-sm transition-colors" placeholder="그룹 이름 또는 태그로 검색..." type="text"/>
</div>
<div class="flex flex-wrap items-center gap-3 w-full md:w-auto">
<div class="relative group">
<select class="appearance-none bg-[#111318] border border-border-dark text-white text-sm rounded-lg pl-3 pr-10 py-2.5 focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors w-full md:w-40">
<option>상태: 전체</option>
<option>활성</option>
<option>초안</option>
<option>보관됨</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none text-text-secondary">
<span class="material-symbols-outlined text-[20px]">expand_more</span>
</div>
</div>
<div class="relative group">
<select class="appearance-none bg-[#111318] border border-border-dark text-white text-sm rounded-lg pl-3 pr-10 py-2.5 focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors w-full md:w-40">
<option>정렬: 최신순</option>
<option>정렬: 오래된순</option>
<option>정렬: 리드순</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none text-text-secondary">
<span class="material-symbols-outlined text-[20px]">sort</span>
</div>
</div>
<button class="p-2.5 bg-[#111318] border border-border-dark rounded-lg text-text-secondary hover:text-white hover:border-gray-500 transition-colors">
<span class="material-symbols-outlined text-[20px] block">filter_list</span>
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-green-500"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">3분기 마케팅 캠페인</h3>
<div class="flex items-center gap-1.5">
<span class="flex h-2 w-2 rounded-full bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]"></span>
<span class="text-green-500 text-[10px] font-bold uppercase tracking-wider">활성</span>
</div>
</div>
<p class="text-text-secondary text-sm">2일 전 생성됨</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<div class="flex gap-4 mb-5 pl-2">
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">총 리드 수</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">1,240</span>
<span class="text-green-500 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">trending_up</span>
12%
</span>
</div>
</div>
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">전환율</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">4.8%</span>
<span class="text-green-500 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">arrow_upward</span>
0.5%
</span>
</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<div class="flex items-center -space-x-2">
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-gray-700 flex items-center justify-center text-xs text-white bg-cover bg-center" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuDqoqeHYVkVTJDf-Q7zsOv8X8D65RM3DXNDoHvetGo_lHPYxTu4qu2rCJabIYEyWG_FMkkzekDUCT24ktGAyx30S2CLmQ8n5aVpYAlFTvT5XHe8ZzaS4XwuZBBgcgyPUNsrUuDhfk27yG2QhhO8msf5oEW_El1hIoV67mgLXJ9j9Ge4R0WboUQJAlwkbeEaZS3hY9wNxST3-WMd0ZZOUJ3_HPjNXpZehqQij1NuESZNlDO73zAzBdOgxd8YA-XtjpETGctExSTUk2vE');"></div>
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-gray-700 flex items-center justify-center text-xs text-white bg-cover bg-center" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuDuT3BYk-99iL5gljI_I1MYQZSkjlxPwEv65196Z2MzrNG8PHOOxOwvDPDITzRWVZLwVA0EZE11bMf8CytEhVeE70zI03WNi_5FcFa0PEHyvYI2zf3Lx-RDa_YpwMqtdLDrd5wsbLCCV2PdHiTkPGqatIE6LW2Qt66R0gh2KR8PAwfc1ewX26q4pptcbzI4CuVDaFPdMG42htObTnNvQl73G4WB7PJIsY33C5bXc1RvahfXCPSBd5OaKT9m-rlrqnkrPoO1Ro71impA');"></div>
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-[#282e39] flex items-center justify-center text-[10px] text-white font-medium">+2</div>
</div>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors border border-transparent hover:border-gray-600">
<span class="material-symbols-outlined text-[16px]">content_copy</span>
URL 복사
</button>
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-primary/20 hover:bg-primary/30 text-primary hover:text-blue-300 transition-colors border border-transparent">
<span class="material-symbols-outlined text-[16px]">tune</span>
변수/페이지
</button>
</div>
</div>
</div>
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden opacity-90">
<div class="absolute top-0 left-0 w-1 h-full bg-gray-600"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">블랙 프라이데이 2023</h3>
<span class="px-2 py-0.5 rounded-full bg-gray-800 border border-gray-700 text-gray-400 text-[10px] font-bold uppercase tracking-wider">보관됨</span>
</div>
<p class="text-text-secondary text-sm">2023년 12월 1일 종료</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<div class="flex gap-4 mb-5 pl-2">
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">총 리드 수</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">5,100</span>
<span class="text-text-secondary text-xs font-medium mb-1">최종</span>
</div>
</div>
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">방문자</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">42.5k</span>
<span class="text-text-secondary text-xs font-medium mb-1">최종</span>
</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<span class="text-text-secondary text-xs">3개 변수 테스트됨</span>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors border border-transparent hover:border-gray-600">
<span class="material-symbols-outlined text-[16px]">analytics</span>
결과 보기
</button>
</div>
</div>
</div>
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-yellow-500"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">SaaS 웨비나 시리즈</h3>
<span class="flex h-2 w-2 rounded-full bg-yellow-500 shadow-[0_0_8px_rgba(234,179,8,0.6)]"></span>
<span class="text-yellow-500 text-[10px] font-bold uppercase tracking-wider ml-1">초안</span>
</div>
<p class="text-text-secondary text-sm">4시간 전 편집됨</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<div class="w-full h-32 mb-4 rounded-lg bg-center bg-cover relative group/preview cursor-pointer" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuDuobmXG8vCN3hj-ToqKXoZS3DiTVyvZmv7cBmE3lL2ysV0RFQaj0NV7VqqsbQe3elZnCvYx46g1tOpYGqbhfYMlXGi7KBf0ovxlb3t-QdmZIhWjTCq4zWtjkndwlZFYy5TnJLHKPTh11yyX3oyX5_vSPosi1CdjsIclJKA8LPN5H6ZrS6_B6XjNbUofAtefq4xM7WFAAFH_9EGHSFYEJsK6p5n_ZwvwgBL9rBtFpbEqc35sNfp-zC2gG8Frx6uYPJ7VbBYskVxTdrb');">
<div class="absolute inset-0 bg-black/40 group-hover/preview:bg-black/20 transition-all flex items-center justify-center">
<button class="bg-black/60 backdrop-blur-sm text-white px-3 py-1 rounded-full text-xs font-medium flex items-center gap-1 hover:bg-primary transition-colors">
<span class="material-symbols-outlined text-[14px]">visibility</span>
미리보기
</button>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<span class="text-text-secondary text-xs">수집된 리드 없음</span>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-primary hover:bg-blue-600 transition-colors shadow-sm shadow-blue-900/50">
<span class="material-symbols-outlined text-[16px]">play_arrow</span>
게시하기
</button>
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors">
<span class="material-symbols-outlined text-[16px]">edit</span>
편집
</button>
</div>
</div>
</div>
<div class="bg-card-dark border border-border-dark rounded-xl p-5 hover:border-gray-600 transition-all hover:shadow-lg hover:shadow-black/20 group relative overflow-hidden">
<div class="absolute top-0 left-0 w-1 h-full bg-green-500"></div>
<div class="flex justify-between items-start mb-4 pl-2">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-white text-lg font-semibold truncate max-w-[200px]">프로덕트 헌트 런칭</h3>
<div class="flex items-center gap-1.5">
<span class="flex h-2 w-2 rounded-full bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]"></span>
<span class="text-green-500 text-[10px] font-bold uppercase tracking-wider">활성</span>
</div>
</div>
<p class="text-text-secondary text-sm">2주 동안 활성 중</p>
</div>
<button class="text-text-secondary hover:text-white p-1 rounded hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined text-[20px]">more_vert</span>
</button>
</div>
<div class="flex gap-4 mb-5 pl-2">
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">총 리드 수</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">892</span>
<span class="text-red-400 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">trending_down</span>
2%
</span>
</div>
</div>
<div class="flex flex-col p-3 bg-[#111318] rounded-lg border border-border-dark flex-1">
<span class="text-text-secondary text-xs font-medium uppercase tracking-wider mb-1">노출 수</span>
<div class="flex items-end gap-2">
<span class="text-white text-2xl font-bold">12k</span>
<span class="text-green-500 text-xs font-medium mb-1 flex items-center">
<span class="material-symbols-outlined text-[14px]">arrow_upward</span>
18%
</span>
</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-border-dark pt-4 pl-2">
<div class="flex items-center -space-x-2">
<div class="w-8 h-8 rounded-full border-2 border-card-dark bg-gray-700 flex items-center justify-center text-xs text-white bg-cover bg-center" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBv1oso4LXTCgoKwjlnaDnVkUoSxqrgbeirpDAkbmVhq6hk7Q35Cer9mzaNoOdHP7bOi0rtHc31IMVmYy-KamZlBDXHgdTlMXmh_wDC4hjtjp_lmuMcD7476H9nswf9BYI93AhAutnRB44cQGecZ659jvrVihlP1jlx9ZE09c7-xx8bqPx9woTR5hlyoRpjPbTV7Hysa5stoKW0NpWA45s6zL5uExlSlkyfaI4lvoaLdOjpALDZ2_fxjfXbD88aGGqBdsuo5ahkbtaG');"></div>
</div>
<div class="flex gap-2">
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-[#282e39] hover:bg-[#3b4354] transition-colors border border-transparent hover:border-gray-600">
<span class="material-symbols-outlined text-[16px]">content_copy</span>
URL 복사
</button>
<button class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium text-white bg-primary/20 hover:bg-primary/30 text-primary hover:text-blue-300 transition-colors border border-transparent">
<span class="material-symbols-outlined text-[16px]">tune</span>
변수/페이지
</button>
</div>
</div>
</div>
<div class="bg-[#111318] border-2 border-dashed border-border-dark rounded-xl p-5 flex flex-col items-center justify-center text-center gap-3 hover:border-gray-600 hover:bg-[#1c1f27] transition-all cursor-pointer group min-h-[200px]">
<div class="size-12 rounded-full bg-[#282e39] group-hover:bg-primary/20 flex items-center justify-center transition-colors">
<span class="material-symbols-outlined text-text-secondary group-hover:text-primary text-[28px]">add</span>
</div>
<div>
<h3 class="text-white text-lg font-medium">새 그룹 생성</h3>
<p class="text-text-secondary text-sm mt-1">새로운 A/B 테스트 또는 캠페인을 시작하세요</p>
</div>
</div>
</div>
<div class="flex items-center justify-between pt-6 border-t border-border-dark mt-4">
<p class="text-text-secondary text-sm">12개 그룹 중 1-4 표시</p>
<div class="flex gap-2">
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] disabled:opacity-50 text-sm font-medium transition-colors">이전</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-white bg-[#282e39] hover:bg-[#3b4354] text-sm font-medium transition-colors">1</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] text-sm font-medium transition-colors">2</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] text-sm font-medium transition-colors">3</button>
<button class="px-3 py-1.5 rounded-lg border border-border-dark text-text-secondary hover:text-white hover:bg-[#282e39] text-sm font-medium transition-colors">다음</button>
</div>
</div>
</div>
</main>
</div>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

View File

@@ -0,0 +1,474 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Leads &amp; Analytics Logs</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1c2333",
"border-dark": "#2d3648",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"]
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style>
/* Custom scrollbar for better dark mode experience */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #101622;
}
::-webkit-scrollbar-thumb {
background: #2d3648;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3b4354;
}
/* Utility for Material Symbols to ensure alignment */
.material-symbols-outlined {
font-size: 20px;
vertical-align: middle;
}
.icon-sm {
font-size: 18px;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display antialiased overflow-hidden">
<div class="relative flex h-screen w-full flex-row overflow-hidden">
<!-- Side Navigation -->
<aside class="flex h-full w-64 flex-col border-r border-border-dark bg-[#111318] flex-shrink-0 z-20">
<div class="flex flex-col h-full justify-between p-4">
<div class="flex flex-col gap-4">
<!-- Brand -->
<div class="flex gap-3 items-center px-2 py-1 mb-2">
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-8 bg-primary/20 flex items-center justify-center text-primary" data-alt="Abstract geometric logo">
<span class="material-symbols-outlined filled">pentagon</span>
</div>
<div class="flex flex-col">
<h1 class="text-white text-sm font-semibold leading-tight">Acme Corp</h1>
<p class="text-gray-500 text-xs font-normal leading-tight">Pro Plan</p>
</div>
</div>
<!-- Navigation Items -->
<nav class="flex flex-col gap-1">
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/5 transition-colors group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">dashboard</span>
<span class="text-sm font-medium">Dashboard</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/5 transition-colors group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">web</span>
<span class="text-sm font-medium">Landing Pages</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg bg-primary/10 text-primary" href="#">
<span class="material-symbols-outlined filled">group</span>
<span class="text-sm font-medium">Leads &amp; Logs</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/5 transition-colors group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">science</span>
<span class="text-sm font-medium">A/B Tests</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/5 transition-colors group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">settings</span>
<span class="text-sm font-medium">Settings</span>
</a>
</nav>
</div>
<!-- Bottom Action -->
<button class="flex w-full items-center justify-center gap-2 rounded-lg h-10 px-4 bg-primary text-white text-sm font-semibold hover:bg-primary/90 transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined icon-sm">add</span>
<span>Create Page</span>
</button>
</div>
</aside>
<!-- Main Content Area -->
<main class="flex-1 flex flex-col h-full overflow-hidden bg-background-light dark:bg-background-dark relative">
<!-- Top Header -->
<header class="h-16 border-b border-border-dark bg-[#111318]/95 backdrop-blur flex items-center justify-between px-6 shrink-0 z-10">
<div class="flex items-center gap-4">
<h2 class="text-white text-lg font-semibold">Leads &amp; Analytics Logs</h2>
<span class="h-4 w-px bg-border-dark"></span>
<div class="flex items-center gap-2 text-gray-400 text-sm">
<span class="material-symbols-outlined icon-sm">calendar_today</span>
<span>Last 30 Days</span>
</div>
</div>
<div class="flex items-center gap-4">
<!-- User Profile / Notifications -->
<button class="size-8 rounded-full bg-surface-dark border border-border-dark flex items-center justify-center text-gray-400 hover:text-white transition-colors">
<span class="material-symbols-outlined icon-sm">notifications</span>
</button>
<div class="size-8 rounded-full bg-gradient-to-tr from-primary to-purple-500" data-alt="User avatar gradient"></div>
</div>
</header>
<!-- Scrollable Content -->
<div class="flex-1 overflow-y-auto p-6">
<div class="mx-auto max-w-[1200px] flex flex-col gap-6">
<!-- Page Intro & Key Actions -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div class="flex flex-col gap-1">
<h1 class="text-white text-3xl font-bold tracking-tight">Leads Overview</h1>
<p class="text-gray-400 text-sm">Manage, filter, and export your form submissions and event data from all landing pages.</p>
</div>
<div class="flex items-center gap-3">
<button class="flex items-center gap-2 h-9 px-4 rounded-lg border border-border-dark bg-surface-dark text-white text-sm font-medium hover:bg-border-dark transition-colors">
<span class="material-symbols-outlined icon-sm">refresh</span>
<span>Refresh</span>
</button>
<button class="flex items-center gap-2 h-9 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary/90 transition-colors shadow-lg shadow-primary/25">
<span class="material-symbols-outlined icon-sm">download</span>
<span>Export CSV</span>
</button>
</div>
</div>
<!-- Filters Bar -->
<div class="p-4 rounded-xl border border-border-dark bg-[#111318] flex flex-wrap gap-4 items-center">
<!-- Search -->
<div class="relative flex-1 min-w-[240px]">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 material-symbols-outlined icon-sm">search</span>
<input class="w-full h-10 pl-10 pr-4 bg-surface-dark border border-border-dark rounded-lg text-sm text-white placeholder-gray-500 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" placeholder="Search by name, email or ID..." type="text"/>
</div>
<div class="h-6 w-px bg-border-dark hidden md:block"></div>
<!-- Dropdown Filters -->
<div class="flex flex-wrap gap-3 flex-1 md:flex-none">
<div class="relative group">
<select class="appearance-none h-10 pl-3 pr-10 bg-surface-dark border border-border-dark rounded-lg text-sm text-white focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors min-w-[140px]">
<option>All Variants</option>
<option>Variant A</option>
<option>Variant B</option>
<option>Variant C</option>
</select>
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none material-symbols-outlined icon-sm">expand_more</span>
</div>
<div class="relative group">
<select class="appearance-none h-10 pl-3 pr-10 bg-surface-dark border border-border-dark rounded-lg text-sm text-white focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors min-w-[140px]">
<option>All Channels</option>
<option>Organic Search</option>
<option>Paid Ads</option>
<option>Social Media</option>
<option>Direct</option>
</select>
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none material-symbols-outlined icon-sm">expand_more</span>
</div>
<div class="relative group">
<select class="appearance-none h-10 pl-3 pr-10 bg-surface-dark border border-border-dark rounded-lg text-sm text-white focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors min-w-[140px]">
<option>Status: All</option>
<option>New</option>
<option>Contacted</option>
<option>Qualified</option>
<option>Spam</option>
</select>
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none material-symbols-outlined icon-sm">expand_more</span>
</div>
</div>
</div>
<!-- Data Table -->
<div class="rounded-xl border border-border-dark bg-[#111318] overflow-hidden flex flex-col shadow-sm">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-surface-dark border-b border-border-dark">
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider w-[60px]">
<input class="rounded bg-background-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Lead Profile</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Variant</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Source Channel</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Submission Date</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-border-dark">
<!-- Row 1 -->
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-blue-500 to-cyan-400 text-xs font-bold text-white flex items-center justify-center shrink-0">AJ</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Alice Johnson</span>
<span class="text-gray-500 text-xs">alice@example.com</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20">
Variant B
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-green-500 icon-sm">ads_click</span>
Google Ads
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
Oct 24, 2023 <span class="text-gray-600 mx-1">|</span> 14:30
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
<span class="size-1.5 rounded-full bg-blue-400"></span> New
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<!-- Row 2 -->
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-orange-500 to-amber-400 text-xs font-bold text-white flex items-center justify-center shrink-0">MS</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Mark Smith</span>
<span class="text-gray-500 text-xs">mark.s@company.com</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
Variant A
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-gray-400 icon-sm">search</span>
Organic Search
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
Oct 24, 2023 <span class="text-gray-600 mx-1">|</span> 13:15
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-500/10 text-amber-400 border border-amber-500/20">
<span class="size-1.5 rounded-full bg-amber-400"></span> Contacted
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<!-- Row 3 -->
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-pink-500 to-rose-400 text-xs font-bold text-white flex items-center justify-center shrink-0">SL</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Sarah Lee</span>
<span class="text-gray-500 text-xs">sarah.lee99@gmail.com</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
Variant A
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-blue-500 icon-sm">public</span>
Facebook Ads
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
Oct 23, 2023 <span class="text-gray-600 mx-1">|</span> 09:45
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
<span class="size-1.5 rounded-full bg-blue-400"></span> New
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<!-- Row 4 -->
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-surface-dark border border-border-dark text-xs font-bold text-gray-300 flex items-center justify-center shrink-0">DC</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">David Chen</span>
<span class="text-gray-500 text-xs">dchen@tech.io</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
Variant C
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-gray-400 icon-sm">link</span>
Direct
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
Oct 22, 2023 <span class="text-gray-600 mx-1">|</span> 16:20
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
<span class="size-1.5 rounded-full bg-green-400"></span> Qualified
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<!-- Row 5 -->
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-indigo-500 to-blue-400 text-xs font-bold text-white flex items-center justify-center shrink-0">EW</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Emma Wilson</span>
<span class="text-gray-500 text-xs">emma.w@studio.co</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20">
Variant B
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-blue-600 icon-sm">group_work</span>
LinkedIn
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
Oct 22, 2023 <span class="text-gray-600 mx-1">|</span> 11:00
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
<span class="size-1.5 rounded-full bg-blue-400"></span> New
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<!-- Row 6 -->
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gray-700 text-xs font-bold text-gray-300 flex items-center justify-center shrink-0">JB</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">James Bond</span>
<span class="text-gray-500 text-xs">007@mi6.gov.uk</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
Variant A
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-purple-400 icon-sm">person_add</span>
Referral
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
Oct 21, 2023 <span class="text-gray-600 mx-1">|</span> 18:30
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-500/10 text-red-400 border border-red-500/20">
<span class="size-1.5 rounded-full bg-red-400"></span> Spam
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="px-6 py-4 bg-surface-dark border-t border-border-dark flex items-center justify-between">
<div class="text-xs text-gray-400">
Showing <span class="text-white font-medium">1-6</span> of <span class="text-white font-medium">124</span> results
</div>
<div class="flex items-center gap-2">
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled="">
<span class="material-symbols-outlined icon-sm">chevron_left</span>
</button>
<button class="flex items-center justify-center size-8 rounded-lg bg-primary text-white text-sm font-medium shadow-md shadow-primary/20">
1
</button>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
2
</button>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
3
</button>
<span class="text-gray-500 text-xs px-1">...</span>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
8
</button>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
<span class="material-symbols-outlined icon-sm">chevron_right</span>
</button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

View File

@@ -0,0 +1,446 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>리드 및 분석 로그</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1c2333",
"border-dark": "#2d3648",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"]
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style>
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #101622;
}
::-webkit-scrollbar-thumb {
background: #2d3648;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3b4354;
}
.material-symbols-outlined {
font-size: 20px;
vertical-align: middle;
}
.icon-sm {
font-size: 18px;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display antialiased overflow-hidden">
<div class="relative flex h-screen w-full flex-row overflow-hidden">
<aside class="flex h-full w-64 flex-col border-r border-border-dark bg-[#111318] flex-shrink-0 z-20">
<div class="flex flex-col h-full justify-between p-4">
<div class="flex flex-col gap-4">
<div class="flex gap-3 items-center px-2 py-1 mb-2">
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-8 bg-primary/20 flex items-center justify-center text-primary">
<span class="material-symbols-outlined filled">pentagon</span>
</div>
<div class="flex flex-col">
<h1 class="text-white text-sm font-semibold leading-tight">Acme Corp</h1>
<p class="text-gray-500 text-xs font-normal leading-tight">Pro Plan</p>
</div>
</div>
<nav class="flex flex-col gap-1">
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/5 transition-colors group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">web</span>
<span class="text-sm font-medium">랜딩 페이지</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/5 transition-colors group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">analytics</span>
<span class="text-sm font-medium">통계 및 분석</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg bg-primary/10 text-primary" href="#">
<span class="material-symbols-outlined filled">group</span>
<span class="text-sm font-medium">리드 관리</span>
</a>
<a class="flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-white/5 transition-colors group" href="#">
<span class="material-symbols-outlined group-hover:text-primary transition-colors">settings</span>
<span class="text-sm font-medium">설정</span>
</a>
</nav>
</div>
<button class="flex w-full items-center justify-center gap-2 rounded-lg h-10 px-4 bg-primary text-white text-sm font-semibold hover:bg-primary/90 transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined icon-sm">add</span>
<span>페이지 생성</span>
</button>
</div>
</aside>
<main class="flex-1 flex flex-col h-full overflow-hidden bg-background-light dark:bg-background-dark relative">
<header class="h-16 border-b border-border-dark bg-[#111318]/95 backdrop-blur flex items-center justify-between px-6 shrink-0 z-10">
<div class="flex items-center gap-4">
<h2 class="text-white text-lg font-semibold">리드 및 분석 로그</h2>
<span class="h-4 w-px bg-border-dark"></span>
<div class="flex items-center gap-2 text-gray-400 text-sm">
<span class="material-symbols-outlined icon-sm">calendar_today</span>
<span>최근 30일</span>
</div>
</div>
<div class="flex items-center gap-4">
<button class="size-8 rounded-full bg-surface-dark border border-border-dark flex items-center justify-center text-gray-400 hover:text-white transition-colors">
<span class="material-symbols-outlined icon-sm">notifications</span>
</button>
<div class="size-8 rounded-full bg-gradient-to-tr from-primary to-purple-500"></div>
</div>
</header>
<div class="flex-1 overflow-y-auto p-6">
<div class="mx-auto max-w-[1200px] flex flex-col gap-6">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div class="flex flex-col gap-1">
<h1 class="text-white text-3xl font-bold tracking-tight">리드 개요</h1>
<p class="text-gray-400 text-sm">랜딩 페이지에서 수집된 리드 및 이벤트 데이터를 관리하고 내보낼 수 있습니다.</p>
</div>
<div class="flex items-center gap-3">
<button class="flex items-center gap-2 h-9 px-4 rounded-lg border border-border-dark bg-surface-dark text-white text-sm font-medium hover:bg-border-dark transition-colors">
<span class="material-symbols-outlined icon-sm">refresh</span>
<span>새로고침</span>
</button>
<button class="flex items-center gap-2 h-9 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary/90 transition-colors shadow-lg shadow-primary/25">
<span class="material-symbols-outlined icon-sm">download</span>
<span>CSV 내보내기</span>
</button>
</div>
</div>
<div class="p-4 rounded-xl border border-border-dark bg-[#111318] flex flex-wrap gap-4 items-center">
<div class="relative flex-1 min-w-[240px]">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 material-symbols-outlined icon-sm">search</span>
<input class="w-full h-10 pl-10 pr-4 bg-surface-dark border border-border-dark rounded-lg text-sm text-white placeholder-gray-500 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all" placeholder="이름, 이메일 등으로 검색..." type="text"/>
</div>
<div class="h-6 w-px bg-border-dark hidden md:block"></div>
<div class="flex flex-wrap gap-3 flex-1 md:flex-none">
<div class="relative group">
<select class="appearance-none h-10 pl-3 pr-10 bg-surface-dark border border-border-dark rounded-lg text-sm text-white focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors min-w-[140px]">
<option>모든 변수</option>
<option>Variant A</option>
<option>Variant B</option>
<option>Variant C</option>
</select>
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none material-symbols-outlined icon-sm">expand_more</span>
</div>
<div class="relative group">
<select class="appearance-none h-10 pl-3 pr-10 bg-surface-dark border border-border-dark rounded-lg text-sm text-white focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors min-w-[140px]">
<option>모든 채널</option>
<option>Organic Search</option>
<option>Paid Ads</option>
<option>Social Media</option>
<option>Direct</option>
</select>
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none material-symbols-outlined icon-sm">expand_more</span>
</div>
<div class="relative group">
<select class="appearance-none h-10 pl-3 pr-10 bg-surface-dark border border-border-dark rounded-lg text-sm text-white focus:outline-none focus:border-primary cursor-pointer hover:border-gray-500 transition-colors min-w-[140px]">
<option>상태: 전체</option>
<option>신규</option>
<option>연락됨</option>
<option>적격</option>
<option>스팸</option>
</select>
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none material-symbols-outlined icon-sm">expand_more</span>
</div>
</div>
</div>
<div class="rounded-xl border border-border-dark bg-[#111318] overflow-hidden flex flex-col shadow-sm">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-surface-dark border-b border-border-dark">
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider w-[60px]">
<input class="rounded bg-background-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">리드 프로필</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">페이지 변수</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">유입 채널</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">신청 일시</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">상태</th>
<th class="px-6 py-3 text-xs font-semibold text-gray-400 uppercase tracking-wider text-right">작업</th>
</tr>
</thead>
<tbody class="divide-y divide-border-dark">
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-blue-500 to-cyan-400 text-xs font-bold text-white flex items-center justify-center shrink-0">AJ</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Alice Johnson</span>
<span class="text-gray-500 text-xs">alice@example.com</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20">
Variant B
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-green-500 icon-sm">ads_click</span>
Google Ads
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
2023.10.24 <span class="text-gray-600 mx-1">|</span> 14:30
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
<span class="size-1.5 rounded-full bg-blue-400"></span> 신규
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-orange-500 to-amber-400 text-xs font-bold text-white flex items-center justify-center shrink-0">MS</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Mark Smith</span>
<span class="text-gray-500 text-xs">mark.s@company.com</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
Variant A
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-gray-400 icon-sm">search</span>
Organic Search
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
2023.10.24 <span class="text-gray-600 mx-1">|</span> 13:15
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-500/10 text-amber-400 border border-amber-500/20">
<span class="size-1.5 rounded-full bg-amber-400"></span> 연락됨
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-pink-500 to-rose-400 text-xs font-bold text-white flex items-center justify-center shrink-0">SL</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Sarah Lee</span>
<span class="text-gray-500 text-xs">sarah.lee99@gmail.com</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
Variant A
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-blue-500 icon-sm">public</span>
Facebook Ads
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
2023.10.23 <span class="text-gray-600 mx-1">|</span> 09:45
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
<span class="size-1.5 rounded-full bg-blue-400"></span> 신규
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-surface-dark border border-border-dark text-xs font-bold text-gray-300 flex items-center justify-center shrink-0">DC</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">David Chen</span>
<span class="text-gray-500 text-xs">dchen@tech.io</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
Variant C
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-gray-400 icon-sm">link</span>
Direct
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
2023.10.22 <span class="text-gray-600 mx-1">|</span> 16:20
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
<span class="size-1.5 rounded-full bg-green-400"></span> 적격
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gradient-to-br from-indigo-500 to-blue-400 text-xs font-bold text-white flex items-center justify-center shrink-0">EW</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">Emma Wilson</span>
<span class="text-gray-500 text-xs">emma.w@studio.co</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20">
Variant B
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-blue-600 icon-sm">group_work</span>
LinkedIn
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
2023.10.22 <span class="text-gray-600 mx-1">|</span> 11:00
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
<span class="size-1.5 rounded-full bg-blue-400"></span> 신규
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-6 py-4">
<input class="rounded bg-surface-dark border-border-dark text-primary focus:ring-0 focus:ring-offset-0 cursor-pointer" type="checkbox"/>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<div class="size-9 rounded-full bg-gray-700 text-xs font-bold text-gray-300 flex items-center justify-center shrink-0">JB</div>
<div class="flex flex-col">
<span class="text-white text-sm font-medium">James Bond</span>
<span class="text-gray-500 text-xs">007@mi6.gov.uk</span>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/10 text-blue-400 border border-blue-500/20">
Variant A
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-gray-300 text-sm">
<span class="material-symbols-outlined text-purple-400 icon-sm">person_add</span>
Referral
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-400 font-mono">
2023.10.21 <span class="text-gray-600 mx-1">|</span> 18:30
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-500/10 text-red-400 border border-red-500/20">
<span class="size-1.5 rounded-full bg-red-400"></span> 스팸
</span>
</td>
<td class="px-6 py-4 text-right">
<button class="text-gray-500 hover:text-white transition-colors p-1 rounded hover:bg-white/10">
<span class="material-symbols-outlined">more_vert</span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="px-6 py-4 bg-surface-dark border-t border-border-dark flex items-center justify-between">
<div class="text-xs text-gray-400">
124개 결과 중 1-6 표시
</div>
<div class="flex items-center gap-2">
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled="">
<span class="material-symbols-outlined icon-sm">chevron_left</span>
</button>
<button class="flex items-center justify-center size-8 rounded-lg bg-primary text-white text-sm font-medium shadow-md shadow-primary/20">
1
</button>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
2
</button>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
3
</button>
<span class="text-gray-500 text-xs px-1">...</span>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
8
</button>
<button class="flex items-center justify-center size-8 rounded-lg border border-border-dark bg-[#111318] text-gray-400 hover:text-white hover:border-gray-500 transition-colors">
<span class="material-symbols-outlined icon-sm">chevron_right</span>
</button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

View File

@@ -0,0 +1,207 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Public Landing Page Mobile View - Variant A</title>
<!-- Google Fonts: Inter -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;900&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS with Plugins -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<!-- Tailwind Configuration -->
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"mobile-bg": "#1c1f27", // Slightly lighter for the mobile container
"desktop-bg": "#0f0f11", // Deep dark for desktop bg
},
fontFamily: {
"display": ["Inter", "sans-serif"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
boxShadow: {
'device': '0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)',
}
},
},
}
</script>
<style>
/* Custom scrollbar for the mobile container to keep it sleek */
.mobile-scroll::-webkit-scrollbar {
width: 4px;
}
.mobile-scroll::-webkit-scrollbar-track {
background: transparent;
}
.mobile-scroll::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 20px;
}
</style>
</head>
<body class="bg-background-light dark:bg-desktop-bg font-display min-h-screen flex items-center justify-center p-4 overflow-hidden">
<!-- Desktop Wrapper simulating the environment -->
<div class="relative w-full h-full flex flex-col items-center justify-center">
<!-- Background decorative elements for the 'Desktop' view -->
<div class="absolute inset-0 overflow-hidden pointer-events-none -z-10">
<div class="absolute top-0 left-1/4 w-[500px] h-[500px] bg-primary/10 rounded-full blur-[100px] opacity-30"></div>
<div class="absolute bottom-0 right-1/4 w-[600px] h-[600px] bg-purple-900/10 rounded-full blur-[120px] opacity-30"></div>
</div>
<!-- Mobile Device Simulation Container -->
<div class="relative w-full max-w-[420px] h-[85vh] bg-background-light dark:bg-mobile-bg rounded-2xl shadow-device overflow-hidden flex flex-col border border-neutral-200 dark:border-neutral-800">
<!-- Mobile Status Bar Simulation (Cosmetic) -->
<div class="h-8 w-full bg-background-light dark:bg-mobile-bg flex items-center justify-between px-6 shrink-0 z-20">
<span class="text-xs font-semibold text-neutral-900 dark:text-white">9:41</span>
<div class="flex gap-1.5 items-center">
<span class="material-symbols-outlined text-[16px] text-neutral-900 dark:text-white">signal_cellular_alt</span>
<span class="material-symbols-outlined text-[16px] text-neutral-900 dark:text-white">wifi</span>
<span class="material-symbols-outlined text-[16px] text-neutral-900 dark:text-white">battery_full</span>
</div>
</div>
<!-- Scrollable Content Area -->
<div class="mobile-scroll flex-1 overflow-y-auto pb-24 relative">
<!-- Hero Section (GIF Placeholder) -->
<div class="w-full h-64 bg-neutral-800 relative overflow-hidden group">
<!-- Loading Skeleton -->
<div class="absolute inset-0 bg-neutral-800 animate-pulse z-0"></div>
<!-- Image -->
<div class="absolute inset-0 bg-cover bg-center z-10" data-alt="Abstract dark blue and purple digital waves flowing smoothly" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuB5wevfRjjZoTDfk00aTMwnULNr7PdLv6cEkFL95TKdN5-8Zru3FPWHspOobe9lMpsiP7L7f_U1eyXqgyMuY8SArhLa9UhJgKl1IeOQWg2l4LpV5Ta8eiPIfAGGva4WbacutKsVSvVEYGpwCZ92DWWREgc-lNpG5I084xuwHRmypwN_j1L1OqQbb1OBBMS2BdQO3U7H75KnzUjks422kejYHtsFaQu1AuEMgGb5wfI9wIzCvBSUtv7cMK_V6i0JagrBj2E_DPrKZ3Bk');">
</div>
<div class="absolute inset-0 bg-gradient-to-t from-background-light dark:from-mobile-bg to-transparent z-20"></div>
</div>
<!-- Content Body -->
<div class="px-6 -mt-8 relative z-30 flex flex-col gap-8">
<!-- Headline & Intro -->
<div class="text-center">
<div class="inline-flex items-center gap-1 bg-primary/10 border border-primary/20 rounded-full px-3 py-1 mb-4">
<span class="w-2 h-2 rounded-full bg-primary animate-pulse"></span>
<span class="text-xs font-medium text-primary uppercase tracking-wider">New Version 2.0</span>
</div>
<h1 class="text-3xl font-black text-neutral-900 dark:text-white leading-tight mb-3">
Accelerate Your Workflow 10x
</h1>
<p class="text-neutral-600 dark:text-neutral-400 text-sm leading-relaxed">
Join thousands of designers shipping faster. No credit card required. Start your A/B testing journey today with our automated tools.
</p>
</div>
<!-- Features Grid -->
<div class="grid grid-cols-1 gap-3">
<!-- Feature 1 -->
<div class="flex items-start gap-4 p-4 rounded-lg bg-white dark:bg-[#252932] border border-neutral-100 dark:border-[#3b4354]">
<div class="p-2 rounded-lg bg-primary/10 text-primary shrink-0">
<span class="material-symbols-outlined">bolt</span>
</div>
<div>
<h3 class="font-bold text-neutral-900 dark:text-white text-sm mb-1">Lightning Fast</h3>
<p class="text-xs text-neutral-500 dark:text-[#9da6b9]">Optimized for speed with edge caching worldwide.</p>
</div>
</div>
<!-- Feature 2 -->
<div class="flex items-start gap-4 p-4 rounded-lg bg-white dark:bg-[#252932] border border-neutral-100 dark:border-[#3b4354]">
<div class="p-2 rounded-lg bg-primary/10 text-primary shrink-0">
<span class="material-symbols-outlined">security</span>
</div>
<div>
<h3 class="font-bold text-neutral-900 dark:text-white text-sm mb-1">Secure by Design</h3>
<p class="text-xs text-neutral-500 dark:text-[#9da6b9]">Enterprise-grade security and SOC2 compliance.</p>
</div>
</div>
<!-- Feature 3 -->
<div class="flex items-start gap-4 p-4 rounded-lg bg-white dark:bg-[#252932] border border-neutral-100 dark:border-[#3b4354]">
<div class="p-2 rounded-lg bg-primary/10 text-primary shrink-0">
<span class="material-symbols-outlined">analytics</span>
</div>
<div>
<h3 class="font-bold text-neutral-900 dark:text-white text-sm mb-1">Analytics First</h3>
<p class="text-xs text-neutral-500 dark:text-[#9da6b9]">Deep insights included in every plan tier.</p>
</div>
</div>
</div>
<!-- Social Proof / Trust -->
<div class="py-4 border-t border-neutral-200 dark:border-[#3b4354]">
<p class="text-center text-xs text-neutral-400 dark:text-neutral-500 mb-4 uppercase tracking-widest font-semibold">Trusted by leading teams</p>
<div class="flex justify-between items-center opacity-50 grayscale gap-2">
<!-- Logos simulated with text for simplicity/reliability -->
<span class="text-sm font-bold text-neutral-800 dark:text-white">ACME</span>
<span class="text-sm font-bold text-neutral-800 dark:text-white">Globex</span>
<span class="text-sm font-bold text-neutral-800 dark:text-white">Soylent</span>
<span class="text-sm font-bold text-neutral-800 dark:text-white">Umbrella</span>
</div>
</div>
<div class="h-8"></div> <!-- Spacer for scrolling past FAB -->
</div>
</div>
<!-- Sticky Bottom Bar -->
<div class="absolute bottom-0 left-0 w-full bg-background-light/80 dark:bg-mobile-bg/80 backdrop-blur-md border-t border-neutral-200 dark:border-[#3b4354] p-4 z-40">
<button class="w-full flex items-center justify-center gap-2 bg-primary hover:bg-primary/90 text-white font-bold py-3.5 px-6 rounded-lg shadow-lg shadow-primary/25 transition-all active:scale-[0.98]">
<span>Apply Now</span>
<span class="material-symbols-outlined text-sm">arrow_forward</span>
</button>
</div>
<!-- Modal Overlay (Simulated 'Open' State for demonstration) -->
<!-- Using a checkbox hack or target to simulate open state isn't allowed (no JS), so we will render it as if the user clicked.
Since the prompt asks for "When clicked... modal appears", I will design it as an overlay that is visually hidden by default
but I'll make it partially visible or explainable.
Actually, usually for these tasks, showing the "interacted" state is better if it's the focus,
but keeping the landing page clean is requested.
However, to show I fulfilled the "Form modal appears" requirement in a static mockup,
I will place the modal HTML here but commented out or hidden,
OR better yet, I will render it as a "slide up" drawer at the very bottom, partially peeking or fully open?
Let's render it fully open on top to showcase the UI design of the modal as requested.
Wait, the prompt says "Description of screen: Public Mobile Landing Page".
It implies the main view. I will include the modal HTML but hide it with `hidden` class
so the code is there, but visually I will show the landing page.
Actually, to make the design impressive, I'll add a peer-checked hack purely in CSS to show it,
or just display the landing page cleanly.
Let's stick to the clean landing page view as the primary deliverable,
but I will render the Modal as a layer that is *just* about to animate in, or
actually, I will create a separate "view" inside the code that can be toggled if I could use JS.
Decision: I will render the modal but set it to `display: none` in the style attribute for now,
so the code is valid and ready for a developer to toggle.
-->
<!-- Sleek Form Modal (Hidden by default in this static view, developer would toggle 'hidden') -->
<div class="hidden absolute inset-0 z-50 flex flex-col justify-end bg-black/60 backdrop-blur-sm" id="lead-modal">
<div class="bg-background-light dark:bg-[#1c1f27] w-full rounded-t-2xl p-6 shadow-2xl animate-in slide-in-from-bottom duration-300">
<div class="w-12 h-1 bg-neutral-300 dark:bg-neutral-600 rounded-full mx-auto mb-6"></div>
<h2 class="text-xl font-bold text-neutral-900 dark:text-white mb-2">Get Early Access</h2>
<p class="text-sm text-neutral-500 dark:text-gray-400 mb-6">Join the waitlist and get 20% off your first month.</p>
<form class="flex flex-col gap-4">
<div>
<label class="block text-xs font-semibold text-neutral-500 dark:text-gray-400 mb-1.5 uppercase tracking-wider">Work Email</label>
<input class="w-full bg-white dark:bg-[#111318] border border-neutral-200 dark:border-[#3b4354] rounded-lg px-4 py-3 text-neutral-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all placeholder:text-gray-500" placeholder="name@company.com" type="email"/>
</div>
<div>
<label class="block text-xs font-semibold text-neutral-500 dark:text-gray-400 mb-1.5 uppercase tracking-wider">Company Name</label>
<input class="w-full bg-white dark:bg-[#111318] border border-neutral-200 dark:border-[#3b4354] rounded-lg px-4 py-3 text-neutral-900 dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all placeholder:text-gray-500" placeholder="Acme Inc." type="text"/>
</div>
<button class="mt-4 w-full bg-primary hover:bg-primary/90 text-white font-bold py-3.5 px-6 rounded-lg shadow-lg shadow-primary/25 transition-all" type="button">
Get Access
</button>
<p class="text-center text-xs text-neutral-400 dark:text-gray-500 mt-2">No spam, unsubscribe anytime.</p>
</form>
</div>
</div>
</div>
<!-- Desktop Context Label (Outside Device) -->
<div class="absolute bottom-8 text-center">
<p class="text-neutral-400 text-sm font-medium tracking-wide">Variant A: Mobile View</p>
</div>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

View File

@@ -0,0 +1,262 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Traffic Rule Builder</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
},
fontFamily: {
"display": ["Inter", "sans-serif"]
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Custom scrollbar for dark mode feeling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #101622;
}
::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #374151;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark min-h-screen flex flex-col overflow-x-hidden text-gray-900 dark:text-white">
<!-- Top Navigation -->
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-gray-200 dark:border-b-[#282e39] bg-white dark:bg-[#111318] px-10 py-3 sticky top-0 z-50">
<div class="flex items-center gap-4">
<div class="size-8 text-primary flex items-center justify-center">
<span class="material-symbols-outlined text-3xl">traffic</span>
</div>
<h2 class="text-gray-900 dark:text-white text-lg font-bold leading-tight tracking-[-0.015em]">TrafficRules</h2>
</div>
<div class="flex flex-1 justify-end gap-8">
<nav class="hidden md:flex items-center gap-9">
<a class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-white text-sm font-medium leading-normal transition-colors" href="#">Dashboard</a>
<a class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-white text-sm font-medium leading-normal transition-colors" href="#">Campaigns</a>
<a class="text-primary dark:text-white text-sm font-medium leading-normal transition-colors" href="#">Rules</a>
<a class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-white text-sm font-medium leading-normal transition-colors" href="#">Analytics</a>
</nav>
<div class="flex items-center gap-4">
<button class="flex items-center justify-center text-gray-500 hover:text-primary transition-colors">
<span class="material-symbols-outlined">notifications</span>
</button>
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 border-2 border-transparent hover:border-primary cursor-pointer transition-all" data-alt="User profile avatar" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBAHAzWf0wnWPSxuClhObjoEgNzMN52e2NypZAc2tCnO5egnpE_8Qy02QZ6C5KuJLR7x6sH0dq1IMgCuY8wVcbplkFyVXrVqUGpQinj2UbmD_3wogWT_WtKHasVyLhDInif4OCZhDexNoxUKTp9xHzWoLUX1GtHC2O_DQo2jtV62z3-5B-PwyVdn7RAQGLsns3YR7tfbw9Ri2c0yRWdDRdObEEsMSjQ2Gd_yKDLTln9IYCHeuyENTiN_FZovVrkl8pS1leAEJFu8LR2");'></div>
</div>
</div>
</header>
<!-- Main Content Area -->
<main class="flex-1 flex flex-col items-center py-8 px-4 md:px-10">
<div class="w-full max-w-[1024px] flex flex-col gap-8">
<!-- Page Header -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div class="flex flex-col gap-2">
<h1 class="text-3xl md:text-4xl font-black tracking-tight text-gray-900 dark:text-white">Traffic Routing Rules</h1>
<p class="text-gray-500 dark:text-[#9da6b9] text-base font-normal max-w-2xl">
Define and prioritize how incoming traffic is distributed to your landing page variants.
Rules are processed from top to bottom.
</p>
</div>
<button class="flex shrink-0 items-center justify-center gap-2 rounded-lg bg-primary hover:bg-blue-600 text-white px-5 py-2.5 text-sm font-bold shadow-lg shadow-primary/20 transition-all">
<span class="material-symbols-outlined text-[20px]">add</span>
<span>Add New Rule</span>
</button>
</div>
<!-- Rules List Container -->
<div class="flex flex-col gap-4">
<!-- Header for List -->
<div class="grid grid-cols-12 gap-4 px-4 pb-2 text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-[#64748b] pl-16">
<div class="col-span-12 md:col-span-5">Conditions</div>
<div class="col-span-6 md:col-span-4">Target Variant</div>
<div class="col-span-6 md:col-span-3 text-right">Status &amp; Actions</div>
</div>
<!-- Rule Card 1 (Active) -->
<div class="group relative flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-white dark:bg-[#1e232e] border border-gray-200 dark:border-[#282e39] rounded-xl p-4 shadow-sm hover:shadow-md hover:border-primary/50 transition-all duration-200">
<!-- Drag Handle & Priority -->
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-gray-100 dark:border-[#282e39] bg-gray-50 dark:bg-[#161b22] rounded-l-xl cursor-grab active:cursor-grabbing">
<span class="text-xs font-bold text-gray-400 mb-1">#1</span>
<span class="material-symbols-outlined text-gray-400 hover:text-white">drag_indicator</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-2">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-gray-900 dark:text-white">High Value Traffic</span>
</div>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium bg-blue-50 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400 border border-blue-100 dark:border-blue-500/20">
<span class="material-symbols-outlined text-[14px]">public</span> Source: Facebook Ads
</span>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium bg-purple-50 text-purple-700 dark:bg-purple-500/10 dark:text-purple-400 border border-purple-100 dark:border-purple-500/20">
<span class="material-symbols-outlined text-[14px]">smartphone</span> Device: Mobile
</span>
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-3">
<div class="hidden md:block text-gray-400">
<span class="material-symbols-outlined">arrow_right_alt</span>
</div>
<div class="flex items-center gap-3">
<div class="size-8 rounded bg-primary/20 text-primary flex items-center justify-center font-bold text-sm">B</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-900 dark:text-white">Variant B</span>
<span class="text-xs text-gray-500 dark:text-gray-400">Viral Campaign V2</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4">
<label class="relative inline-flex items-center cursor-pointer">
<input checked="" class="sr-only peer" type="checkbox" value=""/>
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary/50 dark:peer-focus:ring-primary/30 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
</label>
<button class="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-100 dark:hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined">more_vert</span>
</button>
</div>
</div>
<!-- Rule Card 2 (Active) -->
<div class="group relative flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-white dark:bg-[#1e232e] border border-gray-200 dark:border-[#282e39] rounded-xl p-4 shadow-sm hover:shadow-md hover:border-primary/50 transition-all duration-200">
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-gray-100 dark:border-[#282e39] bg-gray-50 dark:bg-[#161b22] rounded-l-xl cursor-grab active:cursor-grabbing">
<span class="text-xs font-bold text-gray-400 mb-1">#2</span>
<span class="material-symbols-outlined text-gray-400 hover:text-white">drag_indicator</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-2">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-gray-900 dark:text-white">Business Hours B2B</span>
</div>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium bg-orange-50 text-orange-700 dark:bg-orange-500/10 dark:text-orange-400 border border-orange-100 dark:border-orange-500/20">
<span class="material-symbols-outlined text-[14px]">schedule</span> 09:00 - 18:00
</span>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium bg-emerald-50 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400 border border-emerald-100 dark:border-emerald-500/20">
<span class="material-symbols-outlined text-[14px]">calendar_today</span> Weekdays
</span>
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-3">
<div class="hidden md:block text-gray-400">
<span class="material-symbols-outlined">arrow_right_alt</span>
</div>
<div class="flex items-center gap-3">
<div class="size-8 rounded bg-primary/20 text-primary flex items-center justify-center font-bold text-sm">C</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-900 dark:text-white">Variant C</span>
<span class="text-xs text-gray-500 dark:text-gray-400">Enterprise Focus</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4">
<label class="relative inline-flex items-center cursor-pointer">
<input checked="" class="sr-only peer" type="checkbox" value=""/>
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary/50 dark:peer-focus:ring-primary/30 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
</label>
<button class="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-100 dark:hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined">more_vert</span>
</button>
</div>
</div>
<!-- Rule Card 3 (Inactive) -->
<div class="group relative flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-gray-50 dark:bg-[#161b22] border border-gray-200 dark:border-[#282e39] rounded-xl p-4 opacity-75 hover:opacity-100 transition-all duration-200">
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-gray-100 dark:border-[#282e39] bg-transparent rounded-l-xl cursor-grab active:cursor-grabbing">
<span class="text-xs font-bold text-gray-500 mb-1">#3</span>
<span class="material-symbols-outlined text-gray-600">drag_indicator</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-2">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-gray-700 dark:text-gray-400">Weekend Special</span>
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-300 uppercase">Inactive</span>
</div>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400 border border-gray-200 dark:border-gray-700">
<span class="material-symbols-outlined text-[14px]">calendar_month</span> Weekend
</span>
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-3">
<div class="hidden md:block text-gray-600">
<span class="material-symbols-outlined">arrow_right_alt</span>
</div>
<div class="flex items-center gap-3 opacity-60 grayscale">
<div class="size-8 rounded bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-300 flex items-center justify-center font-bold text-sm">B</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-700 dark:text-gray-300">Variant B</span>
<span class="text-xs text-gray-500 dark:text-gray-500">Viral Campaign V2</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4">
<label class="relative inline-flex items-center cursor-pointer">
<input class="sr-only peer" type="checkbox" value=""/>
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary/50 dark:peer-focus:ring-primary/30 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
</label>
<button class="text-gray-500 hover:text-white p-1 rounded hover:bg-gray-100 dark:hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined">more_vert</span>
</button>
</div>
</div>
<!-- Fallback Rule (Locked) -->
<div class="relative mt-2 flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-gray-50/50 dark:bg-[#111318]/50 border border-dashed border-gray-300 dark:border-[#282e39] rounded-xl p-4">
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-transparent">
<span class="material-symbols-outlined text-gray-400 dark:text-gray-600">lock</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-2">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-gray-500 dark:text-gray-400 italic">Default Fallback</span>
</div>
<div class="text-xs text-gray-400 dark:text-gray-500">
Applied when no other rules match
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-3">
<div class="hidden md:block text-gray-600">
<span class="material-symbols-outlined">arrow_right_alt</span>
</div>
<div class="flex items-center gap-3">
<div class="size-8 rounded bg-gray-200 dark:bg-[#282e39] text-gray-500 dark:text-gray-300 flex items-center justify-center font-bold text-sm">A</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-600 dark:text-gray-300">Variant A</span>
<span class="text-xs text-gray-400 dark:text-gray-500">Original / Control</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4 opacity-50 pointer-events-none">
<div class="text-xs font-medium text-gray-400">Always Active</div>
</div>
</div>
</div>
</div>
</main>
<!-- Floating Save Bar (Hidden by default, shown for concept) -->
<div class="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-40">
<div class="flex items-center gap-4 bg-[#1e232e] border border-[#282e39] rounded-full shadow-2xl px-6 py-3 animate-bounce-in">
<span class="text-sm text-gray-300 font-medium">You have unsaved changes</span>
<div class="h-4 w-px bg-gray-600"></div>
<button class="text-sm text-gray-400 hover:text-white transition-colors">Reset</button>
<button class="text-sm bg-primary hover:bg-blue-600 text-white px-4 py-1.5 rounded-full font-bold transition-colors shadow-lg shadow-primary/25">Save Order</button>
</div>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

View File

@@ -0,0 +1,279 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>트래픽 라우팅 규칙 관리</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#0a0f18",
"sidebar-dark": "#111318",
"card-dark": "#1e232e"
},
fontFamily: {
"display": ["Inter", "sans-serif"]
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', sans-serif;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #0a0f18;
}
::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #374151;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark min-h-screen flex text-gray-900 dark:text-white overflow-hidden">
<aside class="w-64 flex-shrink-0 bg-white dark:bg-sidebar-dark border-r border-gray-200 dark:border-[#282e39] hidden md:flex flex-col h-screen sticky top-0">
<div class="p-6 flex items-center gap-3">
<div class="size-8 text-primary flex items-center justify-center bg-primary/10 rounded-lg">
<span class="material-symbols-outlined text-2xl">traffic</span>
</div>
<h1 class="text-xl font-bold tracking-tight">TrafficRules</h1>
</div>
<nav class="flex-1 px-4 space-y-1">
<a class="flex items-center gap-3 px-3 py-2.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg transition-colors group" href="#">
<span class="material-symbols-outlined text-[22px]">web</span>
<span class="font-medium text-sm">랜딩 페이지</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg transition-colors group" href="#">
<span class="material-symbols-outlined text-[22px]">bar_chart</span>
<span class="font-medium text-sm">통계 및 분석</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 bg-primary/10 text-primary rounded-lg transition-colors group" href="#">
<span class="material-symbols-outlined text-[22px]">rule</span>
<span class="font-medium text-sm">라우팅 규칙</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg transition-colors group" href="#">
<span class="material-symbols-outlined text-[22px]">group</span>
<span class="font-medium text-sm">리드 관리</span>
</a>
</nav>
<div class="p-4 border-t border-gray-200 dark:border-[#282e39]">
<a class="flex items-center gap-3 px-3 py-2.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">settings</span>
<span class="font-medium text-sm">설정</span>
</a>
<div class="mt-4 flex items-center gap-3 px-3 py-2">
<div class="size-8 rounded-full bg-cover bg-center border border-gray-200 dark:border-gray-700" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDY3-B2CEYbpeGjCh25jHS9zMbVYIHA_hev8C4G9rI32sb_q2Tul9d7Ko7Ut18mp17nHr45WAG8r1oIj8UkD5YyIwUOgH2lQJmRaX0WZGCMX001QNge0gUpNUjjGVWFjcXlQwy2f4MVGEHPj7eoHcFFcpAX2AZu-Cpn07LQKXixkLVasw70K8BVeYL-Q87_O6bU4c-SQpNmdmSqocR1S8_wWyPiiCW1OYp1KBBM0QlpFUbubiLMi44LqKCtmcpXbL3mbWSHyhXjW8TB");'></div>
<div class="flex-1 min-w-0">
<p class="text-xs font-bold truncate">운영자 계정</p>
<p class="text-[10px] text-gray-500 truncate">admin@example.com</p>
</div>
</div>
</div>
</aside>
<div class="flex-1 flex flex-col h-screen overflow-y-auto">
<header class="flex items-center justify-between px-8 py-4 bg-white/50 dark:bg-background-dark/50 backdrop-blur-md sticky top-0 z-30">
<div class="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
<span>설정</span>
<span class="material-symbols-outlined text-xs">chevron_right</span>
<span class="text-gray-900 dark:text-white font-medium">트래픽 라우팅 규칙</span>
</div>
<div class="flex items-center gap-4">
<button class="p-2 text-gray-500 hover:text-primary transition-colors">
<span class="material-symbols-outlined">notifications</span>
</button>
</div>
</header>
<main class="flex-1 py-8 px-8">
<div class="max-w-5xl mx-auto flex flex-col gap-8">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div class="flex flex-col gap-2">
<h1 class="text-3xl font-black tracking-tight text-gray-900 dark:text-white">트래픽 라우팅 규칙</h1>
<p class="text-gray-500 dark:text-[#9da6b9] text-base font-normal max-w-2xl">
유입되는 트래픽이 각 랜딩 페이지 버전으로 배분되는 우선순위와 규칙을 정의합니다.
규칙은 상단에서 하단 순서로 처리됩니다.
</p>
</div>
<button class="flex shrink-0 items-center justify-center gap-2 rounded-lg bg-primary hover:bg-blue-600 text-white px-5 py-2.5 text-sm font-bold shadow-lg shadow-primary/20 transition-all">
<span class="material-symbols-outlined text-[20px]">add</span>
<span>새 규칙 추가</span>
</button>
</div>
<div class="flex flex-col gap-4">
<div class="grid grid-cols-12 gap-4 px-4 pb-2 text-[11px] font-bold uppercase tracking-widest text-gray-400 dark:text-[#64748b] pl-16">
<div class="col-span-12 md:col-span-5">조건 (Conditions)</div>
<div class="col-span-6 md:col-span-4">대상 페이지 (Target Variant)</div>
<div class="col-span-6 md:col-span-3 text-right">상태 및 작업</div>
</div>
<div class="group relative flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-white dark:bg-card-dark border border-gray-200 dark:border-[#282e39] rounded-xl p-4 shadow-sm hover:shadow-md hover:border-primary/50 transition-all">
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-gray-100 dark:border-[#282e39] bg-gray-50 dark:bg-[#161b22] rounded-l-xl cursor-grab active:cursor-grabbing">
<span class="text-xs font-bold text-gray-400 mb-1">1</span>
<span class="material-symbols-outlined text-gray-400 group-hover:text-primary">drag_indicator</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-2">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-gray-900 dark:text-white">고가치 트래픽 유입</span>
</div>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-bold bg-blue-50 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400 border border-blue-100 dark:border-blue-500/20">
<span class="material-symbols-outlined text-[14px]">public</span> 유입 경로: Facebook Ads
</span>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-bold bg-purple-50 text-purple-700 dark:bg-purple-500/10 dark:text-purple-400 border border-purple-100 dark:border-purple-500/20">
<span class="material-symbols-outlined text-[14px]">smartphone</span> 기기: 모바일
</span>
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-4">
<span class="material-symbols-outlined text-gray-400">arrow_forward</span>
<div class="flex items-center gap-3">
<div class="size-9 rounded-lg bg-primary/20 text-primary flex items-center justify-center font-black text-sm">B</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-900 dark:text-white">페이지 B</span>
<span class="text-[11px] text-gray-500 dark:text-gray-400">바이럴 캠페인 V2</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4">
<label class="relative inline-flex items-center cursor-pointer">
<input checked="" class="sr-only peer" type="checkbox" value=""/>
<div class="w-10 h-5 bg-gray-200 peer-focus:outline-none rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
</label>
<button class="text-gray-400 hover:text-white p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined">more_vert</span>
</button>
</div>
</div>
<div class="group relative flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-white dark:bg-card-dark border border-gray-200 dark:border-[#282e39] rounded-xl p-4 shadow-sm hover:shadow-md hover:border-primary/50 transition-all">
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-gray-100 dark:border-[#282e39] bg-gray-50 dark:bg-[#161b22] rounded-l-xl cursor-grab active:cursor-grabbing">
<span class="text-xs font-bold text-gray-400 mb-1">2</span>
<span class="material-symbols-outlined text-gray-400 group-hover:text-primary">drag_indicator</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-2">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-gray-900 dark:text-white">영업 시간 B2B 대응</span>
</div>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-bold bg-orange-50 text-orange-700 dark:bg-orange-500/10 dark:text-orange-400 border border-orange-100 dark:border-orange-500/20">
<span class="material-symbols-outlined text-[14px]">schedule</span> 09:00 - 18:00
</span>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-bold bg-emerald-50 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400 border border-emerald-100 dark:border-emerald-500/20">
<span class="material-symbols-outlined text-[14px]">calendar_today</span> 평일
</span>
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-4">
<span class="material-symbols-outlined text-gray-400">arrow_forward</span>
<div class="flex items-center gap-3">
<div class="size-9 rounded-lg bg-primary/20 text-primary flex items-center justify-center font-black text-sm">C</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-900 dark:text-white">페이지 C</span>
<span class="text-[11px] text-gray-500 dark:text-gray-400">엔터프라이즈 전용</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4">
<label class="relative inline-flex items-center cursor-pointer">
<input checked="" class="sr-only peer" type="checkbox" value=""/>
<div class="w-10 h-5 bg-gray-200 peer-focus:outline-none rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
</label>
<button class="text-gray-400 hover:text-white p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined">more_vert</span>
</button>
</div>
</div>
<div class="group relative flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-gray-50/50 dark:bg-[#161b22] border border-gray-200 dark:border-[#282e39] rounded-xl p-4 opacity-70 hover:opacity-100 transition-all">
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-gray-100 dark:border-[#282e39] bg-transparent rounded-l-xl cursor-grab active:cursor-grabbing">
<span class="text-xs font-bold text-gray-500 mb-1">3</span>
<span class="material-symbols-outlined text-gray-500">drag_indicator</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-2">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-gray-700 dark:text-gray-400">주말 특별 프로모션</span>
<span class="px-1.5 py-0.5 rounded text-[9px] font-black bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-400 uppercase">비활성화</span>
</div>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-bold bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-500 border border-gray-200 dark:border-gray-700">
<span class="material-symbols-outlined text-[14px]">calendar_month</span> 주말
</span>
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-4 opacity-50 grayscale">
<span class="material-symbols-outlined text-gray-400">arrow_forward</span>
<div class="flex items-center gap-3">
<div class="size-9 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 flex items-center justify-center font-black text-sm">B</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-700 dark:text-gray-400">페이지 B</span>
<span class="text-[11px] text-gray-500 dark:text-gray-500">바이럴 캠페인 V2</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4">
<label class="relative inline-flex items-center cursor-pointer">
<input class="sr-only peer" type="checkbox" value=""/>
<div class="w-10 h-5 bg-gray-200 peer-focus:outline-none rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
</label>
<button class="text-gray-400 hover:text-white p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-[#282e39] transition-colors">
<span class="material-symbols-outlined">more_vert</span>
</button>
</div>
</div>
<div class="relative mt-2 flex flex-col md:grid md:grid-cols-12 gap-4 items-center bg-gray-100/30 dark:bg-sidebar-dark/50 border border-dashed border-gray-300 dark:border-[#282e39] rounded-xl p-4">
<div class="absolute left-0 top-0 bottom-0 w-12 flex flex-col items-center justify-center border-r border-transparent">
<span class="material-symbols-outlined text-gray-400 dark:text-gray-600">lock</span>
</div>
<div class="col-span-12 md:col-span-5 pl-10 md:pl-0 flex flex-col gap-1">
<div class="flex items-center gap-2">
<span class="text-sm font-bold text-gray-400 dark:text-gray-500 italic">기본 대체 규칙 (Fallback)</span>
</div>
<div class="text-[11px] text-gray-400 dark:text-gray-500">
매칭되는 상위 규칙이 없을 때 적용됩니다.
</div>
</div>
<div class="col-span-6 md:col-span-4 pl-10 md:pl-0 flex items-center gap-4">
<span class="material-symbols-outlined text-gray-400">arrow_forward</span>
<div class="flex items-center gap-3">
<div class="size-9 rounded-lg bg-gray-200 dark:bg-[#282e39] text-gray-500 dark:text-gray-400 flex items-center justify-center font-black text-sm">A</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-gray-600 dark:text-gray-400">페이지 A</span>
<span class="text-[11px] text-gray-400 dark:text-gray-500">오리지널 / 대조군</span>
</div>
</div>
</div>
<div class="col-span-6 md:col-span-3 flex items-center justify-end gap-4 opacity-50">
<div class="text-[11px] font-bold text-gray-400 dark:text-gray-600 uppercase">항상 활성</div>
</div>
</div>
</div>
</div>
</main>
</div>
<div class="fixed bottom-8 left-1/2 md:left-[calc(50%+128px)] transform -translate-x-1/2 z-50">
<div class="flex items-center gap-6 bg-white dark:bg-card-dark border border-gray-200 dark:border-[#282e39] rounded-full shadow-2xl px-6 py-3.5 ring-1 ring-black/5 dark:ring-white/5">
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-primary text-[20px]">info</span>
<span class="text-sm text-gray-700 dark:text-gray-300 font-bold whitespace-nowrap">저장되지 않은 변경사항이 있습니다.</span>
</div>
<div class="h-4 w-px bg-gray-300 dark:bg-gray-600"></div>
<div class="flex items-center gap-3">
<button class="text-sm text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white font-bold transition-colors">초기화</button>
<button class="text-sm bg-primary hover:bg-blue-600 text-white px-5 py-2 rounded-full font-black transition-colors shadow-lg shadow-primary/25">순서 저장</button>
</div>
</div>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

View File

@@ -0,0 +1,403 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Variant Builder Editor</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1e293b",
"border-dark": "#282e39",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
borderRadius: {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"2xl": "1rem",
"3xl": "1.5rem",
"full": "9999px"
},
},
},
}
</script>
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Custom scrollbar for dark mode panels */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #374151;
}
/* Hide scrollbar for mobile preview frame content but allow scroll */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white flex flex-col h-screen overflow-hidden">
<!-- Top Navigation -->
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-border-dark bg-[#111318] px-6 py-3 shrink-0 z-20">
<div class="flex items-center gap-4 text-white">
<a class="text-slate-400 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">arrow_back</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg text-primary">
<span class="material-symbols-outlined">view_quilt</span>
</div>
<div>
<h2 class="text-white text-base font-bold leading-tight tracking-[-0.015em]">Variant Builder</h2>
<div class="flex items-center gap-2 text-xs text-slate-400 mt-0.5">
<span>Campaigns</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span>Summer Sale 2024</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span class="text-white">Variant A</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<!-- View Toggles -->
<div class="flex bg-border-dark rounded-lg p-1 gap-1">
<button class="p-1.5 rounded bg-[#111318] text-white shadow-sm transition-all">
<span class="material-symbols-outlined text-[18px]">smartphone</span>
</button>
<button class="p-1.5 rounded hover:bg-[#111318]/50 text-slate-400 hover:text-white transition-all">
<span class="material-symbols-outlined text-[18px]">laptop</span>
</button>
</div>
<div class="h-6 w-px bg-border-dark"></div>
<div class="flex gap-3">
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-border-dark hover:bg-slate-700 text-white text-sm font-semibold transition-colors">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>Preview</span>
</button>
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-primary hover:bg-primary/90 text-white text-sm font-semibold transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[18px]">save</span>
<span>Save Changes</span>
</button>
</div>
<button class="size-9 rounded-full bg-slate-700 bg-cover bg-center ml-2 border border-slate-600" data-alt="User profile picture" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></button>
</div>
</header>
<!-- Main Workspace -->
<main class="flex flex-1 overflow-hidden h-full">
<!-- Left Panel: Structure -->
<aside class="w-[320px] bg-[#111318] border-r border-border-dark flex flex-col shrink-0 z-10">
<div class="p-4 border-b border-border-dark flex justify-between items-center bg-[#111318]">
<h3 class="font-semibold text-sm text-white">Page Structure</h3>
<button class="text-primary hover:text-primary/80 text-xs font-medium flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">add</span>
Add Section
</button>
</div>
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-3 custom-scrollbar">
<!-- Section Item: Header (Active) -->
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-primary shadow-[0_0_0_1px_rgba(19,91,236,1)] cursor-pointer hover:bg-slate-800 transition-colors">
<div class="text-slate-500 cursor-grab active:cursor-grabbing">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-[#111318] flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">image</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">Header Banner</p>
<p class="text-xs text-slate-500 truncate">Image + Logo</p>
</div>
<div class="opacity-100 group-hover:opacity-100 transition-opacity">
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
<!-- Section Item: Text Block -->
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-surface-dark flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">title</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">Value Proposition</p>
<p class="text-xs text-slate-500 truncate">H1 + Subtext</p>
</div>
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
<!-- Section Item: GIF -->
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-surface-dark flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">gif_box</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">Product Demo</p>
<p class="text-xs text-slate-500 truncate">Looping GIF</p>
</div>
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
<!-- Section Item: Features -->
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-surface-dark flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">list</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">Key Benefits</p>
<p class="text-xs text-slate-500 truncate">Bullet points</p>
</div>
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
<!-- Section Item: CTA -->
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-surface-dark flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">touch_app</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">Sticky CTA</p>
<p class="text-xs text-slate-500 truncate">Bottom fixed</p>
</div>
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
</div>
<div class="p-4 border-t border-border-dark mt-auto bg-[#111318]">
<button class="w-full py-2.5 rounded-lg border border-dashed border-slate-600 text-slate-400 hover:text-white hover:border-slate-400 hover:bg-surface-dark transition-all flex items-center justify-center gap-2 text-sm font-medium">
<span class="material-symbols-outlined text-[20px]">add_circle</span>
Add New Section
</button>
</div>
</aside>
<!-- Center Panel: Canvas -->
<section class="flex-1 bg-[#0b0d11] relative flex flex-col items-center justify-center overflow-hidden p-8">
<div class="absolute inset-0 z-0 opacity-20 pointer-events-none" data-alt="Dot pattern background" style="background-image: radial-gradient(#282e39 1px, transparent 1px); background-size: 20px 20px;"></div>
<div class="relative z-10 flex flex-col items-center h-full max-h-[800px] w-full">
<!-- Mobile Frame -->
<div class="relative w-[375px] h-full bg-black rounded-[3rem] border-[8px] border-slate-800 shadow-2xl overflow-hidden flex flex-col ring-1 ring-white/10">
<!-- Status Bar Sim -->
<div class="h-8 bg-black w-full flex justify-between items-center px-6 shrink-0 select-none">
<span class="text-[10px] font-semibold text-white">9:41</span>
<div class="flex gap-1.5">
<span class="material-symbols-outlined text-[12px] text-white">signal_cellular_alt</span>
<span class="material-symbols-outlined text-[12px] text-white">wifi</span>
<span class="material-symbols-outlined text-[12px] text-white">battery_full</span>
</div>
</div>
<!-- Preview Content -->
<div class="flex-1 bg-white overflow-y-auto no-scrollbar relative w-full">
<!-- Header Banner (Selected) -->
<div class="relative group">
<div class="absolute inset-0 border-2 border-primary z-20 pointer-events-none"></div>
<div class="absolute top-2 right-2 z-30 bg-primary text-white text-[10px] px-2 py-0.5 rounded font-bold uppercase tracking-wide shadow-sm">Editing</div>
<div class="relative h-48 w-full bg-slate-900 overflow-hidden">
<img alt="Abstract modern startup office background" class="w-full h-full object-cover opacity-80" data-alt="Abstract modern startup office background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBceHj4fe7PQn5roym2DM9WOocCJB0BQxuKExR8ulwggZyARF0-GRXVdS9rV9qEiAfcjcjQ_jH3NIxoAy7IH8ISJ2z6ND6WbUrEwJySKSEN8L3LL8Lr42mnkATadICesv0h2nm5JMy22wnEXjyIs-F2NiXG56Hh6s3HELVhRK4nU-VsEoy8m-tWbh8FR1jndX6iCq6mxaPrd05yp6V5p2ZqEpTjciLxSUFDO4YG2KKXMvQPf2a_6nWrtv7b8FHU7FeM2sIpf3V7YmF"/>
<div class="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent flex flex-col justify-end p-6">
<div class="flex items-center gap-2 mb-2">
<div class="size-8 bg-blue-600 rounded-lg flex items-center justify-center text-white font-bold text-lg">V</div>
<span class="text-white font-bold text-lg tracking-tight">Vantage</span>
</div>
<h1 class="text-white text-2xl font-bold leading-tight">Scale your SaaS like never before.</h1>
</div>
</div>
</div>
<!-- Value Prop -->
<div class="p-6 bg-white">
<h2 class="text-slate-900 text-xl font-bold mb-3">All-in-one platform for growth</h2>
<p class="text-slate-600 text-base leading-relaxed">Stop juggling multiple tools. Get analytics, CRM, and marketing automation in a single dashboard.</p>
</div>
<!-- GIF Placeholder -->
<div class="w-full aspect-video bg-slate-100 flex items-center justify-center text-slate-400 border-y border-slate-100">
<div class="flex flex-col items-center gap-2">
<span class="material-symbols-outlined text-[32px]">play_circle</span>
<span class="text-xs font-medium uppercase tracking-wider">Product Demo GIF</span>
</div>
</div>
<!-- Features -->
<div class="p-6 bg-slate-50">
<div class="flex gap-4 mb-4">
<div class="size-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 shrink-0">
<span class="material-symbols-outlined text-[20px]">rocket_launch</span>
</div>
<div>
<h3 class="font-bold text-slate-900 mb-1">Fast Integration</h3>
<p class="text-sm text-slate-600">Connect with your stack in minutes, not days.</p>
</div>
</div>
<div class="flex gap-4">
<div class="size-10 rounded-full bg-green-100 flex items-center justify-center text-green-600 shrink-0">
<span class="material-symbols-outlined text-[20px]">security</span>
</div>
<div>
<h3 class="font-bold text-slate-900 mb-1">Enterprise Security</h3>
<p class="text-sm text-slate-600">SOC2 Type II certified data protection.</p>
</div>
</div>
</div>
<!-- Spacer for scrolling -->
<div class="h-20"></div>
</div>
<!-- Sticky CTA -->
<div class="absolute bottom-0 left-0 right-0 p-4 bg-white/95 backdrop-blur-sm border-t border-slate-100 shadow-lg z-20">
<button class="w-full bg-blue-600 text-white font-bold py-3 rounded-xl shadow-lg shadow-blue-600/30">Get Started Free</button>
</div>
<!-- Home Indicator -->
<div class="absolute bottom-1 left-1/2 -translate-x-1/2 w-32 h-1 bg-white/20 rounded-full z-30"></div>
</div>
<div class="mt-4 text-slate-500 text-xs flex gap-2 items-center bg-[#111318] py-1 px-3 rounded-full border border-border-dark">
<span class="size-2 bg-green-500 rounded-full animate-pulse"></span>
Live Preview • iPhone 14 Pro
</div>
</div>
</section>
<!-- Right Panel: Properties -->
<aside class="w-[360px] bg-[#111318] border-l border-border-dark flex flex-col shrink-0 z-10">
<!-- Properties Header -->
<div class="px-5 py-4 border-b border-border-dark flex justify-between items-center bg-[#111318]">
<div>
<h3 class="font-semibold text-sm text-white">Edit: Header Banner</h3>
<p class="text-xs text-slate-500 mt-0.5">Configure layout and content</p>
</div>
<div class="flex gap-1">
<button class="size-8 flex items-center justify-center text-slate-400 hover:text-white rounded hover:bg-surface-dark transition-colors" title="Reset to default">
<span class="material-symbols-outlined text-[18px]">restart_alt</span>
</button>
<button class="size-8 flex items-center justify-center text-slate-400 hover:text-red-400 rounded hover:bg-surface-dark transition-colors" title="Delete section">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</div>
<!-- Tabs -->
<div class="flex border-b border-border-dark">
<button class="flex-1 py-3 text-sm font-medium text-primary border-b-2 border-primary bg-surface-dark/50">Content</button>
<button class="flex-1 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-surface-dark transition-colors">Style</button>
<button class="flex-1 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-surface-dark transition-colors">Settings</button>
</div>
<!-- Scrollable Form Area -->
<div class="flex-1 overflow-y-auto p-5 custom-scrollbar">
<div class="flex flex-col gap-6">
<!-- Image Upload -->
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Background Image</label>
<div class="border-2 border-dashed border-border-dark rounded-lg p-4 hover:border-primary/50 hover:bg-surface-dark transition-all cursor-pointer group text-center">
<div class="size-16 bg-[#111318] rounded-md mx-auto mb-3 overflow-hidden border border-border-dark relative">
<img alt="Current background" class="w-full h-full object-cover opacity-60 group-hover:opacity-40 transition-opacity" data-alt="Current background preview" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARKX7eC65mz2IpuBwDVp_I5oS2RC62dOWT-WoIBq5cR6aap-2XMq4Y9ntPmPhycnDw1NszD32Wgwrec6t7qQmVHp1llkEWal5-zfta2pCL4SWfO5v0nYou7715kxFts5jf4O3TDJPkL1hFpSEWNypgSvYP2I-avJIA-hfssJxwf1RyrWDUwewJG14-ivVP91ECuBDyyMLkZA62gUX_fjTwO-W6cCrJmNITt2oWnzn9gAjAX7wxvWfjLfIirMNVNfwolEaHgXl-B_pI"/>
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<span class="material-symbols-outlined text-white">edit</span>
</div>
</div>
<p class="text-sm text-primary font-medium">Click to replace</p>
<p class="text-xs text-slate-500 mt-1">Supports JPG, PNG, WEBP (Max 2MB)</p>
</div>
</div>
<!-- Input Group -->
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Heading Text</label>
<textarea class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-shadow resize-none" rows="3">Scale your SaaS like never before.</textarea>
</div>
<!-- Input Group -->
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Company Name</label>
<div class="relative">
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white pl-3 pr-10 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow" type="text" value="Vantage"/>
<div class="absolute right-3 top-2 text-slate-500">
<span class="material-symbols-outlined text-[18px]">edit</span>
</div>
</div>
</div>
<!-- Toggle Switch -->
<div class="flex items-center justify-between p-3 rounded-lg bg-[#0b0d11] border border-border-dark">
<div class="flex flex-col">
<span class="text-sm font-medium text-white">Show Logo Icon</span>
<span class="text-xs text-slate-500">Display icon next to name</span>
</div>
<button class="w-11 h-6 bg-primary rounded-full relative transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-[#111318] focus:ring-primary">
<span class="absolute left-6 top-1 bg-white size-4 rounded-full transition-transform"></span>
</button>
</div>
<!-- Slider -->
<div class="flex flex-col gap-3">
<div class="flex justify-between items-center">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Overlay Opacity</label>
<span class="text-xs font-mono text-primary bg-primary/10 px-1.5 py-0.5 rounded">80%</span>
</div>
<input class="w-full h-1.5 bg-border-dark rounded-lg appearance-none cursor-pointer accent-primary" max="100" min="0" type="range" value="80"/>
</div>
<!-- Alignment -->
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Text Alignment</label>
<div class="flex bg-[#0b0d11] p-1 rounded-md border border-border-dark">
<button class="flex-1 py-1.5 rounded text-slate-400 hover:bg-surface-dark hover:text-white transition-colors flex justify-center">
<span class="material-symbols-outlined text-[20px]">format_align_left</span>
</button>
<button class="flex-1 py-1.5 rounded bg-primary text-white shadow-sm flex justify-center">
<span class="material-symbols-outlined text-[20px]">format_align_center</span>
</button>
<button class="flex-1 py-1.5 rounded text-slate-400 hover:bg-surface-dark hover:text-white transition-colors flex justify-center">
<span class="material-symbols-outlined text-[20px]">format_align_right</span>
</button>
</div>
</div>
</div>
</div>
<!-- Properties Footer -->
<div class="p-4 border-t border-border-dark bg-[#111318]">
<button class="w-full py-2.5 rounded-lg bg-primary text-white text-sm font-semibold hover:bg-primary/90 transition-colors shadow-lg shadow-primary/20">
Apply Changes
</button>
</div>
</aside>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View File

@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>새 랜딩 페이지 생성 - 대시보드</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#0b0d11",
"surface-dark": "#111318",
"border-dark": "#282e39",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
.sidebar-item-active {
@apply bg-primary/10 text-primary border-r-2 border-primary;
}
</style>
</head>
<body class="bg-background-dark text-white flex h-screen overflow-hidden">
<aside class="w-64 flex flex-col border-r border-border-dark bg-surface-dark shrink-0">
<div class="p-6 border-b border-border-dark flex items-center gap-3">
<div class="size-8 bg-primary rounded-lg flex items-center justify-center">
<span class="material-symbols-outlined text-white text-xl">rocket_launch</span>
</div>
<span class="font-bold text-lg tracking-tight">SaaS Builder</span>
</div>
<nav class="flex-1 py-6 flex flex-col gap-1">
<a class="flex items-center gap-3 px-6 py-3 text-sm font-medium transition-colors sidebar-item-active" href="#">
<span class="material-symbols-outlined">public</span>
랜딩 페이지
</a>
<a class="flex items-center gap-3 px-6 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined">bar_chart</span>
통계 및 분석
</a>
<a class="flex items-center gap-3 px-6 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined">group</span>
리드 관리
</a>
<div class="mt-auto">
<a class="flex items-center gap-3 px-6 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined">settings</span>
설정
</a>
</div>
</nav>
<div class="p-4 border-t border-border-dark">
<div class="flex items-center gap-3 p-2 rounded-lg hover:bg-white/5 cursor-pointer transition-colors">
<div class="size-8 rounded-full bg-slate-700 flex items-center justify-center text-xs font-bold text-white">JD</div>
<div class="flex flex-col">
<span class="text-xs font-semibold">John Doe</span>
<span class="text-[10px] text-slate-500">Pro Plan</span>
</div>
</div>
</div>
</aside>
<div class="flex-1 flex flex-col overflow-hidden">
<header class="flex items-center justify-between border-b border-border-dark bg-surface-dark px-8 py-4 shrink-0 z-10">
<div class="flex items-center gap-4">
<a class="text-slate-400 hover:text-white transition-colors flex items-center" href="#">
<span class="material-symbols-outlined">arrow_back</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<h2 class="text-lg font-bold tracking-tight">새 랜딩 페이지 생성</h2>
</div>
<div class="flex items-center gap-3">
<button class="px-5 py-2 rounded-lg text-sm font-semibold text-slate-400 hover:text-white hover:bg-white/5 transition-all">
취소
</button>
<button class="px-6 py-2 rounded-lg bg-primary hover:bg-primary/90 text-white text-sm font-semibold transition-all shadow-lg shadow-primary/20">
생성하기
</button>
</div>
</header>
<main class="flex-1 overflow-y-auto custom-scrollbar bg-background-dark py-10 px-8">
<div class="max-w-4xl mx-auto space-y-8">
<section class="flex flex-col gap-6 bg-surface-dark p-8 rounded-2xl border border-border-dark">
<div class="flex flex-col gap-1 border-b border-border-dark pb-4">
<h3 class="text-base font-semibold">기본 정보</h3>
<p class="text-xs text-slate-500">랜딩 페이지의 식별 정보와 접속 경로를 설정합니다.</p>
</div>
<div class="grid grid-cols-1 gap-6">
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">페이지 이름</label>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-lg text-sm text-white px-4 py-3 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-all" placeholder="예: 2024 여름 할인 캠페인" type="text"/>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">URL 경로</label>
<div class="flex items-center">
<span class="bg-[#0b0d11] border border-r-0 border-border-dark px-4 py-3 text-sm text-slate-500 rounded-l-lg">
example.com/pages/
</span>
<input class="flex-1 bg-[#0b0d11] border border-border-dark rounded-r-lg text-sm text-white px-4 py-3 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-all" placeholder="summer-sale" type="text"/>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">설명</label>
<textarea class="w-full bg-[#0b0d11] border border-border-dark rounded-lg text-sm text-white px-4 py-3 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-all resize-none" placeholder="랜딩 페이지에 대한 상세 설명을 입력하세요." rows="4"></textarea>
</div>
</div>
</section>
<section class="flex flex-col gap-6 bg-surface-dark p-8 rounded-2xl border border-border-dark">
<div class="flex flex-col gap-1 border-b border-border-dark pb-4">
<h3 class="text-base font-semibold">고급 설정</h3>
<p class="text-xs text-slate-500">페이지 동작에 필요한 추가 기능을 활성화합니다.</p>
</div>
<div class="space-y-4">
<div class="flex items-center justify-between p-4 rounded-xl bg-[#0b0d11] border border-border-dark hover:border-slate-700 transition-colors">
<div class="flex gap-4 items-center">
<div class="size-10 bg-primary/10 text-primary rounded-lg flex items-center justify-center">
<span class="material-symbols-outlined">task_alt</span>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-white">완료 페이지 사용 여부</span>
<span class="text-xs text-slate-500">제출 성공 후 감사 페이지 또는 리다이렉션을 설정합니다.</span>
</div>
</div>
<button class="w-11 h-6 bg-slate-700 rounded-full relative transition-colors focus:outline-none ring-offset-2 ring-offset-surface-dark group">
<span class="absolute left-1 top-1 bg-white size-4 rounded-full transition-transform"></span>
</button>
</div>
<div class="flex items-center justify-between p-4 rounded-xl bg-[#0b0d11] border border-border-dark hover:border-slate-700 transition-colors">
<div class="flex gap-4 items-center">
<div class="size-10 bg-amber-500/10 text-amber-500 rounded-lg flex items-center justify-center">
<span class="material-symbols-outlined">code</span>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-white">스크립트 사용 여부</span>
<span class="text-xs text-slate-500">GA4, Pixel 등 외부 트래킹 및 커스텀 스크립트를 삽입합니다.</span>
</div>
</div>
<button class="w-11 h-6 bg-primary rounded-full relative transition-colors focus:outline-none ring-offset-2 ring-offset-surface-dark">
<span class="absolute left-6 top-1 bg-white size-4 rounded-full transition-transform"></span>
</button>
</div>
</div>
</section>
<section class="flex flex-col gap-6 bg-surface-dark p-8 rounded-2xl border border-border-dark" id="script-settings">
<div class="flex flex-col gap-1 border-b border-border-dark pb-4">
<h3 class="text-base font-semibold">스크립트 설정</h3>
<p class="text-xs text-slate-500">활성화된 스크립트 도구들을 구성합니다.</p>
</div>
<div class="grid grid-cols-1 gap-4">
<div class="p-4 rounded-xl bg-[#0b0d11] border border-border-dark">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider mb-2 block">Google Analytics (GA4) ID</label>
<input class="w-full bg-surface-dark border border-border-dark rounded-lg text-sm text-white px-4 py-3 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-all" placeholder="G-XXXXXXXXXX" type="text"/>
</div>
</div>
</section>
<section class="flex flex-col gap-6 bg-surface-dark p-8 rounded-2xl border border-border-dark opacity-50 grayscale select-none">
<div class="flex flex-col gap-1 border-b border-border-dark pb-4">
<div class="flex items-center gap-2">
<h3 class="text-base font-semibold">템플릿 선택</h3>
<span class="text-[10px] bg-border-dark px-1.5 py-0.5 rounded text-slate-400 font-bold uppercase">Coming Soon</span>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="aspect-[3/4] rounded-lg bg-[#0b0d11] border border-border-dark flex items-center justify-center">
<span class="material-symbols-outlined text-slate-700 text-4xl">web</span>
</div>
<div class="aspect-[3/4] rounded-lg bg-[#0b0d11] border border-border-dark flex items-center justify-center">
<span class="material-symbols-outlined text-slate-700 text-4xl">web</span>
</div>
<div class="aspect-[3/4] rounded-lg bg-[#0b0d11] border border-border-dark flex items-center justify-center">
<span class="material-symbols-outlined text-slate-700 text-4xl">web</span>
</div>
</div>
</section>
</div>
</main>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>스크립트 및 추적 설정 - Variant Builder</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#0b0d11",
"surface-dark": "#1e293b",
"border-dark": "#282e39",
"code-bg": "#0b0d11",
"sidebar-bg": "#111318"
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
"mono": ["Fira Code", "monospace"]
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
.code-editor {
font-family: 'Fira Code', monospace;
tab-size: 4;
line-height: 1.6;
}
</style>
</head>
<body class="bg-background-dark text-slate-900 dark:text-white flex h-screen overflow-hidden">
<aside class="w-[240px] bg-sidebar-bg border-r border-border-dark flex flex-col shrink-0 z-30">
<div class="p-6">
<div class="flex items-center gap-3 mb-8 px-2">
<div class="size-8 bg-primary rounded-lg flex items-center justify-center">
<span class="material-symbols-outlined text-white text-xl">layers</span>
</div>
<span class="font-bold text-lg tracking-tight text-white">Variant</span>
</div>
<nav class="space-y-1">
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-surface-dark/50 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">dashboard</span>
<span class="text-sm font-medium">랜딩 페이지</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-surface-dark/50 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">analytics</span>
<span class="text-sm font-medium">통계 및 분석</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-surface-dark/50 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">person_search</span>
<span class="text-sm font-medium">리드 관리</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-white bg-primary/10 border border-primary/20 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">settings</span>
<span class="text-sm font-semibold">설정</span>
</a>
</nav>
</div>
<div class="mt-auto p-4 border-t border-border-dark">
<div class="flex items-center gap-3 px-2">
<div class="size-8 rounded-full bg-slate-700 bg-cover bg-center border border-slate-600" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></div>
<div class="flex flex-col">
<span class="text-xs font-semibold text-white">마케팅 팀</span>
<span class="text-[10px] text-slate-500 tracking-tight">Pro Plan</span>
</div>
</div>
</div>
</aside>
<div class="flex flex-col flex-1 min-w-0 h-screen">
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-border-dark bg-sidebar-bg px-6 py-3 shrink-0 z-20">
<div class="flex items-center gap-4 text-white">
<a class="text-slate-400 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">arrow_back</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg text-primary">
<span class="material-symbols-outlined">code</span>
</div>
<div>
<h2 class="text-white text-base font-bold leading-tight tracking-[-0.015em]">스크립트 및 추적 설정</h2>
<div class="flex items-center gap-2 text-xs text-slate-400 mt-0.5">
<span>랜딩페이지 관리</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span>여름 프로모션 캠페인</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span class="text-white">스크립트 설정</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex gap-3">
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-border-dark hover:bg-slate-700 text-white text-sm font-semibold transition-colors">
<span>취소</span>
</button>
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-primary hover:bg-primary/90 text-white text-sm font-semibold transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[18px]">save</span>
<span>설정 저장</span>
</button>
</div>
</div>
</header>
<main class="flex flex-1 overflow-hidden h-full">
<aside class="w-[280px] bg-sidebar-bg border-r border-border-dark flex flex-col shrink-0 z-10">
<div class="p-6">
<h3 class="font-semibold text-sm text-white mb-4">도움말 및 가이드</h3>
<div class="space-y-4">
<div class="p-4 rounded-lg bg-surface-dark/30 border border-border-dark">
<div class="flex items-center gap-2 text-primary mb-2">
<span class="material-symbols-outlined text-sm">info</span>
<span class="text-xs font-bold uppercase tracking-wider">스크립트 삽입 위치</span>
</div>
<p class="text-[11px] text-slate-400 leading-relaxed">
모든 스크립트는 해당 페이지의 <code class="text-blue-400 font-mono">&lt;/head&gt;</code> 태그 바로 위에 자동으로 삽입됩니다.
</p>
</div>
<div class="p-4 rounded-lg bg-surface-dark/30 border border-border-dark">
<div class="flex items-center gap-2 text-amber-500 mb-2">
<span class="material-symbols-outlined text-sm">warning</span>
<span class="text-xs font-bold uppercase tracking-wider">주의사항</span>
</div>
<p class="text-[11px] text-slate-400 leading-relaxed">
잘못된 스크립트는 페이지 렌더링에 영향을 줄 수 있습니다. 외부 라이브러리 연동 시 반드시 테스트 후 배포하세요.
</p>
</div>
</div>
</div>
<div class="mt-auto p-6 border-t border-border-dark">
<div class="flex items-center gap-3 text-slate-400 hover:text-white cursor-pointer transition-colors group">
<span class="material-symbols-outlined group-hover:text-primary">help_outline</span>
<span class="text-sm">고급 가이드 문서 보기</span>
</div>
</div>
</aside>
<section class="flex-1 bg-[#0b0d11] overflow-y-auto custom-scrollbar p-8">
<div class="max-w-4xl mx-auto space-y-8">
<div class="flex flex-col gap-4">
<div class="flex justify-between items-end">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-lg font-bold text-white">글로벌 스크립트</h3>
<span class="px-2 py-0.5 rounded bg-blue-500/10 text-blue-500 text-[10px] font-bold tracking-tight border border-blue-500/20">ALL PAGES</span>
</div>
<p class="text-sm text-slate-500">모든 섹션과 페이지에 공통으로 삽입되는 스크립트입니다.</p>
</div>
<div class="flex gap-2">
<button class="px-3 py-1.5 rounded bg-surface-dark text-xs text-slate-300 hover:text-white transition-colors">템플릿 불러오기</button>
</div>
</div>
<div class="relative group">
<div class="absolute top-4 left-4 z-10 flex gap-2">
<div class="size-2.5 rounded-full bg-red-500/50"></div>
<div class="size-2.5 rounded-full bg-amber-500/50"></div>
<div class="size-2.5 rounded-full bg-green-500/50"></div>
</div>
<div class="absolute top-4 right-4 z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded bg-slate-800 text-slate-400 hover:text-white" title="복사하기">
<span class="material-symbols-outlined text-[18px]">content_copy</span>
</button>
</div>
<textarea class="w-full h-64 bg-[#0d1117] border border-border-dark rounded-xl p-12 code-editor text-sm text-blue-300 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all" placeholder="&lt;!-- 여기에 스크립트를 입력하세요 (e.g. &lt;script&gt;...&lt;/script&gt;) --&gt;" spellcheck="false">&lt;script async="" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"&gt;&lt;/script&gt;
&lt;script&gt;
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
&lt;/script&gt;</textarea>
</div>
</div>
<div class="flex flex-col gap-4">
<div class="flex justify-between items-end">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-lg font-bold text-white">전환 스크립트</h3>
<span class="px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-500 text-[10px] font-bold tracking-tight border border-emerald-500/20">COMPLETION ONLY</span>
</div>
<p class="text-sm text-slate-500">신청 완료 혹은 구매 완료 페이지에만 삽입되는 스크립트입니다.</p>
</div>
<div class="flex gap-2">
<button class="px-3 py-1.5 rounded bg-surface-dark text-xs text-slate-300 hover:text-white transition-colors">포스트백 가이드</button>
</div>
</div>
<div class="relative group">
<div class="absolute top-4 left-4 z-10 flex gap-2">
<div class="size-2.5 rounded-full bg-red-500/50"></div>
<div class="size-2.5 rounded-full bg-amber-500/50"></div>
<div class="size-2.5 rounded-full bg-green-500/50"></div>
</div>
<div class="absolute top-4 right-4 z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded bg-slate-800 text-slate-400 hover:text-white" title="복사하기">
<span class="material-symbols-outlined text-[18px]">content_copy</span>
</button>
</div>
<textarea class="w-full h-48 bg-[#0d1117] border border-border-dark rounded-xl p-12 code-editor text-sm text-emerald-300 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all" placeholder="&lt;!-- 전환 추적 스크립트를 입력하세요 --&gt;" spellcheck="false">&lt;script&gt;
// Conversion event for purchase
fbq('track', 'Purchase', {
value: 29.00,
currency: 'USD'
});
&lt;/script&gt;</textarea>
</div>
</div>
<div class="grid grid-cols-2 gap-4 pb-12">
<div class="p-4 rounded-xl bg-surface-dark border border-border-dark flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-[#111318] flex items-center justify-center text-slate-400">
<span class="material-symbols-outlined">analytics</span>
</div>
<div>
<p class="text-sm font-semibold text-white">기본 로그 수집</p>
<p class="text-xs text-slate-500">방문자 수 및 유입 경로 자동 분석</p>
</div>
</div>
<button class="w-10 h-5 bg-primary rounded-full relative transition-colors">
<span class="absolute right-1 top-1 bg-white size-3 rounded-full"></span>
</button>
</div>
<div class="p-4 rounded-xl bg-surface-dark border border-border-dark flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-[#111318] flex items-center justify-center text-slate-400">
<span class="material-symbols-outlined">ads_click</span>
</div>
<div>
<p class="text-sm font-semibold text-white">UTM 파라미터 보존</p>
<p class="text-xs text-slate-500">페이지 이동 시 광고 파라미터 유지</p>
</div>
</div>
<button class="w-10 h-5 bg-primary rounded-full relative transition-colors">
<span class="absolute right-1 top-1 bg-white size-3 rounded-full"></span>
</button>
</div>
</div>
</div>
</section>
<aside class="w-[320px] bg-sidebar-bg border-l border-border-dark flex flex-col shrink-0 z-10">
<div class="px-5 py-4 border-b border-border-dark flex justify-between items-center bg-sidebar-bg">
<div>
<h3 class="font-semibold text-sm text-white">스크립트 변경 이력</h3>
<p class="text-xs text-slate-500 mt-0.5">최근 5개의 변경 사항</p>
</div>
<button class="size-8 flex items-center justify-center text-slate-400 hover:text-white rounded hover:bg-surface-dark transition-colors">
<span class="material-symbols-outlined text-[18px]">history</span>
</button>
</div>
<div class="flex-1 overflow-y-auto p-5 custom-scrollbar">
<div class="flex flex-col gap-4">
<div class="flex gap-3">
<div class="shrink-0 mt-1">
<div class="size-2 rounded-full bg-primary"></div>
<div class="w-0.5 h-full bg-border-dark mx-auto mt-1"></div>
</div>
<div class="flex-1 pb-4">
<p class="text-xs font-semibold text-white">Google Analytics 4 추가</p>
<p class="text-[10px] text-slate-500 mt-0.5">2024.05.24 14:20 • admin_user</p>
</div>
</div>
<div class="flex gap-3">
<div class="shrink-0 mt-1">
<div class="size-2 rounded-full bg-slate-600"></div>
<div class="w-0.5 h-full bg-border-dark mx-auto mt-1"></div>
</div>
<div class="flex-1 pb-4">
<p class="text-xs font-semibold text-slate-400">Facebook Pixel 스크립트 수정</p>
<p class="text-[10px] text-slate-500 mt-0.5">2024.05.20 10:15 • dev_team</p>
</div>
</div>
<div class="flex gap-3">
<div class="shrink-0 mt-1">
<div class="size-2 rounded-full bg-slate-600"></div>
</div>
<div class="flex-1">
<p class="text-xs font-semibold text-slate-400">최초 스크립트 설정</p>
<p class="text-[10px] text-slate-500 mt-0.5">2024.05.15 09:00 • system</p>
</div>
</div>
</div>
</div>
<div class="p-6 border-t border-border-dark bg-sidebar-bg">
<div class="p-4 rounded-lg bg-primary/5 border border-primary/20">
<div class="flex items-center gap-2 text-primary mb-2">
<span class="material-symbols-outlined text-sm">auto_awesome</span>
<span class="text-xs font-bold uppercase tracking-wider">AI 도우미</span>
</div>
<p class="text-[11px] text-slate-400 leading-relaxed mb-3">
설치하려는 도구의 이름만 입력하면 AI가 최적화된 코드를 생성해 드립니다.
</p>
<button class="w-full py-2 bg-primary/10 hover:bg-primary/20 text-primary text-xs font-bold rounded transition-colors">
AI로 스크립트 생성하기
</button>
</div>
</div>
</aside>
</main>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

View File

@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Variant Builder Editor</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1e293b",
"border-dark": "#282e39",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
borderRadius: {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"2xl": "1rem",
"3xl": "1.5rem",
"full": "9999px"
},
},
},
}
</script>
<style>
body {
font-family: 'Inter', sans-serif;
}.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #374151;
}.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}.code-line {
counter-increment: line;
}
.code-line::before {
content: counter(line);
display: inline-block;
width: 1.5rem;
margin-right: 1rem;
color: #475569;
text-align: right;
font-size: 0.75rem;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white flex flex-col h-screen overflow-hidden">
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-border-dark bg-[#111318] px-6 py-3 shrink-0 z-20">
<div class="flex items-center gap-4 text-white">
<a class="text-slate-400 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">arrow_back</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg text-primary">
<span class="material-symbols-outlined">view_quilt</span>
</div>
<div>
<h2 class="text-white text-base font-bold leading-tight tracking-[-0.015em]">Variant Builder</h2>
<div class="flex items-center gap-2 text-xs text-slate-400 mt-0.5">
<span>Campaigns</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span>Summer Sale 2024</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span class="text-white">Variant A</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex bg-border-dark rounded-lg p-1 gap-1">
<button class="p-1.5 rounded bg-[#111318] text-white shadow-sm transition-all">
<span class="material-symbols-outlined text-[18px]">smartphone</span>
</button>
<button class="p-1.5 rounded hover:bg-[#111318]/50 text-slate-400 hover:text-white transition-all">
<span class="material-symbols-outlined text-[18px]">laptop</span>
</button>
</div>
<div class="h-6 w-px bg-border-dark"></div>
<div class="flex gap-3">
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-border-dark hover:bg-slate-700 text-white text-sm font-semibold transition-colors">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>Preview</span>
</button>
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-primary hover:bg-primary/90 text-white text-sm font-semibold transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[18px]">save</span>
<span>Save Changes</span>
</button>
</div>
<button class="size-9 rounded-full bg-slate-700 bg-cover bg-center ml-2 border border-slate-600" data-alt="User profile picture" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></button>
</div>
</header>
<main class="flex flex-1 overflow-hidden h-full">
<aside class="w-[380px] bg-[#111318] border-r border-border-dark flex flex-col shrink-0 z-10">
<div class="p-4 border-b border-border-dark bg-[#111318]">
<div class="flex p-1 bg-surface-dark rounded-lg border border-border-dark">
<button class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md bg-primary text-white text-xs font-semibold shadow-sm transition-all">
<span class="material-symbols-outlined text-[16px]">view_agenda</span>
Visual
</button>
<button class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-slate-400 hover:text-white hover:bg-white/5 text-xs font-medium transition-all">
<span class="material-symbols-outlined text-[16px]">code</span>
HTML Code
</button>
</div>
</div>
<div class="flex-1 flex flex-col min-h-0">
<div class="p-4 border-b border-border-dark flex justify-between items-center bg-[#111318]">
<h3 class="font-semibold text-sm text-white">Visual Blocks</h3>
<div class="flex gap-2">
<button class="text-slate-400 hover:text-white" title="Expand All">
<span class="material-symbols-outlined text-[18px]">unfold_more</span>
</button>
</div>
</div>
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-3 custom-scrollbar">
<div class="border-2 border-dashed border-border-dark rounded-lg p-6 flex flex-col items-center justify-center text-center hover:border-primary/50 hover:bg-surface-dark transition-all cursor-pointer group mb-2">
<span class="material-symbols-outlined text-[24px] text-slate-500 group-hover:text-primary mb-2">cloud_upload</span>
<p class="text-xs font-medium text-slate-300">Drop images to create sections</p>
<p class="text-[10px] text-slate-500 mt-1">JPG, PNG, GIF up to 5MB</p>
</div>
<div class="group relative flex flex-col gap-2 p-3 rounded-lg bg-surface-dark border border-primary shadow-[0_0_0_1px_rgba(19,91,236,1)] cursor-pointer">
<div class="flex items-center gap-3">
<div class="text-slate-500 cursor-grab active:cursor-grabbing">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="h-10 w-16 bg-slate-800 rounded overflow-hidden relative">
<img class="object-cover w-full h-full opacity-70" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARKX7eC65mz2IpuBwDVp_I5oS2RC62dOWT-WoIBq5cR6aap-2XMq4Y9ntPmPhycnDw1NszD32Wgwrec6t7qQmVHp1llkEWal5-zfta2pCL4SWfO5v0nYou7715kxFts5jf4O3TDJPkL1hFpSEWNypgSvYP2I-avJIA-hfssJxwf1RyrWDUwewJG14-ivVP91ECuBDyyMLkZA62gUX_fjTwO-W6cCrJmNITt2oWnzn9gAjAX7wxvWfjLfIirMNVNfwolEaHgXl-B_pI"/>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">Main_Hero_V1.jpg</p>
<p class="text-xs text-slate-400">1200x800 • 145KB</p>
</div>
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
<div class="group relative flex flex-col gap-2 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="flex items-center gap-3">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="h-10 w-16 bg-slate-800 rounded overflow-hidden flex items-center justify-center">
<span class="material-symbols-outlined text-slate-600 text-[20px]">gif</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">Demo_Walkthrough.gif</p>
<p class="text-xs text-slate-500">800x600 • 2.1MB</p>
</div>
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700 opacity-0 group-hover:opacity-100 transition-opacity">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
<div class="group relative flex flex-col gap-2 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="flex items-center gap-3">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="h-10 w-16 bg-slate-800 rounded overflow-hidden relative">
<img class="object-cover w-full h-full opacity-70" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBceHj4fe7PQn5roym2DM9WOocCJB0BQxuKExR8ulwggZyARF0-GRXVdS9rV9qEiAfcjcjQ_jH3NIxoAy7IH8ISJ2z6ND6WbUrEwJySKSEN8L3LL8Lr42mnkATadICesv0h2nm5JMy22wnEXjyIs-F2NiXG56Hh6s3HELVhRK4nU-VsEoy8m-tWbh8FR1jndX6iCq6mxaPrd05yp6V5p2ZqEpTjciLxSUFDO4YG2KKXMvQPf2a_6nWrtv7b8FHU7FeM2sIpf3V7YmF"/>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">Social_Proof_Logos.png</p>
<p class="text-xs text-slate-500">1200x200 • 45KB</p>
</div>
<button class="text-slate-400 hover:text-white p-1 rounded hover:bg-slate-700 opacity-0 group-hover:opacity-100 transition-opacity">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
</div>
</div>
</aside>
<section class="flex-1 bg-[#0b0d11] relative flex flex-col items-center justify-center overflow-hidden p-8">
<div class="absolute inset-0 z-0 opacity-20 pointer-events-none" data-alt="Dot pattern background" style="background-image: radial-gradient(#282e39 1px, transparent 1px); background-size: 20px 20px;"></div>
<div class="relative z-10 flex flex-col items-center h-full max-h-[800px] w-full">
<div class="relative w-[375px] h-full bg-black rounded-[3rem] border-[8px] border-slate-800 shadow-2xl overflow-hidden flex flex-col ring-1 ring-white/10">
<div class="h-8 bg-black w-full flex justify-between items-center px-6 shrink-0 select-none">
<span class="text-[10px] font-semibold text-white">9:41</span>
<div class="flex gap-1.5">
<span class="material-symbols-outlined text-[12px] text-white">signal_cellular_alt</span>
<span class="material-symbols-outlined text-[12px] text-white">wifi</span>
<span class="material-symbols-outlined text-[12px] text-white">battery_full</span>
</div>
</div>
<div class="flex-1 bg-white overflow-y-auto no-scrollbar relative w-full">
<div class="relative group cursor-pointer">
<div class="absolute inset-0 border-2 border-primary z-20 pointer-events-none"></div>
<div class="absolute top-2 right-2 z-30 bg-primary text-white text-[10px] px-2 py-0.5 rounded font-bold uppercase tracking-wide shadow-sm">Active</div>
<div class="relative h-auto w-full bg-slate-900 overflow-hidden">
<img alt="Abstract modern startup office background" class="w-full h-auto object-cover" data-alt="Abstract modern startup office background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARKX7eC65mz2IpuBwDVp_I5oS2RC62dOWT-WoIBq5cR6aap-2XMq4Y9ntPmPhycnDw1NszD32Wgwrec6t7qQmVHp1llkEWal5-zfta2pCL4SWfO5v0nYou7715kxFts5jf4O3TDJPkL1hFpSEWNypgSvYP2I-avJIA-hfssJxwf1RyrWDUwewJG14-ivVP91ECuBDyyMLkZA62gUX_fjTwO-W6cCrJmNITt2oWnzn9gAjAX7wxvWfjLfIirMNVNfwolEaHgXl-B_pI"/>
</div>
</div>
<div class="w-full aspect-video bg-slate-100 flex items-center justify-center text-slate-400 border-y border-slate-100 relative group hover:border-blue-300 transition-colors cursor-pointer">
<div class="flex flex-col items-center gap-2">
<span class="material-symbols-outlined text-[32px]">play_circle</span>
<span class="text-xs font-medium uppercase tracking-wider">Product Demo GIF</span>
</div>
</div>
<div class="w-full h-24 bg-slate-50 flex items-center justify-center border-b border-slate-100 group hover:bg-slate-100 cursor-pointer">
<img alt="Logos" class="h-full w-full object-cover opacity-80" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBceHj4fe7PQn5roym2DM9WOocCJB0BQxuKExR8ulwggZyARF0-GRXVdS9rV9qEiAfcjcjQ_jH3NIxoAy7IH8ISJ2z6ND6WbUrEwJySKSEN8L3LL8Lr42mnkATadICesv0h2nm5JMy22wnEXjyIs-F2NiXG56Hh6s3HELVhRK4nU-VsEoy8m-tWbh8FR1jndX6iCq6mxaPrd05yp6V5p2ZqEpTjciLxSUFDO4YG2KKXMvQPf2a_6nWrtv7b8FHU7FeM2sIpf3V7YmF"/>
</div>
<div class="h-20"></div>
</div>
<div class="absolute bottom-0 left-0 right-0 p-4 bg-white/95 backdrop-blur-sm border-t border-slate-100 shadow-lg z-20">
<button class="w-full bg-blue-600 text-white font-bold py-3 rounded-xl shadow-lg shadow-blue-600/30">Get Started Free</button>
</div>
<div class="absolute bottom-1 left-1/2 -translate-x-1/2 w-32 h-1 bg-white/20 rounded-full z-30"></div>
</div>
<div class="mt-4 text-slate-500 text-xs flex gap-2 items-center bg-[#111318] py-1 px-3 rounded-full border border-border-dark">
<span class="size-2 bg-green-500 rounded-full animate-pulse"></span>
Live Preview • iPhone 14 Pro
</div>
</div>
</section>
<aside class="w-[320px] bg-[#111318] border-l border-border-dark flex flex-col shrink-0 z-10">
<div class="px-5 py-4 border-b border-border-dark flex justify-between items-center bg-[#111318]">
<div>
<h3 class="font-semibold text-sm text-white">Global Settings</h3>
<p class="text-xs text-slate-500 mt-0.5">Page-wide configuration</p>
</div>
<button class="size-8 flex items-center justify-center text-slate-400 hover:text-white rounded hover:bg-surface-dark transition-colors" title="Reset defaults">
<span class="material-symbols-outlined text-[18px]">settings_backup_restore</span>
</button>
</div>
<div class="flex border-b border-border-dark">
<button class="flex-1 py-3 text-sm font-medium text-primary border-b-2 border-primary bg-surface-dark/50">General</button>
<button class="flex-1 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-surface-dark transition-colors">SEO &amp; Meta</button>
</div>
<div class="flex-1 overflow-y-auto p-5 custom-scrollbar">
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Internal Title</label>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow placeholder-slate-600" type="text" value="Summer Sale Variant A"/>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">URL Slug</label>
<div class="flex items-center">
<span class="bg-[#1e293b] border border-r-0 border-border-dark text-slate-400 text-xs px-2 py-2.5 rounded-l-md">/lp/</span>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-r-md text-sm text-white px-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow" type="text" value="summer-sale-a"/>
</div>
</div>
<div class="h-px bg-border-dark w-full"></div>
<h4 class="text-sm font-semibold text-white">Sticky CTA Button</h4>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Button Text</label>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow" type="text" value="Get Started Free"/>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Button Color</label>
<div class="flex gap-2">
<div class="h-10 w-full bg-[#0b0d11] border border-border-dark rounded-md flex items-center px-2 gap-2">
<div class="size-6 rounded bg-blue-600 border border-white/10"></div>
<span class="text-sm text-slate-300 font-mono">#2563EB</span>
</div>
<button class="size-10 bg-surface-dark border border-border-dark rounded-md flex items-center justify-center hover:bg-slate-700 text-white">
<span class="material-symbols-outlined text-[18px]">colorize</span>
</button>
</div>
<div class="flex gap-2 mt-1">
<button class="size-6 rounded-full bg-blue-600 border-2 border-white ring-2 ring-blue-600"></button>
<button class="size-6 rounded-full bg-emerald-500 border border-transparent hover:scale-110 transition-transform"></button>
<button class="size-6 rounded-full bg-purple-600 border border-transparent hover:scale-110 transition-transform"></button>
<button class="size-6 rounded-full bg-orange-500 border border-transparent hover:scale-110 transition-transform"></button>
<button class="size-6 rounded-full bg-slate-900 border border-slate-700 hover:scale-110 transition-transform"></button>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">Destination URL</label>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow" type="text" value="https://app.vantage.com/signup"/>
</div>
<div class="h-px bg-border-dark w-full"></div>
<div class="flex items-center justify-between p-3 rounded-lg bg-[#0b0d11] border border-border-dark">
<div class="flex flex-col">
<span class="text-sm font-medium text-white">Google Analytics</span>
<span class="text-xs text-slate-500">Enable page view events</span>
</div>
<button class="w-11 h-6 bg-primary rounded-full relative transition-colors focus:outline-none">
<span class="absolute left-6 top-1 bg-white size-4 rounded-full transition-transform shadow-sm"></span>
</button>
</div>
</div>
</div>
<div class="p-4 border-t border-border-dark bg-[#111318]">
<button class="w-full py-2.5 rounded-lg bg-surface-dark border border-border-dark text-slate-300 text-sm font-medium hover:bg-slate-700 hover:text-white transition-colors flex items-center justify-center gap-2">
<span class="material-symbols-outlined text-[18px]">tune</span>
Advanced Settings
</button>
</div>
</aside>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

View File

@@ -0,0 +1,322 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>랜딩 페이지 편집기</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1e293b",
"border-dark": "#282e39",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
borderRadius: {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"2xl": "1rem",
"3xl": "1.5rem",
"full": "9999px"
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #374151;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white flex flex-col h-screen overflow-hidden">
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-border-dark bg-[#111318] px-6 py-3 shrink-0 z-20">
<div class="flex items-center gap-4 text-white">
<a class="text-slate-400 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">arrow_back</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg text-primary">
<span class="material-symbols-outlined">auto_awesome_motion</span>
</div>
<div>
<h2 class="text-white text-base font-bold leading-tight tracking-[-0.015em]">페이지 편집기</h2>
<div class="flex items-center gap-2 text-xs text-slate-400 mt-0.5">
<span>캠페인</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span>여름 세일 프로모션</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span class="text-white">메인 랜딩 페이지</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex bg-border-dark rounded-lg p-1 gap-1">
<button class="p-1.5 rounded bg-[#111318] text-white shadow-sm transition-all">
<span class="material-symbols-outlined text-[18px]">smartphone</span>
</button>
<button class="p-1.5 rounded hover:bg-[#111318]/50 text-slate-400 hover:text-white transition-all">
<span class="material-symbols-outlined text-[18px]">laptop</span>
</button>
</div>
<div class="h-6 w-px bg-border-dark"></div>
<div class="flex gap-3">
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-border-dark hover:bg-slate-700 text-white text-sm font-semibold transition-colors">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>미리보기</span>
</button>
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-primary hover:bg-primary/90 text-white text-sm font-semibold transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[18px]">save</span>
<span>저장</span>
</button>
</div>
<button class="size-9 rounded-full bg-slate-700 bg-cover bg-center ml-2 border border-slate-600" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></button>
</div>
</header>
<main class="flex flex-1 overflow-hidden h-full">
<aside class="w-[320px] bg-[#111318] border-r border-border-dark flex flex-col shrink-0 z-10">
<div class="p-4 border-b border-border-dark flex justify-between items-center bg-[#111318]">
<h3 class="font-semibold text-sm text-white">페이지 구조</h3>
<div class="flex items-center gap-2">
<span class="text-[10px] text-slate-500 font-bold uppercase tracking-wider">HTML 편집</span>
<button class="w-8 h-4 bg-slate-700 rounded-full relative transition-colors focus:outline-none">
<span class="absolute left-1 top-1 bg-slate-400 size-2 rounded-full transition-transform"></span>
</button>
</div>
</div>
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-3 custom-scrollbar">
<div class="p-3 rounded-lg border border-dashed border-border-dark bg-[#141820] text-center mb-2">
<span class="material-symbols-outlined text-slate-500 text-[24px] mb-1">add_photo_alternate</span>
<p class="text-[11px] text-slate-400 leading-tight">여기에 이미지를 드래그하여<br/>새 섹션을 추가하세요</p>
</div>
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-primary shadow-[0_0_0_1px_rgba(19,91,236,1)] cursor-pointer hover:bg-slate-800 transition-colors">
<div class="text-slate-500 cursor-grab active:cursor-grabbing">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-[#111318] flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">image</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">상단 배너</p>
<p class="text-xs text-slate-500 truncate">이미지 + 로고</p>
</div>
<button class="text-slate-400 hover:text-white p-1 rounded">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-surface-dark flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">title</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">가치 제안</p>
<p class="text-xs text-slate-500 truncate">H1 + 본문</p>
</div>
<button class="opacity-0 group-hover:opacity-100 text-slate-400 hover:text-white p-1 rounded">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-surface-dark flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">gif_box</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">데모 영상</p>
<p class="text-xs text-slate-500 truncate">루프 GIF</p>
</div>
<button class="opacity-0 group-hover:opacity-100 text-slate-400 hover:text-white p-1 rounded">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
<div class="group relative flex items-center gap-3 p-3 rounded-lg bg-[#111318] border border-border-dark cursor-pointer hover:bg-surface-dark hover:border-slate-600 transition-colors">
<div class="text-slate-600 cursor-grab active:cursor-grabbing group-hover:text-slate-500">
<span class="material-symbols-outlined text-[20px]">drag_indicator</span>
</div>
<div class="size-8 rounded bg-surface-dark flex items-center justify-center text-slate-400 shrink-0">
<span class="material-symbols-outlined text-[18px]">touch_app</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-300 truncate">고정 CTA</p>
<p class="text-xs text-slate-500 truncate">하단 고정 버튼</p>
</div>
<button class="opacity-0 group-hover:opacity-100 text-slate-400 hover:text-white p-1 rounded">
<span class="material-symbols-outlined text-[18px]">more_vert</span>
</button>
</div>
</div>
<div class="p-4 border-t border-border-dark mt-auto bg-[#111318]">
<button class="w-full py-2.5 rounded-lg border border-dashed border-slate-600 text-slate-400 hover:text-white hover:border-slate-400 hover:bg-surface-dark transition-all flex items-center justify-center gap-2 text-sm font-medium">
<span class="material-symbols-outlined text-[20px]">add_circle</span>
섹션 추가
</button>
</div>
</aside>
<section class="flex-1 bg-[#0b0d11] relative flex flex-col items-center justify-center overflow-hidden p-8">
<div class="absolute inset-0 z-0 opacity-20 pointer-events-none" style="background-image: radial-gradient(#282e39 1px, transparent 1px); background-size: 20px 20px;"></div>
<div class="relative z-10 flex flex-col items-center h-full max-h-[800px] w-full">
<div class="relative w-[375px] h-full bg-black rounded-[3rem] border-[8px] border-slate-800 shadow-2xl overflow-hidden flex flex-col ring-1 ring-white/10">
<div class="h-8 bg-black w-full flex justify-between items-center px-6 shrink-0 select-none">
<span class="text-[10px] font-semibold text-white">9:41</span>
<div class="flex gap-1.5">
<span class="material-symbols-outlined text-[12px] text-white">signal_cellular_alt</span>
<span class="material-symbols-outlined text-[12px] text-white">wifi</span>
<span class="material-symbols-outlined text-[12px] text-white">battery_full</span>
</div>
</div>
<div class="flex-1 bg-white overflow-y-auto no-scrollbar relative w-full">
<div class="relative group">
<div class="absolute inset-0 border-2 border-primary z-20 pointer-events-none"></div>
<div class="absolute top-2 right-2 z-30 bg-primary text-white text-[10px] px-2 py-0.5 rounded font-bold tracking-wide shadow-sm">편집 중</div>
<div class="relative h-48 w-full bg-slate-900 overflow-hidden">
<img alt="Header background" class="w-full h-full object-cover opacity-80" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBceHj4fe7PQn5roym2DM9WOocCJB0BQxuKExR8ulwggZyARF0-GRXVdS9rV9qEiAfcjcjQ_jH3NIxoAy7IH8ISJ2z6ND6WbUrEwJySKSEN8L3LL8Lr42mnkATadICesv0h2nm5JMy22wnEXjyIs-F2NiXG56Hh6s3HELVhRK4nU-VsEoy8m-tWbh8FR1jndX6iCq6mxaPrd05yp6V5p2ZqEpTjciLxSUFDO4YG2KKXMvQPf2a_6nWrtv7b8FHU7FeM2sIpf3V7YmF"/>
<div class="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent flex flex-col justify-end p-6">
<div class="flex items-center gap-2 mb-2">
<div class="size-8 bg-blue-600 rounded-lg flex items-center justify-center text-white font-bold text-lg">V</div>
<span class="text-white font-bold text-lg tracking-tight">Vantage</span>
</div>
<h1 class="text-white text-2xl font-bold leading-tight">이전에 없던 SaaS 성장을 경험하세요.</h1>
</div>
</div>
</div>
<div class="p-6 bg-white">
<h2 class="text-slate-900 text-xl font-bold mb-3">성장을 위한 올인원 플랫폼</h2>
<p class="text-slate-600 text-base leading-relaxed">여러 도구를 번갈아 쓸 필요가 없습니다. 분석, CRM, 마케팅 자동화를 하나의 대시보드에서 해결하세요.</p>
</div>
<div class="w-full aspect-video bg-slate-100 flex items-center justify-center text-slate-400 border-y border-slate-100">
<div class="flex flex-col items-center gap-2">
<span class="material-symbols-outlined text-[32px]">play_circle</span>
<span class="text-xs font-medium uppercase tracking-wider">제품 데모 GIF</span>
</div>
</div>
<div class="h-20"></div>
</div>
<div class="absolute bottom-0 left-0 right-0 p-4 bg-white/95 backdrop-blur-sm border-t border-slate-100 shadow-lg z-20">
<button class="w-full bg-blue-600 text-white font-bold py-3 rounded-xl shadow-lg shadow-blue-600/30">지금 무료로 시작하기</button>
</div>
<div class="absolute bottom-1 left-1/2 -translate-x-1/2 w-32 h-1 bg-white/20 rounded-full z-30"></div>
</div>
<div class="mt-4 text-slate-500 text-xs flex gap-2 items-center bg-[#111318] py-1 px-3 rounded-full border border-border-dark">
<span class="size-2 bg-green-500 rounded-full animate-pulse"></span>
라이브 미리보기 • iPhone 14 Pro
</div>
</div>
</section>
<aside class="w-[360px] bg-[#111318] border-l border-border-dark flex flex-col shrink-0 z-10">
<div class="px-5 py-4 border-b border-border-dark flex justify-between items-center bg-[#111318]">
<div>
<h3 class="font-semibold text-sm text-white">속성 편집: 상단 배너</h3>
<p class="text-xs text-slate-500 mt-0.5">레이아웃 및 콘텐츠 설정</p>
</div>
<div class="flex gap-1">
<button class="size-8 flex items-center justify-center text-slate-400 hover:text-white rounded hover:bg-surface-dark transition-colors" title="초기화">
<span class="material-symbols-outlined text-[18px]">restart_alt</span>
</button>
<button class="size-8 flex items-center justify-center text-slate-400 hover:text-red-400 rounded hover:bg-surface-dark transition-colors" title="삭제">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</div>
<div class="flex border-b border-border-dark">
<button class="flex-1 py-3 text-sm font-medium text-primary border-b-2 border-primary bg-surface-dark/50">콘텐츠</button>
<button class="flex-1 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-surface-dark transition-colors">스타일</button>
<button class="flex-1 py-3 text-sm font-medium text-slate-400 hover:text-white hover:bg-surface-dark transition-colors">설정</button>
</div>
<div class="flex-1 overflow-y-auto p-5 custom-scrollbar">
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">배경 이미지</label>
<div class="border-2 border-dashed border-border-dark rounded-lg p-4 hover:border-primary/50 hover:bg-surface-dark transition-all cursor-pointer group text-center">
<div class="size-16 bg-[#111318] rounded-md mx-auto mb-3 overflow-hidden border border-border-dark relative">
<img alt="Current background" class="w-full h-full object-cover opacity-60 group-hover:opacity-40 transition-opacity" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARKX7eC65mz2IpuBwDVp_I5oS2RC62dOWT-WoIBq5cR6aap-2XMq4Y9ntPmPhycnDw1NszD32Wgwrec6t7qQmVHp1llkEWal5-zfta2pCL4SWfO5v0nYou7715kxFts5jf4O3TDJPkL1hFpSEWNypgSvYP2I-avJIA-hfssJxwf1RyrWDUwewJG14-ivVP91ECuBDyyMLkZA62gUX_fjTwO-W6cCrJmNITt2oWnzn9gAjAX7wxvWfjLfIirMNVNfwolEaHgXl-B_pI"/>
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<span class="material-symbols-outlined text-white">edit</span>
</div>
</div>
<p class="text-sm text-primary font-medium">클릭하거나 이미지를 드래그하세요</p>
<p class="text-xs text-slate-500 mt-1">JPG, PNG, WEBP (최대 2MB)</p>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">헤드라인 문구</label>
<textarea class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-shadow resize-none" rows="3">이전에 없던 SaaS 성장을 경험하세요.</textarea>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">회사명/서비스명</label>
<div class="relative">
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white pl-3 pr-10 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow" type="text" value="Vantage"/>
<div class="absolute right-3 top-2 text-slate-500">
<span class="material-symbols-outlined text-[18px]">edit</span>
</div>
</div>
</div>
<div class="flex items-center justify-between p-3 rounded-lg bg-[#0b0d11] border border-border-dark">
<div class="flex flex-col">
<span class="text-sm font-medium text-white">로고 아이콘 표시</span>
<span class="text-xs text-slate-500">이름 옆에 아이콘을 보여줍니다</span>
</div>
<button class="w-11 h-6 bg-primary rounded-full relative transition-colors focus:outline-none">
<span class="absolute left-6 top-1 bg-white size-4 rounded-full transition-transform"></span>
</button>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">텍스트 정렬</label>
<div class="flex bg-[#0b0d11] p-1 rounded-md border border-border-dark">
<button class="flex-1 py-1.5 rounded text-slate-400 hover:bg-surface-dark hover:text-white transition-colors flex justify-center">
<span class="material-symbols-outlined text-[20px]">format_align_left</span>
</button>
<button class="flex-1 py-1.5 rounded bg-primary text-white shadow-sm flex justify-center">
<span class="material-symbols-outlined text-[20px]">format_align_center</span>
</button>
<button class="flex-1 py-1.5 rounded text-slate-400 hover:bg-surface-dark hover:text-white transition-colors flex justify-center">
<span class="material-symbols-outlined text-[20px]">format_align_right</span>
</button>
</div>
</div>
</div>
</div>
<div class="p-4 border-t border-border-dark bg-[#111318]">
<button class="w-full py-2.5 rounded-lg bg-primary text-white text-sm font-semibold hover:bg-primary/90 transition-colors shadow-lg shadow-primary/20">
변경사항 적용
</button>
</div>
</aside>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

View File

@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>랜딩 페이지 목록 및 관리</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#0b0d11",
"surface-dark": "#111318",
"border-dark": "#1e293b",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', 'Pretendard', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #0b0d11;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #1e293b;
border-radius: 3px;
}
</style>
</head>
<body class="bg-background-dark text-slate-200 flex h-screen overflow-hidden">
<aside class="w-64 border-r border-border-dark bg-surface-dark flex flex-col shrink-0">
<div class="p-6 border-b border-border-dark flex items-center gap-3">
<div class="size-8 bg-primary rounded-lg flex items-center justify-center text-white">
<span class="material-symbols-outlined">dashboard</span>
</div>
<span class="font-bold text-lg tracking-tight text-white">Vantage</span>
</div>
<nav class="flex-1 p-4 space-y-2">
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg bg-primary/10 text-primary font-medium" href="#">
<span class="material-symbols-outlined text-[22px]">web</span>
랜딩 페이지
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">analytics</span>
통계 및 분석
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">group</span>
리드 관리
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">settings</span>
설정
</a>
</nav>
<div class="p-4 border-t border-border-dark">
<div class="flex items-center gap-3 px-2">
<div class="size-9 rounded-full bg-slate-700 bg-cover bg-center border border-slate-600" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">김철수 팀장</p>
<p class="text-xs text-slate-500 truncate">Pro Plan</p>
</div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col min-w-0 bg-background-dark">
<header class="h-16 border-b border-border-dark flex items-center justify-between px-8 bg-surface-dark">
<div class="flex items-center gap-2">
<h1 class="text-xl font-bold text-white">랜딩 페이지 목록 및 관리</h1>
<span class="ml-2 px-2 py-0.5 bg-slate-800 text-slate-400 text-xs rounded-full">총 24개</span>
</div>
<div class="flex items-center gap-4">
<div class="relative">
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 text-xl">search</span>
<input class="bg-background-dark border border-border-dark rounded-lg pl-10 pr-4 py-2 text-sm focus:ring-1 focus:ring-primary focus:border-primary outline-none text-white w-64" placeholder="페이지 이름 검색..." type="text"/>
</div>
<button class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg text-sm font-semibold flex items-center gap-2 transition-all shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[20px]">add</span>
새 페이지 추가
</button>
</div>
</header>
<div class="flex-1 overflow-auto p-8 custom-scrollbar">
<div class="bg-surface-dark border border-border-dark rounded-xl overflow-hidden shadow-2xl">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-border-dark bg-slate-800/50">
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">페이지 정보</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">공개 URL</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">상태</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider text-right">관리</th>
</tr>
</thead>
<tbody class="divide-y divide-border-dark">
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<img alt="Preview" class="w-full h-full object-cover opacity-60" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBceHj4fe7PQn5roym2DM9WOocCJB0BQxuKExR8ulwggZyARF0-GRXVdS9rV9qEiAfcjcjQ_jH3NIxoAy7IH8ISJ2z6ND6WbUrEwJySKSEN8L3LL8Lr42mnkATadICesv0h2nm5JMy22wnEXjyIs-F2NiXG56Hh6s3HELVhRK4nU-VsEoy8m-tWbh8FR1jndX6iCq6mxaPrd05yp6V5p2ZqEpTjciLxSUFDO4YG2KKXMvQPf2a_6nWrtv7b8FHU7FeM2sIpf3V7YmF"/>
</div>
<div>
<p class="font-semibold text-white">2024 여름 시즌 세일 캠페인</p>
<p class="text-xs text-slate-500 mt-1">수정일: 2시간 전 • 생성일: 2024.05.12</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/summer-2024</span>
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 text-xs font-medium">
<span class="size-1.5 rounded-full bg-green-500"></span>
활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="p-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors" title="URL 복사">
<span class="material-symbols-outlined text-[20px]">content_copy</span>
</button>
<button class="p-2 text-slate-400 hover:text-primary hover:bg-primary/10 rounded-lg transition-colors" title="편집">
<span class="material-symbols-outlined text-[20px]">edit</span>
</button>
<button class="p-2 text-slate-400 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors" title="삭제">
<span class="material-symbols-outlined text-[20px]">delete</span>
</button>
</div>
</td>
</tr>
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<span class="material-symbols-outlined">image</span>
</div>
<div>
<p class="font-semibold text-white">신규 제품 사전 예약 페이지</p>
<p class="text-xs text-slate-500 mt-1">수정일: 1일 전 • 생성일: 2024.06.01</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/pre-order-now</span>
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-slate-800 text-slate-500 text-xs font-medium border border-slate-700">
<span class="size-1.5 rounded-full bg-slate-500"></span>
비활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="p-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors" title="URL 복사">
<span class="material-symbols-outlined text-[20px]">content_copy</span>
</button>
<button class="p-2 text-slate-400 hover:text-primary hover:bg-primary/10 rounded-lg transition-colors" title="편집">
<span class="material-symbols-outlined text-[20px]">edit</span>
</button>
<button class="p-2 text-slate-400 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors" title="삭제">
<span class="material-symbols-outlined text-[20px]">delete</span>
</button>
</div>
</td>
</tr>
<tr class="hover:bg-white/[0.02] transition-colors group border-b-0">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<img alt="Preview" class="w-full h-full object-cover opacity-60" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARKX7eC65mz2IpuBwDVp_I5oS2RC62dOWT-WoIBq5cR6aap-2XMq4Y9ntPmPhycnDw1NszD32Wgwrec6t7qQmVHp1llkEWal5-zfta2pCL4SWfO5v0nYou7715kxFts5jf4O3TDJPkL1hFpSEWNypgSvYP2I-avJIA-hfssJxwf1RyrWDUwewJG14-ivVP91ECuBDyyMLkZA62gUX_fjTwO-W6cCrJmNITt2oWnzn9gAjAX7wxvWfjLfIirMNVNfwolEaHgXl-B_pI"/>
</div>
<div>
<p class="font-semibold text-white">엔터프라이즈 솔루션 문의</p>
<p class="text-xs text-slate-500 mt-1">수정일: 3일 전 • 생성일: 2024.04.15</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/enterprise-demo</span>
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 text-xs font-medium">
<span class="size-1.5 rounded-full bg-green-500"></span>
활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="p-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors" title="URL 복사">
<span class="material-symbols-outlined text-[20px]">content_copy</span>
</button>
<button class="p-2 text-slate-400 hover:text-primary hover:bg-primary/10 rounded-lg transition-colors" title="편집">
<span class="material-symbols-outlined text-[20px]">edit</span>
</button>
<button class="p-2 text-slate-400 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors" title="삭제">
<span class="material-symbols-outlined text-[20px]">delete</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="px-6 py-4 border-t border-border-dark flex items-center justify-between bg-slate-800/20">
<span class="text-sm text-slate-500">24개 중 1-10개 표시</span>
<div class="flex items-center gap-2">
<button class="p-1.5 rounded-lg border border-border-dark text-slate-500 hover:text-white hover:bg-slate-800 disabled:opacity-50" disabled="">
<span class="material-symbols-outlined">chevron_left</span>
</button>
<button class="size-8 rounded-lg bg-primary text-white text-sm font-medium">1</button>
<button class="size-8 rounded-lg text-slate-400 hover:bg-slate-800 text-sm font-medium">2</button>
<button class="size-8 rounded-lg text-slate-400 hover:bg-slate-800 text-sm font-medium">3</button>
<button class="p-1.5 rounded-lg border border-border-dark text-slate-500 hover:text-white hover:bg-slate-800">
<span class="material-symbols-outlined">chevron_right</span>
</button>
</div>
</div>
</div>
</div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -0,0 +1,156 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>새 랜딩 페이지 생성</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#0b0d11",
"surface-dark": "#111318",
"border-dark": "#282e39",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
</style>
</head>
<body class="bg-background-dark text-white flex flex-col h-screen overflow-hidden">
<header class="flex items-center justify-between border-b border-border-dark bg-surface-dark px-8 py-4 shrink-0 z-20">
<div class="flex items-center gap-4">
<a class="text-slate-400 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">close</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<h1 class="text-lg font-bold tracking-tight">새 랜딩 페이지 생성</h1>
</div>
<div class="flex items-center gap-3">
<button class="px-5 py-2 rounded-lg text-sm font-semibold text-slate-400 hover:text-white hover:bg-white/5 transition-all">
취소
</button>
<button class="px-6 py-2 rounded-lg bg-primary hover:bg-primary/90 text-white text-sm font-semibold transition-all shadow-lg shadow-primary/20">
생성하기
</button>
</div>
</header>
<main class="flex-1 overflow-y-auto custom-scrollbar flex justify-center bg-background-dark py-12 px-6">
<div class="w-full max-w-2xl flex flex-col gap-8">
<section class="flex flex-col gap-6 bg-surface-dark p-8 rounded-2xl border border-border-dark">
<div class="flex flex-col gap-1 border-b border-border-dark pb-4">
<h3 class="text-base font-semibold">기본 정보</h3>
<p class="text-xs text-slate-500">랜딩 페이지의 식별 정보와 접속 경로를 설정합니다.</p>
</div>
<div class="grid grid-cols-1 gap-6">
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">페이지 이름</label>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-lg text-sm text-white px-4 py-3 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-all" placeholder="예: 2024 여름 할인 캠페인" type="text"/>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">URL 경로</label>
<div class="flex items-center">
<span class="bg-[#0b0d11] border border-r-0 border-border-dark px-4 py-3 text-sm text-slate-500 rounded-l-lg">
example.com/pages/
</span>
<input class="flex-1 bg-[#0b0d11] border border-border-dark rounded-r-lg text-sm text-white px-4 py-3 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-all" placeholder="summer-sale" type="text"/>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">설명</label>
<textarea class="w-full bg-[#0b0d11] border border-border-dark rounded-lg text-sm text-white px-4 py-3 focus:ring-1 focus:ring-primary focus:border-primary placeholder-slate-600 outline-none transition-all resize-none" placeholder="랜딩 페이지에 대한 상세 설명을 입력하세요." rows="4"></textarea>
</div>
</div>
</section>
<section class="flex flex-col gap-6 bg-surface-dark p-8 rounded-2xl border border-border-dark">
<div class="flex flex-col gap-1 border-b border-border-dark pb-4">
<h3 class="text-base font-semibold">고급 설정</h3>
<p class="text-xs text-slate-500">페이지 동작에 필요한 추가 기능을 활성화합니다.</p>
</div>
<div class="space-y-4">
<div class="flex items-center justify-between p-4 rounded-xl bg-[#0b0d11] border border-border-dark hover:border-slate-700 transition-colors">
<div class="flex gap-4 items-center">
<div class="size-10 bg-primary/10 text-primary rounded-lg flex items-center justify-center">
<span class="material-symbols-outlined">task_alt</span>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-white">완료 페이지 사용 여부</span>
<span class="text-xs text-slate-500">제출 성공 후 감사 페이지 또는 리다이렉션을 설정합니다.</span>
</div>
</div>
<button class="w-11 h-6 bg-slate-700 rounded-full relative transition-colors focus:outline-none ring-offset-2 ring-offset-surface-dark group">
<span class="absolute left-1 top-1 bg-white size-4 rounded-full transition-transform"></span>
</button>
</div>
<div class="flex items-center justify-between p-4 rounded-xl bg-[#0b0d11] border border-border-dark hover:border-slate-700 transition-colors">
<div class="flex gap-4 items-center">
<div class="size-10 bg-amber-500/10 text-amber-500 rounded-lg flex items-center justify-center">
<span class="material-symbols-outlined">code</span>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-white">스크립트 사용 여부</span>
<span class="text-xs text-slate-500">GA4, Pixel 등 외부 트래킹 및 커스텀 스크립트를 삽입합니다.</span>
</div>
</div>
<button class="w-11 h-6 bg-primary rounded-full relative transition-colors focus:outline-none ring-offset-2 ring-offset-surface-dark">
<span class="absolute left-6 top-1 bg-white size-4 rounded-full transition-transform"></span>
</button>
</div>
</div>
</section>
<section class="flex flex-col gap-6 bg-surface-dark p-8 rounded-2xl border border-border-dark opacity-80 grayscale pointer-events-none">
<div class="flex flex-col gap-1 border-b border-border-dark pb-4">
<div class="flex items-center gap-2">
<h3 class="text-base font-semibold">템플릿 선택</h3>
<span class="text-[10px] bg-border-dark px-1.5 py-0.5 rounded text-slate-400 font-bold uppercase">Coming Soon</span>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="aspect-[3/4] rounded-lg bg-[#0b0d11] border border-border-dark flex items-center justify-center">
<span class="material-symbols-outlined text-slate-700 text-4xl">web</span>
</div>
<div class="aspect-[3/4] rounded-lg bg-[#0b0d11] border border-border-dark flex items-center justify-center">
<span class="material-symbols-outlined text-slate-700 text-4xl">web</span>
</div>
<div class="aspect-[3/4] rounded-lg bg-[#0b0d11] border border-border-dark flex items-center justify-center">
<span class="material-symbols-outlined text-slate-700 text-4xl">web</span>
</div>
</div>
</section>
</div>
</main>
<footer class="md:hidden border-t border-border-dark bg-surface-dark p-4 shrink-0 flex gap-3">
<button class="flex-1 py-3 rounded-lg text-sm font-semibold text-slate-400 border border-border-dark">
취소
</button>
<button class="flex-1 py-3 rounded-lg bg-primary text-white text-sm font-semibold">
생성하기
</button>
</footer>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@@ -0,0 +1,279 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>스크립트 및 추적 설정 - Variant Builder</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1e293b",
"border-dark": "#282e39",
"code-bg": "#0b0d11"
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
"mono": ["Fira Code", "monospace"]
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
.code-editor {
font-family: 'Fira Code', monospace;
tab-size: 4;
line-height: 1.6;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white flex flex-col h-screen overflow-hidden">
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-border-dark bg-[#111318] px-6 py-3 shrink-0 z-20">
<div class="flex items-center gap-4 text-white">
<a class="text-slate-400 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">arrow_back</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg text-primary">
<span class="material-symbols-outlined">code</span>
</div>
<div>
<h2 class="text-white text-base font-bold leading-tight tracking-[-0.015em]">스크립트 및 추적 설정</h2>
<div class="flex items-center gap-2 text-xs text-slate-400 mt-0.5">
<span>랜딩페이지 관리</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span>여름 프로모션 캠페인</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span class="text-white">스크립트 설정</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex gap-3">
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-border-dark hover:bg-slate-700 text-white text-sm font-semibold transition-colors">
<span>취소</span>
</button>
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-primary hover:bg-primary/90 text-white text-sm font-semibold transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[18px]">save</span>
<span>설정 저장</span>
</button>
</div>
<button class="size-9 rounded-full bg-slate-700 bg-cover bg-center ml-2 border border-slate-600" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></button>
</div>
</header>
<main class="flex flex-1 overflow-hidden h-full">
<aside class="w-[300px] bg-[#111318] border-r border-border-dark flex flex-col shrink-0 z-10">
<div class="p-6">
<h3 class="font-semibold text-sm text-white mb-4">도움말 및 가이드</h3>
<div class="space-y-4">
<div class="p-4 rounded-lg bg-surface-dark/30 border border-border-dark">
<div class="flex items-center gap-2 text-primary mb-2">
<span class="material-symbols-outlined text-sm">info</span>
<span class="text-xs font-bold uppercase tracking-wider">스크립트 삽입 위치</span>
</div>
<p class="text-xs text-slate-400 leading-relaxed">
모든 스크립트는 해당 페이지의 <code class="text-blue-400">&lt;/head&gt;</code> 태그 바로 위에 자동으로 삽입됩니다.
</p>
</div>
<div class="p-4 rounded-lg bg-surface-dark/30 border border-border-dark">
<div class="flex items-center gap-2 text-amber-500 mb-2">
<span class="material-symbols-outlined text-sm">warning</span>
<span class="text-xs font-bold uppercase tracking-wider">주의사항</span>
</div>
<p class="text-xs text-slate-400 leading-relaxed">
잘못된 스크립트는 페이지 렌더링에 영향을 줄 수 있습니다. 외부 라이브러리 연동 시 반드시 테스트 후 배포하세요.
</p>
</div>
</div>
</div>
<div class="mt-auto p-6 border-t border-border-dark">
<div class="flex items-center gap-3 text-slate-400 hover:text-white cursor-pointer transition-colors group">
<span class="material-symbols-outlined group-hover:text-primary">help_outline</span>
<span class="text-sm">고급 가이드 문서 보기</span>
</div>
</div>
</aside>
<section class="flex-1 bg-[#0b0d11] overflow-y-auto custom-scrollbar p-8">
<div class="max-w-4xl mx-auto space-y-8">
<div class="flex flex-col gap-4">
<div class="flex justify-between items-end">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-lg font-bold text-white">글로벌 스크립트</h3>
<span class="px-2 py-0.5 rounded bg-blue-500/10 text-blue-500 text-[10px] font-bold tracking-tight border border-blue-500/20">ALL PAGES</span>
</div>
<p class="text-sm text-slate-500">모든 섹션과 페이지(메인, 랜딩, 완료 페이지)에 공통으로 삽입되는 스크립트입니다. Google Analytics, Facebook Pixel 등을 설정하세요.</p>
</div>
<div class="flex gap-2">
<button class="px-3 py-1.5 rounded bg-surface-dark text-xs text-slate-300 hover:text-white transition-colors">템플릿 불러오기</button>
</div>
</div>
<div class="relative group">
<div class="absolute top-4 left-4 z-10 flex gap-2">
<div class="size-2.5 rounded-full bg-red-500/50"></div>
<div class="size-2.5 rounded-full bg-amber-500/50"></div>
<div class="size-2.5 rounded-full bg-green-500/50"></div>
</div>
<div class="absolute top-4 right-4 z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded bg-slate-800 text-slate-400 hover:text-white" title="복사하기">
<span class="material-symbols-outlined text-[18px]">content_copy</span>
</button>
</div>
<textarea class="w-full h-64 bg-[#0d1117] border border-border-dark rounded-xl p-12 code-editor text-sm text-blue-300 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all" placeholder="&lt;!-- 여기에 스크립트를 입력하세요 (e.g. &lt;script&gt;...&lt;/script&gt;) --&gt;" spellcheck="false">&lt;script async="" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"&gt;&lt;/script&gt;
&lt;script&gt;
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
&lt;/script&gt;</textarea>
</div>
</div>
<div class="flex flex-col gap-4">
<div class="flex justify-between items-end">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<h3 class="text-lg font-bold text-white">전환 스크립트</h3>
<span class="px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-500 text-[10px] font-bold tracking-tight border border-emerald-500/20">COMPLETION ONLY</span>
</div>
<p class="text-sm text-slate-500">신청 완료 혹은 구매 완료 페이지에만 삽입되는 스크립트입니다. 전환 추적 코드(Conversion Tracking)를 이곳에 삽입하세요.</p>
</div>
<div class="flex gap-2">
<button class="px-3 py-1.5 rounded bg-surface-dark text-xs text-slate-300 hover:text-white transition-colors">포스트백 가이드</button>
</div>
</div>
<div class="relative group">
<div class="absolute top-4 left-4 z-10 flex gap-2">
<div class="size-2.5 rounded-full bg-red-500/50"></div>
<div class="size-2.5 rounded-full bg-amber-500/50"></div>
<div class="size-2.5 rounded-full bg-green-500/50"></div>
</div>
<div class="absolute top-4 right-4 z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded bg-slate-800 text-slate-400 hover:text-white" title="복사하기">
<span class="material-symbols-outlined text-[18px]">content_copy</span>
</button>
</div>
<textarea class="w-full h-48 bg-[#0d1117] border border-border-dark rounded-xl p-12 code-editor text-sm text-emerald-300 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all" placeholder="&lt;!-- 전환 추적 스크립트를 입력하세요 --&gt;" spellcheck="false">&lt;script&gt;
// Conversion event for purchase
fbq('track', 'Purchase', {
value: 29.00,
currency: 'USD'
});
&lt;/script&gt;</textarea>
</div>
</div>
<div class="grid grid-cols-2 gap-4 pb-12">
<div class="p-4 rounded-xl bg-surface-dark border border-border-dark flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-[#111318] flex items-center justify-center text-slate-400">
<span class="material-symbols-outlined">analytics</span>
</div>
<div>
<p class="text-sm font-semibold text-white">기본 로그 수집</p>
<p class="text-xs text-slate-500">방문자 수 및 유입 경로 자동 분석</p>
</div>
</div>
<button class="w-10 h-5 bg-primary rounded-full relative transition-colors">
<span class="absolute right-1 top-1 bg-white size-3 rounded-full"></span>
</button>
</div>
<div class="p-4 rounded-xl bg-surface-dark border border-border-dark flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-[#111318] flex items-center justify-center text-slate-400">
<span class="material-symbols-outlined">ads_click</span>
</div>
<div>
<p class="text-sm font-semibold text-white">UTM 파라미터 보존</p>
<p class="text-xs text-slate-500">페이지 이동 시 광고 파라미터 유지</p>
</div>
</div>
<button class="w-10 h-5 bg-primary rounded-full relative transition-colors">
<span class="absolute right-1 top-1 bg-white size-3 rounded-full"></span>
</button>
</div>
</div>
</div>
</section>
<aside class="w-[360px] bg-[#111318] border-l border-border-dark flex flex-col shrink-0 z-10">
<div class="px-5 py-4 border-b border-border-dark flex justify-between items-center bg-[#111318]">
<div>
<h3 class="font-semibold text-sm text-white">스크립트 변경 이력</h3>
<p class="text-xs text-slate-500 mt-0.5">최근 5개의 변경 사항</p>
</div>
<button class="size-8 flex items-center justify-center text-slate-400 hover:text-white rounded hover:bg-surface-dark transition-colors">
<span class="material-symbols-outlined text-[18px]">history</span>
</button>
</div>
<div class="flex-1 overflow-y-auto p-5 custom-scrollbar">
<div class="flex flex-col gap-4">
<div class="flex gap-3">
<div class="shrink-0 mt-1">
<div class="size-2 rounded-full bg-primary"></div>
<div class="w-0.5 h-full bg-border-dark mx-auto mt-1"></div>
</div>
<div class="flex-1 pb-4">
<p class="text-xs font-semibold text-white">Google Analytics 4 추가</p>
<p class="text-[10px] text-slate-500 mt-0.5">2024.05.24 14:20 • admin_user</p>
</div>
</div>
<div class="flex gap-3">
<div class="shrink-0 mt-1">
<div class="size-2 rounded-full bg-slate-600"></div>
<div class="w-0.5 h-full bg-border-dark mx-auto mt-1"></div>
</div>
<div class="flex-1 pb-4">
<p class="text-xs font-semibold text-slate-400">Facebook Pixel 스크립트 수정</p>
<p class="text-[10px] text-slate-500 mt-0.5">2024.05.20 10:15 • dev_team</p>
</div>
</div>
<div class="flex gap-3">
<div class="shrink-0 mt-1">
<div class="size-2 rounded-full bg-slate-600"></div>
</div>
<div class="flex-1">
<p class="text-xs font-semibold text-slate-400">최초 스크립트 설정</p>
<p class="text-[10px] text-slate-500 mt-0.5">2024.05.15 09:00 • system</p>
</div>
</div>
</div>
</div>
<div class="p-6 border-t border-border-dark bg-[#111318]">
<div class="p-4 rounded-lg bg-primary/5 border border-primary/20">
<div class="flex items-center gap-2 text-primary mb-2">
<span class="material-symbols-outlined text-sm">auto_awesome</span>
<span class="text-xs font-bold uppercase tracking-wider">AI 도우미</span>
</div>
<p class="text-[11px] text-slate-400 leading-relaxed mb-3">
설치하려는 도구의 이름만 입력하면 AI가 최적화된 스크립트 코드를 생성해 드립니다.
</p>
<button class="w-full py-2 bg-primary/10 hover:bg-primary/20 text-primary text-xs font-bold rounded transition-colors">
AI로 스크립트 생성하기
</button>
</div>
</div>
</aside>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

View File

@@ -0,0 +1,247 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Completion Page Editor - Vantage</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#101622",
"surface-dark": "#1e293b",
"border-dark": "#282e39",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
borderRadius: {
"DEFAULT": "0.25rem",
"lg": "0.5rem",
"xl": "0.75rem",
"2xl": "1rem",
"3xl": "1.5rem",
"full": "9999px"
},
},
},
}
</script>
<style type="text/tailwindcss">
@layer base {
body {
@apply font-body;
}
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #111318;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #282e39;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #374151;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white flex flex-col h-screen overflow-hidden text-sm">
<header class="flex items-center justify-between border-b border-border-dark bg-[#111318] px-6 py-3 shrink-0 z-20">
<div class="flex items-center gap-4 text-white">
<a class="text-slate-400 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">arrow_back</span>
</a>
<div class="h-6 w-px bg-border-dark mx-2"></div>
<div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg text-primary">
<span class="material-symbols-outlined">task_alt</span>
</div>
<div>
<h2 class="text-white text-base font-bold leading-tight tracking-tight">완료 페이지 설정</h2>
<div class="flex items-center gap-2 text-xs text-slate-400 mt-0.5">
<span>Summer Sale 2024</span>
<span class="material-symbols-outlined text-[10px]">chevron_right</span>
<span class="text-white">감사 페이지 구성</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex bg-border-dark rounded-lg p-1 gap-1">
<button class="p-1.5 rounded bg-[#111318] text-white shadow-sm transition-all">
<span class="material-symbols-outlined text-[18px]">smartphone</span>
</button>
<button class="p-1.5 rounded hover:bg-[#111318]/50 text-slate-400 hover:text-white transition-all">
<span class="material-symbols-outlined text-[18px]">laptop</span>
</button>
</div>
<div class="h-6 w-px bg-border-dark"></div>
<div class="flex gap-3">
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-border-dark hover:bg-slate-700 text-white font-semibold transition-colors">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>미리보기</span>
</button>
<button class="flex items-center justify-center gap-2 rounded-lg h-9 px-4 bg-primary hover:bg-primary/90 text-white font-semibold transition-colors shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[18px]">check_circle</span>
<span>저장 후 게시</span>
</button>
</div>
<button class="size-9 rounded-full bg-slate-700 bg-cover bg-center ml-2 border border-slate-600" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></button>
</div>
</header>
<main class="flex flex-1 overflow-hidden h-full">
<aside class="w-[280px] bg-[#111318] border-r border-border-dark flex flex-col shrink-0 z-10">
<div class="p-4 border-b border-border-dark bg-[#111318]">
<h3 class="font-semibold text-slate-300 uppercase tracking-widest text-[10px]">Page Theme</h3>
</div>
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-2 custom-scrollbar">
<div class="p-3 rounded-lg bg-surface-dark border border-primary flex items-center gap-3">
<span class="material-symbols-outlined text-primary">auto_awesome</span>
<span class="text-sm font-medium">Standard Layout</span>
</div>
<div class="p-3 rounded-lg bg-[#111318] border border-border-dark text-slate-500 flex items-center gap-3 hover:border-slate-600 cursor-not-allowed">
<span class="material-symbols-outlined">splitscreen</span>
<span class="text-sm">Split Image</span>
</div>
<div class="p-3 rounded-lg bg-[#111318] border border-border-dark text-slate-500 flex items-center gap-3 hover:border-slate-600 cursor-not-allowed">
<span class="material-symbols-outlined">video_library</span>
<span class="text-sm">Video Background</span>
</div>
</div>
<div class="p-4 border-t border-border-dark bg-[#111318] text-xs text-slate-500">
<p>완료 페이지는 데이터 수집 후 사용자에게 보여지는 마지막 단계입니다.</p>
</div>
</aside>
<section class="flex-1 bg-[#0b0d11] relative flex flex-col items-center justify-center overflow-hidden p-8">
<div class="absolute inset-0 z-0 opacity-20 pointer-events-none" style="background-image: radial-gradient(#282e39 1px, transparent 1px); background-size: 20px 20px;"></div>
<div class="relative z-10 flex flex-col items-center h-full max-h-[800px] w-full">
<div class="relative w-[375px] h-full bg-black rounded-[3rem] border-[8px] border-slate-800 shadow-2xl overflow-hidden flex flex-col ring-1 ring-white/10">
<div class="h-8 bg-black w-full flex justify-between items-center px-6 shrink-0 select-none">
<span class="text-[10px] font-semibold text-white">9:41</span>
<div class="flex gap-1.5">
<span class="material-symbols-outlined text-[12px] text-white">signal_cellular_alt</span>
<span class="material-symbols-outlined text-[12px] text-white">wifi</span>
<span class="material-symbols-outlined text-[12px] text-white">battery_full</span>
</div>
</div>
<div class="flex-1 bg-white overflow-y-auto no-scrollbar relative w-full flex flex-col">
<div class="flex-1 flex flex-col items-center justify-center p-8 text-center">
<div class="size-20 bg-green-50 text-green-500 rounded-full flex items-center justify-center mb-6">
<span class="material-symbols-outlined text-[48px]">check_circle</span>
</div>
<h1 class="text-slate-900 text-2xl font-bold mb-4">신청이 완료되었습니다!</h1>
<p class="text-slate-600 text-base leading-relaxed mb-8">
입력하신 이메일로 가이드북을 발송해 드렸습니다. <br/>
문의사항은 고객센터로 연락주세요.
</p>
<a class="w-full bg-primary text-white font-bold py-4 rounded-xl shadow-lg shadow-primary/30" href="#">
홈으로 돌아가기
</a>
</div>
<div class="p-8 flex justify-center">
<div class="flex items-center gap-2 opacity-30 grayscale">
<div class="size-6 bg-slate-900 rounded flex items-center justify-center text-white text-[10px] font-bold">V</div>
<span class="text-slate-900 font-bold tracking-tight text-sm">Vantage</span>
</div>
</div>
</div>
<div class="absolute bottom-1 left-1/2 -translate-x-1/2 w-32 h-1 bg-white/20 rounded-full z-30"></div>
</div>
<div class="mt-4 text-slate-500 text-xs flex gap-2 items-center bg-[#111318] py-1.5 px-4 rounded-full border border-border-dark">
<span class="size-2 bg-green-500 rounded-full animate-pulse"></span>
실시간 미리보기 • iPhone 14 Pro
</div>
</div>
</section>
<aside class="w-[360px] bg-[#111318] border-l border-border-dark flex flex-col shrink-0 z-10">
<div class="px-5 py-4 border-b border-border-dark bg-[#111318]">
<h3 class="font-semibold text-sm text-white">콘텐츠 설정</h3>
<p class="text-xs text-slate-500 mt-0.5">완료 페이지의 문구와 동작을 수정합니다.</p>
</div>
<div class="flex-1 overflow-y-auto p-5 custom-scrollbar">
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">메인 아이콘</label>
<div class="flex items-center gap-3 p-3 bg-[#0b0d11] border border-border-dark rounded-lg">
<div class="size-10 bg-green-500/10 text-green-500 rounded flex items-center justify-center">
<span class="material-symbols-outlined">check_circle</span>
</div>
<div class="flex-1">
<span class="text-xs text-slate-400 block mb-1">Color: Success Green</span>
<div class="flex gap-1">
<div class="size-4 rounded-full bg-green-500 cursor-pointer border border-white/20"></div>
<div class="size-4 rounded-full bg-primary cursor-pointer"></div>
<div class="size-4 rounded-full bg-orange-500 cursor-pointer"></div>
<div class="size-4 rounded-full bg-slate-500 cursor-pointer"></div>
</div>
</div>
<button class="text-slate-400 hover:text-white">
<span class="material-symbols-outlined text-[20px]">sync</span>
</button>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">완료 메시지</label>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2.5 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow" type="text" value="신청이 완료되었습니다!"/>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">추가 안내 문구</label>
<textarea class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2.5 focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-shadow resize-none" rows="4">입력하신 이메일로 가이드북을 발송해 드렸습니다.
문의사항은 고객센터로 연락주세요.</textarea>
</div>
<div class="h-px bg-border-dark my-2"></div>
<div class="flex flex-col gap-4">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">버튼 설정</label>
<div class="flex flex-col gap-2">
<span class="text-[11px] text-slate-500">버튼 텍스트</span>
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white px-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none" type="text" value="홈으로 돌아가기"/>
</div>
<div class="flex flex-col gap-2">
<span class="text-[11px] text-slate-500">클릭 시 이동 (Link)</span>
<div class="relative">
<input class="w-full bg-[#0b0d11] border border-border-dark rounded-md text-sm text-white pl-8 pr-3 py-2 focus:ring-1 focus:ring-primary focus:border-primary outline-none" type="text" value="https://vantage.io/home"/>
<span class="material-symbols-outlined absolute left-2 top-2 text-[18px] text-slate-600">link</span>
</div>
</div>
</div>
<div class="flex flex-col gap-3">
<label class="text-xs font-semibold text-slate-300 uppercase tracking-wider">추가 설정</label>
<div class="flex items-center justify-between p-3 rounded-lg bg-[#0b0d11] border border-border-dark">
<span class="text-xs font-medium text-slate-300">자동 리다이렉트 (5초 후)</span>
<button class="w-9 h-5 bg-border-dark rounded-full relative transition-colors">
<span class="absolute left-1 top-1 bg-slate-500 size-3 rounded-full transition-transform"></span>
</button>
</div>
<div class="flex items-center justify-between p-3 rounded-lg bg-[#0b0d11] border border-border-dark">
<span class="text-xs font-medium text-slate-300">SNS 공유 버튼 표시</span>
<button class="w-9 h-5 bg-primary rounded-full relative transition-colors">
<span class="absolute left-5 top-1 bg-white size-3 rounded-full transition-transform"></span>
</button>
</div>
</div>
</div>
</div>
<div class="p-4 border-t border-border-dark bg-[#111318]">
<button class="w-full py-3 rounded-lg bg-primary text-white text-sm font-semibold hover:bg-primary/90 transition-colors shadow-lg shadow-primary/20">
변경사항 적용하기
</button>
</div>
</aside>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

View File

@@ -0,0 +1,253 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>랜딩 페이지 목록 및 관리</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#0b0d11",
"surface-dark": "#111318",
"border-dark": "#1e293b",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', 'Pretendard', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #0b0d11;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #1e293b;
border-radius: 3px;
}
</style>
</head>
<body class="bg-background-dark text-slate-200 flex h-screen overflow-hidden">
<aside class="w-64 border-r border-border-dark bg-surface-dark flex flex-col shrink-0">
<div class="p-6 border-b border-border-dark flex items-center gap-3">
<div class="size-8 bg-primary rounded-lg flex items-center justify-center text-white">
<span class="material-symbols-outlined">dashboard</span>
</div>
<span class="font-bold text-lg tracking-tight text-white">Vantage</span>
</div>
<nav class="flex-1 p-4 space-y-2">
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg bg-primary/10 text-primary font-medium" href="#">
<span class="material-symbols-outlined text-[22px]">web</span>
랜딩 페이지
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">analytics</span>
통계 및 분석
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">group</span>
리드 관리
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined text-[22px]">settings</span>
설정
</a>
</nav>
<div class="p-4 border-t border-border-dark">
<div class="flex items-center gap-3 px-2">
<div class="size-9 rounded-full bg-slate-700 bg-cover bg-center border border-slate-600" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">김철수 팀장</p>
<p class="text-xs text-slate-500 truncate">Pro 플랜</p>
</div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col min-w-0 bg-background-dark">
<header class="h-16 border-b border-border-dark flex items-center justify-between px-8 bg-surface-dark">
<div class="flex items-center gap-2">
<h1 class="text-xl font-bold text-white">랜딩 페이지 목록 및 관리</h1>
<span class="ml-2 px-2 py-0.5 bg-slate-800 text-slate-400 text-xs rounded-full">총 24개</span>
</div>
<div class="flex items-center gap-4">
<div class="relative">
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 text-xl">search</span>
<input class="bg-background-dark border border-border-dark rounded-lg pl-10 pr-4 py-2 text-sm focus:ring-1 focus:ring-primary focus:border-primary outline-none text-white w-64" placeholder="페이지 이름 검색..." type="text"/>
</div>
<button class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg text-sm font-semibold flex items-center gap-2 transition-all shadow-lg shadow-primary/20">
<span class="material-symbols-outlined text-[20px]">add</span>
새 페이지 추가
</button>
</div>
</header>
<div class="flex-1 overflow-auto p-8 custom-scrollbar">
<div class="bg-surface-dark border border-border-dark rounded-xl overflow-hidden shadow-2xl">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-border-dark bg-slate-800/50">
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">페이지 정보</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">공개 URL</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">상태</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider text-right">관리</th>
</tr>
</thead>
<tbody class="divide-y divide-border-dark">
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<img alt="Preview" class="w-full h-full object-cover opacity-60" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBceHj4fe7PQn5roym2DM9WOocCJB0BQxuKExR8ulwggZyARF0-GRXVdS9rV9qEiAfcjcjQ_jH3NIxoAy7IH8ISJ2z6ND6WbUrEwJySKSEN8L3LL8Lr42mnkATadICesv0h2nm5JMy22wnEXjyIs-F2NiXG56Hh6s3HELVhRK4nU-VsEoy8m-tWbh8FR1jndX6iCq6mxaPrd05yp6V5p2ZqEpTjciLxSUFDO4YG2KKXMvQPf2a_6nWrtv7b8FHU7FeM2sIpf3V7YmF"/>
</div>
<div>
<p class="font-semibold text-white">2024 여름 시즌 세일 캠페인</p>
<p class="text-xs text-slate-500 mt-1">수정일: 2시간 전 • 생성일: 2024.05.12</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/summer-2024</span>
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 text-xs font-medium">
<span class="size-1.5 rounded-full bg-green-500"></span>
활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-white hover:bg-slate-800 border border-border-dark rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">content_copy</span>
복사
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-primary hover:bg-primary/10 border border-border-dark hover:border-primary/30 rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">edit</span>
수정
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-red-400 hover:bg-red-400/10 border border-border-dark hover:border-red-400/30 rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">delete</span>
삭제
</button>
</div>
</td>
</tr>
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<span class="material-symbols-outlined">image</span>
</div>
<div>
<p class="font-semibold text-white">신규 제품 사전 예약 페이지</p>
<p class="text-xs text-slate-500 mt-1">수정일: 1일 전 • 생성일: 2024.06.01</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/pre-order-now</span>
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-slate-800 text-slate-500 text-xs font-medium border border-slate-700">
<span class="size-1.5 rounded-full bg-slate-500"></span>
비활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-white hover:bg-slate-800 border border-border-dark rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">content_copy</span>
복사
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-primary hover:bg-primary/10 border border-border-dark hover:border-primary/30 rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">edit</span>
수정
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-red-400 hover:bg-red-400/10 border border-border-dark hover:border-red-400/30 rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">delete</span>
삭제
</button>
</div>
</td>
</tr>
<tr class="hover:bg-white/[0.02] transition-colors group border-b-0">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<img alt="Preview" class="w-full h-full object-cover opacity-60" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARKX7eC65mz2IpuBwDVp_I5oS2RC62dOWT-WoIBq5cR6aap-2XMq4Y9ntPmPhycnDw1NszD32Wgwrec6t7qQmVHp1llkEWal5-zfta2pCL4SWfO5v0nYou7715kxFts5jf4O3TDJPkL1hFpSEWNypgSvYP2I-avJIA-hfssJxwf1RyrWDUwewJG14-ivVP91ECuBDyyMLkZA62gUX_fjTwO-W6cCrJmNITt2oWnzn9gAjAX7wxvWfjLfIirMNVNfwolEaHgXl-B_pI"/>
</div>
<div>
<p class="font-semibold text-white">엔터프라이즈 솔루션 문의</p>
<p class="text-xs text-slate-500 mt-1">수정일: 3일 전 • 생성일: 2024.04.15</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/enterprise-demo</span>
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 text-xs font-medium">
<span class="size-1.5 rounded-full bg-green-500"></span>
활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-white hover:bg-slate-800 border border-border-dark rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">content_copy</span>
복사
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-primary hover:bg-primary/10 border border-border-dark hover:border-primary/30 rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">edit</span>
수정
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-red-400 hover:bg-red-400/10 border border-border-dark hover:border-red-400/30 rounded-lg transition-colors flex items-center gap-1">
<span class="material-symbols-outlined text-[16px]">delete</span>
삭제
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="px-6 py-4 border-t border-border-dark flex items-center justify-between bg-slate-800/20">
<span class="text-sm text-slate-500">24개 중 1-10개 표시</span>
<div class="flex items-center gap-2">
<button class="p-1.5 rounded-lg border border-border-dark text-slate-500 hover:text-white hover:bg-slate-800 disabled:opacity-50" disabled="">
<span class="material-symbols-outlined">chevron_left</span>
</button>
<button class="size-8 rounded-lg bg-primary text-white text-sm font-medium">1</button>
<button class="size-8 rounded-lg text-slate-400 hover:bg-slate-800 text-sm font-medium">2</button>
<button class="size-8 rounded-lg text-slate-400 hover:bg-slate-800 text-sm font-medium">3</button>
<button class="p-1.5 rounded-lg border border-border-dark text-slate-500 hover:text-white hover:bg-slate-800">
<span class="material-symbols-outlined">chevron_right</span>
</button>
</div>
</div>
</div>
</div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

View File

@@ -0,0 +1,253 @@
<!DOCTYPE html>
<html class="dark" lang="ko"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>랜딩 페이지 목록 및 관리</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#135bec",
"background-light": "#f6f6f8",
"background-dark": "#0b0d11",
"surface-dark": "#111318",
"border-dark": "#1e293b",
},
fontFamily: {
"display": ["Inter", "sans-serif"],
"body": ["Inter", "sans-serif"],
},
},
},
}
</script>
<style type="text/tailwindcss">
body {
font-family: 'Inter', 'Pretendard', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #0b0d11;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #1e293b;
border-radius: 3px;
}
</style>
</head>
<body class="bg-background-dark text-slate-200 flex h-screen overflow-hidden">
<aside class="w-64 border-r border-border-dark bg-surface-dark flex flex-col shrink-0">
<div class="p-6 border-b border-border-dark flex items-center gap-3">
<div class="size-8 bg-primary rounded-lg flex items-center justify-center text-white">
<span class="material-symbols-outlined !text-[20px]">dashboard</span>
</div>
<span class="font-bold text-lg tracking-tight text-white">Vantage</span>
</div>
<nav class="flex-1 p-4 space-y-2">
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg bg-primary/10 text-primary font-medium" href="#">
<span class="material-symbols-outlined !text-[22px]">language</span>
랜딩 페이지
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined !text-[22px]">bar_chart</span>
통계 및 분석
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined !text-[22px]">group</span>
리드 관리
</a>
<a class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-colors" href="#">
<span class="material-symbols-outlined !text-[22px]">settings</span>
설정
</a>
</nav>
<div class="p-4 border-t border-border-dark">
<div class="flex items-center gap-3 px-2">
<div class="size-9 rounded-full bg-slate-700 bg-cover bg-center border border-slate-600" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCHHV_xxaCD_itmq00wbhHSgMPL6YYIvw0VvbHthgZtpG92t0BLtDw3aFTuxKmLEkdr2eM5KTzDBsQSRUu3O-0wDG6DQGAS-FomveQ9ZPVJxUbucMb_ILTtBUO5klsE_PHCGlFQuISn87l2ED7ExAM-W-Df3Vjo0_k4_kjZqlLq6FTdhiks7vjFE-I-9i1g1xm08UGuyYh0fOZewRGWNCk4ORn3Uf7GdqZOlZc676-XLE3h0PEEhWdQ-Re-FXjqfnFn2XAXdr4LGH2_');"></div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">김철수 팀장</p>
<p class="text-xs text-slate-500 truncate">Pro 플랜</p>
</div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col min-w-0 bg-background-dark">
<header class="h-16 border-b border-border-dark flex items-center justify-between px-8 bg-surface-dark">
<div class="flex items-center gap-2">
<h1 class="text-xl font-bold text-white">랜딩 페이지 목록 및 관리</h1>
<span class="ml-2 px-2 py-0.5 bg-slate-800 text-slate-400 text-xs rounded-full">총 24개</span>
</div>
<div class="flex items-center gap-4">
<div class="relative">
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 !text-xl">search</span>
<input class="bg-background-dark border border-border-dark rounded-lg pl-10 pr-4 py-2 text-sm focus:ring-1 focus:ring-primary focus:border-primary outline-none text-white w-64" placeholder="페이지 이름 검색..." type="text"/>
</div>
<button class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg text-sm font-semibold flex items-center gap-2 transition-all shadow-lg shadow-primary/20">
<span class="material-symbols-outlined !text-[20px]">add</span>
새 페이지 추가
</button>
</div>
</header>
<div class="flex-1 overflow-auto p-8 custom-scrollbar">
<div class="bg-surface-dark border border-border-dark rounded-xl overflow-hidden shadow-2xl">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-border-dark bg-slate-800/50">
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">페이지 정보</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">공개 URL</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider">상태</th>
<th class="px-6 py-4 text-xs font-semibold text-slate-400 uppercase tracking-wider text-right">관리</th>
</tr>
</thead>
<tbody class="divide-y divide-border-dark">
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<img alt="Preview" class="w-full h-full object-cover opacity-60" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBceHj4fe7PQn5roym2DM9WOocCJB0BQxuKExR8ulwggZyARF0-GRXVdS9rV9qEiAfcjcjQ_jH3NIxoAy7IH8ISJ2z6ND6WbUrEwJySKSEN8L3LL8Lr42mnkATadICesv0h2nm5JMy22wnEXjyIs-F2NiXG56Hh6s3HELVhRK4nU-VsEoy8m-tWbh8FR1jndX6iCq6mxaPrd05yp6V5p2ZqEpTjciLxSUFDO4YG2KKXMvQPf2a_6nWrtv7b8FHU7FeM2sIpf3V7YmF"/>
</div>
<div>
<p class="font-semibold text-white">2024 여름 시즌 세일 캠페인</p>
<p class="text-xs text-slate-500 mt-1">수정일: 2시간 전 • 생성일: 2024.05.12</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/summer-2024</span>
<span class="material-symbols-outlined !text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 text-xs font-medium">
<span class="size-1.5 rounded-full bg-green-500"></span>
활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-white hover:bg-slate-800 border border-border-dark rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">content_copy</span>
<span>복사</span>
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-primary hover:bg-primary/10 border border-border-dark hover:border-primary/30 rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">edit</span>
<span>수정</span>
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-red-400 hover:bg-red-400/10 border border-border-dark hover:border-red-400/30 rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">delete</span>
<span>삭제</span>
</button>
</div>
</td>
</tr>
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<span class="material-symbols-outlined">image</span>
</div>
<div>
<p class="font-semibold text-white">신규 제품 사전 예약 페이지</p>
<p class="text-xs text-slate-500 mt-1">수정일: 1일 전 • 생성일: 2024.06.01</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/pre-order-now</span>
<span class="material-symbols-outlined !text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-slate-800 text-slate-500 text-xs font-medium border border-slate-700">
<span class="size-1.5 rounded-full bg-slate-500"></span>
비활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-white hover:bg-slate-800 border border-border-dark rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">content_copy</span>
<span>복사</span>
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-primary hover:bg-primary/10 border border-border-dark hover:border-primary/30 rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">edit</span>
<span>수정</span>
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-red-400 hover:bg-red-400/10 border border-border-dark hover:border-red-400/30 rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">delete</span>
<span>삭제</span>
</button>
</div>
</td>
</tr>
<tr class="hover:bg-white/[0.02] transition-colors group border-b-0">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="size-12 rounded bg-slate-800 flex items-center justify-center text-slate-500 overflow-hidden shrink-0 border border-slate-700">
<img alt="Preview" class="w-full h-full object-cover opacity-60" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARKX7eC65mz2IpuBwDVp_I5oS2RC62dOWT-WoIBq5cR6aap-2XMq4Y9ntPmPhycnDw1NszD32Wgwrec6t7qQmVHp1llkEWal5-zfta2pCL4SWfO5v0nYou7715kxFts5jf4O3TDJPkL1hFpSEWNypgSvYP2I-avJIA-hfssJxwf1RyrWDUwewJG14-ivVP91ECuBDyyMLkZA62gUX_fjTwO-W6cCrJmNITt2oWnzn9gAjAX7wxvWfjLfIirMNVNfwolEaHgXl-B_pI"/>
</div>
<div>
<p class="font-semibold text-white">엔터프라이즈 솔루션 문의</p>
<p class="text-xs text-slate-500 mt-1">수정일: 3일 전 • 생성일: 2024.04.15</p>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2 text-primary hover:underline cursor-pointer">
<span class="text-sm truncate max-w-[200px]">vantage.io/enterprise-demo</span>
<span class="material-symbols-outlined !text-[16px]">open_in_new</span>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 text-xs font-medium">
<span class="size-1.5 rounded-full bg-green-500"></span>
활성
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-white hover:bg-slate-800 border border-border-dark rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">content_copy</span>
<span>복사</span>
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-primary hover:bg-primary/10 border border-border-dark hover:border-primary/30 rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">edit</span>
<span>수정</span>
</button>
<button class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-red-400 hover:bg-red-400/10 border border-border-dark hover:border-red-400/30 rounded-lg transition-colors flex items-center gap-1.5">
<span class="material-symbols-outlined !text-[18px]">delete</span>
<span>삭제</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="px-6 py-4 border-t border-border-dark flex items-center justify-between bg-slate-800/20">
<span class="text-sm text-slate-500">24개 중 1-10개 표시</span>
<div class="flex items-center gap-2">
<button class="p-1.5 rounded-lg border border-border-dark text-slate-500 hover:text-white hover:bg-slate-800 disabled:opacity-50" disabled="">
<span class="material-symbols-outlined !text-[20px]">chevron_left</span>
</button>
<button class="size-8 rounded-lg bg-primary text-white text-sm font-medium">1</button>
<button class="size-8 rounded-lg text-slate-400 hover:bg-slate-800 text-sm font-medium">2</button>
<button class="size-8 rounded-lg text-slate-400 hover:bg-slate-800 text-sm font-medium">3</button>
<button class="p-1.5 rounded-lg border border-border-dark text-slate-500 hover:text-white hover:bg-slate-800">
<span class="material-symbols-outlined !text-[20px]">chevron_right</span>
</button>
</div>
</div>
</div>
</div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

594
landing-strategy.md Normal file
View File

@@ -0,0 +1,594 @@
# 랜딩 페이지 SaaS 전략 문서
작성일: 2026-03-01
## 1. 제품 방향
- 대상: 광고 운영자가 고객 랜딩 페이지를 빠르게 생성·수정·배포·분석 가능한 SaaS
- 핵심 가치: "빠른 제작 + 안정적인 발행 + 성과 측정 + 팀 협업"
- 기술 스택(기준): Nuxt 4, Elysia, Prisma, SQLite(개발), PostgreSQL(운영), Bun, Nuxt UI, Pinia
## 2. 아키텍처 개요
- Frontend: Nuxt 4 + Nuxt UI + Pinia
- Backend: Elysia REST API
- ORM: Prisma
- DB: 개발 SQLite, 운영 PostgreSQL
- 파일/정적 자산: Object Storage + CDN
- 배포: Docker + CI/CD
## 3. 구현 우선순위(권장)
### 3.1 MVP (필수)
- 사용자/조직 관리 (회원가입, 로그인, 역할 기반 접근)
- 프로젝트/페이지 CRUD
- 랜딩 페이지 빌더(섹션 단위 블록)
- 초안/발행 상태 관리(드래프트/퍼블리시/아카이브)
- 페이지 미리보기
- 폼 제출 수집(기본 리드 저장)
- 기본 분석 이벤트(조회/클릭/제출)
- 템플릿 기반 시작 템플릿 제공
### 3.2 확장판
- 버전 히스토리 + 롤백
- 커스텀 도메인 연결
- 캠페인별 KPI 대시보드
- 자동화 알림(이메일/슬랙)
- 결제/구독 모델(요금제)
- 팀 협업(댓글, 승인 플로우)
## 4. 데이터/도메인 모델 핵심 엔티티
- User: 인증/프로필
- Team: 팀/고객사 단위 소유권
- Project: 고객별 랜딩 프로젝트
- LandingPage: 페이지 메타 정보
- LandingPageVersion: 버전 히스토리
- PublishLog: 발행 이력
- FormSubmission: 랜딩 폼 제출
- LeadEvent: 트래킹 이벤트
- Subscription(선택): 요금제/사용량
## 5. 기능 상세 체크리스트
### 인증/권한
- JWT 또는 세션 기반 인증
- 역할: Owner/Admin/Editor/Viewer
- 팀 단위 ACL + 프로젝트 단위 권한
- 감사 로그 필수
### 멀티테넌시
- team_id/project_id 기반 강제 분리
- 리소스 조회 시 항상 소유권 조건 적용
- 교차 접근 방지 테스트 필수
### 배포/발행
- 페이지 상태: draft, scheduled, published, archived
- 발행 동작은 큐 기반 비동기 처리 고려
- 발행 로그/실패 사유 저장
### SEO/마케팅 설정
- 메타 태그(og,title,description)
- 스크립트 태깅 (GA, 픽셀 등)
- OG 이미지/파비콘/robots 설정
### 분석/리포트
- 이벤트 모델 최소화: page_view, section_view, cta_click, form_submit
- 지표 집계(일 단위)
- 기간별 조회 및 CSV 내보내기
### 보안
- 입력 유효성/인젝션 방지
- rate limit, CORS, CSP, 보안 헤더
- 파일 업로드 확장자/용량 정책
### 운영
- 로그, 모니터링, 에러 추적(Sentry)
- 백업/복구 절차 정리
- 릴리스 전 Prisma migration 검사
## 6. SQLite → PostgreSQL 전환 전략
1. Prisma schema를 PostgreSQL/SQLite 타입 호환 위주로 작성
2. DB 특화 함수/타입(특히 JSON/날짜/enum) 사용 최소화
3. `prisma migrate` 기반 migration 기록을 운영 분기와 동기화
4. 전환 전:
- 마이그레이션 dry-run
- 샘플 데이터 이관 스크립트 작성
- 무중단 전환 테스트
## 7. 권장 개발 규칙
- TypeScript strict 사용
- ESLint/Prettier 통일
- API 계약 우선 (DTO, zod/valibot 등 검증)
- 페이지 빌더 스키마 버전 관리
- CI에서 테스트(요청 시 실행)
## 8. 다음 액션(요청하면 바로 진행)
- Prisma 스키마 초안 작성
- DB ERD/시퀀스 다이어그램 정리
- MVP API 엔드포인트 목록 도출
- 실제 폴더 구조 제안
## 9. 최소 라우트/페이지 설계(1차)
### 9.1 공개 라우트(인증 불필요)
- `/` 또는 `/{path?}`: 도메인 기반 랜딩 라우팅 진입점
- 동작: 요청 호스트 + path 기준으로 매핑된 랜딩 조회 후 렌더
- 조건 엔진 적용: 요일/시간/기간 규칙을 먼저 검사해 매칭 페이지 렌더
- `/_lead/success`: 폼 제출 성공 화면(선택)
- `/_lead/error`: 폼 제출 실패 화면(선택)
### 9.2 운영자 라우트(인증 필요)
- `/login`: 로그인
- `/admin` (Dashboard): 캠페인·리드 개요
- `/admin/campaigns`: 캠페인 목록
- `/admin/campaigns/new`: 캠페인 생성
- `/admin/campaigns/[id]/edit`: 캠페인 편집
- `/admin/pages`: 랜딩 페이지 목록(캠페인 필터)
- `/admin/pages/new`: 랜딩 페이지 생성
- `/admin/pages/[id]`: 랜딩 상세/조회
- `/admin/pages/[id]/builder`: 블록형 빌더 편집
- 하위 탭: 콘텐츠 / 블록 / 도메인 라우팅 / 조건 규칙 / 미리보기
- `/admin/pages/[id]/conditions`: 조건 규칙 전용(요청 시 별도 탭으로 분리 가능)
- `/admin/pages/[id]/routes`: 도메인+경로 매핑 관리
- `/admin/leads`: 리드 조회 목록
- `/admin/leads/[campaignId]`: 캠페인별 리드 목록
- `/admin/users`: 사용자/역할 관리(관리자, 리드관리자)
### 9.3 API 엔드포인트 권장(요청 도메인 라우팅/조건 렌더링 중심)
- `GET /api/public/route-by-host`: 호스트+path 기반 랜딩 조회
- `POST /api/public/lead`: 리드 생성(폼 제출)
- `GET /api/admin/campaigns`
- `POST /api/admin/campaigns`
- `GET /api/admin/pages`
- `POST /api/admin/pages`
- `GET/PUT /api/admin/pages/{id}/builder`
- `GET/POST /api/admin/pages/{id}/conditions`
- `POST /api/admin/pages/{id}/routes`
- `GET /api/admin/leads`
- `GET /api/admin/users`
### 9.4 1차 최소 화면(권장)
- 로그인
- 관리자 대시보드
- 캠페인 목록/생성/수정
- 랜딩 목록/빌더
- 조건 규칙 편집(요일/시간 슬롯)
- 도메인 라우팅 편집
- 리드 목록 조회
- 사용자/역할 관리(선택)
### 9.5 1차 제외(유지보수/확장으로 미루기)
- 버전관리/롤백
- 예약 발행
- 내부 트래픽 분석 대시보드
- 결제/구독
- 다국어/SEO 고도화
## 10. 1차 핵심 데이터 모델 설계(시작안)
### 10.1 엔티티 개요
- `Campaign`: 종목(광고 그룹)
- 한 종목 아래 여러 랜딩 페이지 보유
- `LandingPage`: 실제 렌더링 대상 페이지
- 블록 데이터 저장(이미지/폼/버튼 등)
- `LandingRoute`: 요청 도메인+경로의 기본 매핑
- 예: `aaa.com` + `/`, `aaa.com/google`
- `RouteCondition`: 특정 시간/요일 규칙 매칭 시 교체할 페이지 지정
- `Lead`: 폼 제출 데이터
- `User`: 관리자/리드관리자 계정
### 10.2 엔티티 관계
- Campaign 1:N LandingPage
- Campaign 1:N LandingRoute
- LandingRoute 1:N RouteCondition
- LandingRoute N:1 LandingPage (기본 랜딩)
- RouteCondition N:1 LandingPage (조건 매칭 시 보여줄 랜딩)
- LandingPage 1:N Lead
- Campaign 1:N Lead
### 10.3 권한 설계(최소)
- `ADMIN`
- 모든 관리자 화면 접근
- 캠페인/페이지/조건/도메인/사용자 관리
- `LEAD_MANAGER`
- 리드 목록 조회만 가능
### 10.4 최소 Prisma 스키마 초안(개념용)
```prisma
enum UserRole {
ADMIN
LEAD_MANAGER
}
model Campaign {
id String @id @default(cuid())
name String
description String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
landingPages LandingPage[]
routes LandingRoute[]
leads Lead[]
}
model LandingPage {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
name String
slug String?
blocks Json // 블록형 에디터 JSON
isDefault Boolean @default(false)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
routes LandingRoute[] @relation("RouteDefaultPage")
conditions RouteCondition[]
leads Lead[]
@@index([campaignId, isActive])
}
model LandingRoute {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
host String // 예: aaa.com
path String // 예: /, /google
defaultPageId String
defaultPage LandingPage @relation("RouteDefaultPage", fields: [defaultPageId], references: [id], onDelete: Restrict)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
conditions RouteCondition[]
leads Lead[]
@@unique([host, path])
}
model RouteCondition {
id String @id @default(cuid())
routeId String
route LandingRoute @relation(fields: [routeId], references: [id], onDelete: Cascade)
pageId String
page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict)
label String // 운영자 확인용
priority Int @default(0) // 높을수록 우선
isActive Boolean @default(true)
startDate DateTime?
endDate DateTime?
timezone String? @default("Asia/Seoul")
// 7자리 문자열: 0(sun)~6(sat) 1=적용
weekMask String @default("0000000")
// 분 단위(0~1439)
startMinute Int?
endMinute Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Lead {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
pageId String
page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict)
routeId String?
route LandingRoute? @relation(fields: [routeId], references: [id], onDelete: SetNull)
payload Json // 폼 데이터
sourceMeta Json? // ip, userAgent, referer 등
submittedAt DateTime @default(now())
}
model User {
id String @id @default(cuid())
email String @unique
name String?
password String // bcrypt 해시
role UserRole @default(ADMIN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
### 10.5 조건 평가 규칙(우선순위)
1. 요청 호스트+path로 `LandingRoute` 조회
2. 해당 라우트의 `RouteCondition``isActive = true`만 대상
3. 현재 `day/time/date`가 조건(`weekMask`, `startMinute~endMinute`, `startDate~endDate`)에 일치하면 후보로 포함
4. 후보가 여러 개면 `priority desc, createdAt desc`로 정렬해 첫 번째 사용
5. 후보가 없으면 `defaultPageId` 페이지 렌더
### 10.6 렌더 우선순위 및 폴백
- 매칭은 `exact` 방식(요청 path와 저장된 path가 완전 일치)으로 수행
- 라우트 미존재: 404 또는 안내 페이지
- `startMinute`/`endMinute`가 교차 구간(예: 20:00~06:00)인 경우 자정 넘김 처리
### 10.7 버튼 블록 설정(카카오 포함)
- 공통: `buttonType`, `label`, `style`, `url`, `target`
- 카카오 버튼:
- `kakaoClientId`
- `kakaoSyncCode` 또는 `isKakaoSyncCodeInput`
- `redirectUri`
- 필요 시 스크립트 코드 블록
- 버튼 설정은 페이지 JSON 블록 단위로 저장
### 10.8 빌더 최소 블록 타입
- `hero`(문구/배경)
- `image_gallery`(이미지 나열)
- `form`(입력 폼)
- `button`(일반/카카오 버튼)
- `footer`
## 11. 생성 규칙(공식 방식 고정)
### 11.1 원칙
- Nuxt 4, Elysia, Prisma, Bun의 프로젝트 생성/설정은 공식 문서/공식 CLI 기준으로만 수행
- 임의 경로/구조를 새로 만들지 않고, 공식 권장 생성물을 우선 사용
- 추후 코드 생성은 공식 패턴의 레이어를 유지하며, 기존 공식 파일 위에 기능만 추가
### 11.2 프로젝트 초기화 규칙(요약)
- Nuxt는 공식 `nuxi` 초기화/레이아웃/라우트 규칙 준수
- Elysia는 공식 앱 생성 및 플러그인 미들웨어 등록 순서 준수
- Prisma는 `backend` 기준 `prisma init` + `backend/prisma/schema.prisma` + `migrate` 기준
- Bun은 공식 실행 스크립트/패키지 실행 규칙 준수
### 11.3 다음 작업 시 준수할 실행 순서(예정)
1. `Nuxt` 공식 생성 및 폴더 구조 확보
2. `Elysia` 공식 방식으로 API 엔트리 포인트 생성
3. `Prisma` 스키마를 공식 스키마 문법으로 정식화
4. `Pinia` 공식 store 패턴으로 상태영역 분리
5. 위 규칙에 맞는 페이지/라우트 파일만 추가
## 12. 네트워크/운영 결정 반영(최종 합의안)
### 12.1 환경 기본값
- 로컬 DB: `dev.db` (SQLite)
- 프론트엔드 포트: `3000`
- API 포트: `4000`
- 기본 타임존: `Asia/Seoul`
### 12.2 블록 편집 정책
- 블록은 드래그 앤 드롭으로 순서 변경 지원
- 핵심 블록 타입: 이미지 기반 섹션, 폼, 버튼, 푸터
- 카카오 버튼은 블록 단위 설정값으로 `kakaoSyncCode`(카카오 싱크 고유값) 입력 지원
### 12.3 권한 정책
- 역할: `ADMIN`, `LEAD_MANAGER` 2개로 확정
- `ADMIN`: 모든 관리 기능
- `LEAD_MANAGER`: 리드 조회만
### 12.4 도메인/경로 규칙
- 기본 도메인 예시: `aaa.com` 사용
- 경로 규칙은 현재 도메인+패스 매칭으로 처리하되, `/`(슬래시 단독 루트) 경로 사용
- 경로 충돌 방지를 위해 `aaa.com` 자체와 `/` 매핑을 동일한 기본 기준으로 운용
### 12.5 exact와 prefix의 의미(확인용)
- exact 매칭: 요청 경로가 저장된 경로와 완전히 같아야 매칭
- 예: 저장 경로가 `/google`이면 요청도 정확히 `/google`일 때만 매칭
- prefix 매칭: 저장된 경로가 요청 경로의 시작 부분이면 매칭
- 예: 저장 경로가 `/`이면 `/google`, `/landing` 모두 매칭(루트가 모든 경로 포괄)
- 현재 1차는 `exact`로 고정
## 13. 1차 정식 Prisma 스키마(실행형 초안)
> SQLite 기준(예: `backend/prisma/schema.prisma`)으로 작성. PostgreSQL 전환 시에는 `provider = "postgresql"`로 교체.
```prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
enum UserRole {
ADMIN
LEAD_MANAGER
}
model Campaign {
id String @id @default(cuid())
name String
description String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
landingPages LandingPage[]
routes LandingRoute[]
leads Lead[]
}
model LandingPage {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
name String
slug String?
blocks Json // 블록형 에디터 데이터(배열)
isDefault Boolean @default(false)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
defaultRoutes LandingRoute[] @relation("RouteDefaultPage")
conditions RouteCondition[]
leads Lead[]
@@index([campaignId, isActive])
}
model LandingRoute {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
host String // ex) aaa.com
path String // ex) /, /google
defaultPageId String
defaultPage LandingPage @relation("RouteDefaultPage", fields: [defaultPageId], references: [id], onDelete: Restrict)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
conditions RouteCondition[]
leads Lead[]
@@unique([host, path])
}
model RouteCondition {
id String @id @default(cuid())
routeId String
route LandingRoute @relation(fields: [routeId], references: [id], onDelete: Cascade)
pageId String
page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict)
label String
priority Int @default(0) // 높을수록 우선
isActive Boolean @default(true)
startDate DateTime?
endDate DateTime?
timezone String @default("Asia/Seoul")
// 7자 문자열(일~토) ex) 0111100
weekMask String @default("0000000")
startMinute Int?
endMinute Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Lead {
id String @id @default(cuid())
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
pageId String
page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict)
routeId String?
route LandingRoute? @relation(fields: [routeId], references: [id], onDelete: SetNull)
payload Json // 폼 submit payload
sourceMeta Json?
submittedAt DateTime @default(now())
@@index([campaignId, submittedAt])
}
model User {
id String @id @default(cuid())
email String @unique
name String?
password String
role UserRole @default(ADMIN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
주의:
- 위 스키마는 개념 정리 목적이며, SQLite에서 지원되지 않는 제약/체크 조건은 실제 마이그레이션 환경에서 조정 필요.
- `RouteRelationDefaultPage`와 같은 별도 보조 모델은 사용하지 않음.
## 14. 라우팅/조건 매칭 알고리즘(1차: exact)
### 14.1 목적
- 요청 host/path에 대해 기본 페이지를 찾아서, 조건 규칙 매칭이 있으면 변형 페이지로 전환.
### 14.2 pseudocode
```ts
// Elysia 핸들러에서 사용
function isWeekMatch(weekMask: string, now: Date, timezone: string): boolean {
const tzNow = new Date(now.toLocaleString("en-US", { timeZone: timezone }))
const day = tzNow.getDay() // 0=Sun..6=Sat
return weekMask[day] === "1"
}
function inTimeRange(startMinute?: number | null, endMinute?: number | null, nowMinute: number): boolean {
if (startMinute == null || endMinute == null) return true // no time restriction
if (startMinute <= endMinute) {
return nowMinute >= startMinute && nowMinute <= endMinute
}
// cross midnight: 예) 20:00~06:00
return nowMinute >= startMinute || nowMinute <= endMinute
}
function inDateRange(startDate: Date | null, endDate: Date | null, now: Date): boolean {
if (startDate && now < startDate) return false
if (endDate && now > endDate) return false
return true
}
async function resolveLandingPage(host: string, path: string) {
const route = await prisma.landingRoute.findFirst({
where: { host, path, isActive: true },
include: { defaultPage: true, conditions: true }
})
if (!route) return null
const now = new Date()
const nowMinute = now.getHours() * 60 + now.getMinutes()
const matched = route.conditions
.filter((c) => c.isActive)
.filter((c) => inDateRange(c.startDate, c.endDate, now))
.filter((c) => isWeekMatch(c.weekMask, now, c.timezone ?? "Asia/Seoul"))
.filter((c) => inTimeRange(c.startMinute, c.endMinute, nowMinute))
.sort((a, b) => b.priority - a.priority || +new Date(b.updatedAt) - +new Date(a.updatedAt))
return matched.length > 0 ? matched[0].pageId : route.defaultPageId
}
```
### 14.3 라우트 우선순위
- 1단계: exact host+path 조회 (`AAA.com`, `/google` 정확히 일치)
- 2단계: 조건 일치 페이지 선택(우선순위/최신 업데이트 순)
- 3단계: 일치 조건 없으면 `defaultPageId`
## 15. 1차 API 계약(구현 시작점)
### 15.1 공개 API
- `GET /api/public/route-by-host`
- Query: `host`, `path`
- Response:
- `page`: blocks 포함 렌더 데이터
- `campaignId`, `pageId`, `routeId`
- `status`: `ok | not_found`
- `POST /api/public/lead`
- Body: `campaignId`, `pageId`, `routeId`, `payload`, `sourceMeta`
- Response: `{ ok: true }`
### 15.2 관리자 API(필수)
- `POST /api/admin/auth/login`
- `GET /api/admin/campaigns`, `POST /api/admin/campaigns`
- `GET /api/admin/pages`, `POST /api/admin/pages`, `GET /api/admin/pages/:id`, `PUT /api/admin/pages/:id`
- `GET /api/admin/pages/:id/route`, `PUT /api/admin/pages/:id/route`
- `GET /api/admin/pages/:id/conditions`, `POST /api/admin/pages/:id/conditions`, `PATCH /api/admin/pages/:id/conditions/:conditionId`
- `GET /api/admin/leads`
- `GET /api/admin/users`, `POST /api/admin/users`, `PUT /api/admin/users/:id/role`
### 15.3 공통 응답 규칙(제안)
- 인증 필요: `{ error: "unauthorized" }`
- 권한 부족: `{ error: "forbidden" }`
- 검증 실패: `{ error: "validation", details: [...] }`
## 16. 문서 분리(운영 모드)
- 전략/결정: `docs/strategy.md`
- DB/스키마: `docs/database.md`
- 라우팅/조건 엔진: `docs/routing-conditions.md`
- 화면/라우팅: `docs/ui-flow.md`
- API 계약: `docs/api-spec.md`
- 실행 코드(초안):
- Prisma 스키마: `backend/prisma/schema.prisma`
- 조건 라우팅 엔진: `backend/src/routing/resolveLandingPage.ts`
향후 구현을 진행할 때는 위 문서를 기준으로 업데이트하고, 필요 시 `landing-strategy.md`는 요약본으로 유지한다.