macbook 에서 리눅스로 이동
5
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
node_modules/
|
||||
*.db
|
||||
15
backend/README.md
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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/시간대 처리 포함 마이그레이션 검증이 필요하다.
|
||||
29
docs/routing-conditions.md
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,60 @@
|
||||
# Nuxt Starter Template
|
||||
|
||||
[](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
|
||||
|
||||
[](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
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
17
frontend/app/assets/css/main.css
Normal 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;
|
||||
}
|
||||
}
|
||||
151
frontend/app/components/admin/CreatePageModal.vue
Normal 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>
|
||||
89
frontend/app/components/admin/CreateProjectModal.vue
Normal 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>
|
||||
41
frontend/app/components/ui/button/index.ts
Normal 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 };
|
||||
|
||||
50
frontend/app/pages/admin.vue
Normal 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>
|
||||
95
frontend/app/pages/admin/index.vue
Normal 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>
|
||||
213
frontend/app/pages/admin/leads.vue
Normal 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>
|
||||
375
frontend/app/pages/admin/pages/[id]/builder.vue
Normal 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('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''')
|
||||
}
|
||||
</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">홈 > 페이지 > 빌더</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>
|
||||
224
frontend/app/pages/admin/pages/[id]/variants.vue
Normal 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>
|
||||
273
frontend/app/pages/admin/pages/index.vue
Normal 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>
|
||||
259
frontend/app/pages/admin/projects.vue
Normal 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>
|
||||
90
frontend/app/pages/index.vue
Normal 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>
|
||||
69
frontend/app/stores/dashboard.ts
Normal 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[]
|
||||
})
|
||||
})
|
||||
242
frontend/app/stores/pages.ts
Normal 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}`
|
||||
}
|
||||
}
|
||||
})
|
||||
156
frontend/app/stores/projects.ts
Normal 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
|
||||
}
|
||||
}
|
||||
})
|
||||
238
frontend/app/stores/variants.ts
Normal 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
6
frontend/eslint.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt(
|
||||
// Your custom configs here
|
||||
)
|
||||
505
frontend/landing-admin-notes.md
Normal 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
@@ -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
@@ -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
|
After Width: | Height: | Size: 4.2 KiB |
13
frontend/renovate.json
Normal 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
@@ -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" }
|
||||
]
|
||||
}
|
||||
134
images/dashboard_admin_login_1/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/dashboard_admin_login_1/screen.png
Normal file
|
After Width: | Height: | Size: 286 KiB |
123
images/dashboard_admin_login_2/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/dashboard_admin_login_2/screen.png
Normal file
|
After Width: | Height: | Size: 284 KiB |
405
images/group_details_settings/code.html
Normal 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 & 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/group_details_settings/screen.png
Normal file
|
After Width: | Height: | Size: 295 KiB |
378
images/groups_dashboard_list_1/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/groups_dashboard_list_1/screen.png
Normal file
|
After Width: | Height: | Size: 296 KiB |
364
images/groups_dashboard_list_2/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/groups_dashboard_list_2/screen.png
Normal file
|
After Width: | Height: | Size: 260 KiB |
474
images/leads_analytics_logs_1/code.html
Normal 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 & Analytics Logs</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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 & 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 & 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>
|
||||
BIN
images/leads_analytics_logs_1/screen.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
446
images/leads_analytics_logs_2/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/leads_analytics_logs_2/screen.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
207
images/public_landing_page_mobile_view/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<!-- Material Symbols -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/public_landing_page_mobile_view/screen.png
Normal file
|
After Width: | Height: | Size: 298 KiB |
262
images/traffic_rule_builder_1/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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 & 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>
|
||||
BIN
images/traffic_rule_builder_1/screen.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
279
images/traffic_rule_builder_2/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/traffic_rule_builder_2/screen.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
403
images/variant_builder_editor_1/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_1/screen.png
Normal file
|
After Width: | Height: | Size: 361 KiB |
202
images/variant_builder_editor_10/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_10/screen.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
318
images/variant_builder_editor_11/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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"></head></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="<!-- 여기에 스크립트를 입력하세요 (e.g. <script>...</script>) -->" spellcheck="false"><script async="" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-XXXXXXXXXX');
|
||||
</script></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="<!-- 전환 추적 스크립트를 입력하세요 -->" spellcheck="false"><script>
|
||||
// Conversion event for purchase
|
||||
fbq('track', 'Purchase', {
|
||||
value: 29.00,
|
||||
currency: 'USD'
|
||||
});
|
||||
</script></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>
|
||||
BIN
images/variant_builder_editor_11/screen.png
Normal file
|
After Width: | Height: | Size: 284 KiB |
318
images/variant_builder_editor_2/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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 & 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>
|
||||
BIN
images/variant_builder_editor_2/screen.png
Normal file
|
After Width: | Height: | Size: 387 KiB |
322
images/variant_builder_editor_3/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_3/screen.png
Normal file
|
After Width: | Height: | Size: 321 KiB |
244
images/variant_builder_editor_4/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_4/screen.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
156
images/variant_builder_editor_5/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_5/screen.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
279
images/variant_builder_editor_6/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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"></head></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="<!-- 여기에 스크립트를 입력하세요 (e.g. <script>...</script>) -->" spellcheck="false"><script async="" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-XXXXXXXXXX');
|
||||
</script></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="<!-- 전환 추적 스크립트를 입력하세요 -->" spellcheck="false"><script>
|
||||
// Conversion event for purchase
|
||||
fbq('track', 'Purchase', {
|
||||
value: 29.00,
|
||||
currency: 'USD'
|
||||
});
|
||||
</script></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>
|
||||
BIN
images/variant_builder_editor_6/screen.png
Normal file
|
After Width: | Height: | Size: 300 KiB |
247
images/variant_builder_editor_7/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_7/screen.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
253
images/variant_builder_editor_8/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_8/screen.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
253
images/variant_builder_editor_9/code.html
Normal 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&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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>
|
||||
BIN
images/variant_builder_editor_9/screen.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
594
landing-strategy.md
Normal 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`는 요약본으로 유지한다.
|
||||