991 lines
27 KiB
HTML
991 lines
27 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
<title>스와이프 모드 - Chakmate</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
/* Blue Light Puzzle Theme - Intelligent, Precise, Cool */
|
|
--primary: #3b82f6;
|
|
--primary-light: #60a5fa;
|
|
--primary-dark: #1d4ed8;
|
|
--secondary: #06b6d4;
|
|
--secondary-light: #22d3ee;
|
|
--accent: #0ea5e9;
|
|
--accent-warn: #38bdf8;
|
|
--accent-danger: #f472b6;
|
|
|
|
--bg-primary: #f8fafc;
|
|
--bg-secondary: #e2e8f0;
|
|
--bg-card: #ffffff;
|
|
--bg-overlay: rgba(14, 165, 233, 0.08);
|
|
|
|
--text-primary: #0f172a;
|
|
--text-secondary: #475569;
|
|
--text-muted: #94a3b8;
|
|
|
|
--shadow-sm: 0 2px 8px rgba(14, 165, 233, 0.06);
|
|
--shadow-md: 0 4px 20px rgba(14, 165, 233, 0.08);
|
|
--shadow-lg: 0 8px 40px rgba(14, 165, 233, 0.12);
|
|
--shadow-glow: 0 0 30px rgba(14, 165, 233, 0.25);
|
|
--shadow-blue: 0 4px 20px rgba(59, 130, 246, 0.3);
|
|
|
|
--space-1: 4px;
|
|
--space-2: 8px;
|
|
--space-3: 12px;
|
|
--space-4: 16px;
|
|
--space-5: 20px;
|
|
--space-6: 24px;
|
|
--space-8: 32px;
|
|
--space-10: 40px;
|
|
--space-12: 48px;
|
|
--space-16: 64px;
|
|
|
|
--radius-sm: 8px;
|
|
--radius-md: 12px;
|
|
--radius-lg: 20px;
|
|
--radius-xl: 28px;
|
|
--radius-full: 9999px;
|
|
|
|
--transition-fast: 150ms ease;
|
|
--transition-base: 250ms ease;
|
|
--transition-slow: 400ms ease;
|
|
--transition-bounce: 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
[data-theme="dark"] {
|
|
--bg-primary: #0a0e1a;
|
|
--bg-secondary: #111827;
|
|
--bg-card: #1e293b;
|
|
--bg-overlay: rgba(14, 165, 233, 0.12);
|
|
--text-primary: #f1f5f9;
|
|
--text-secondary: #94a3b8;
|
|
--text-muted: #64748b;
|
|
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
--shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.6);
|
|
--shadow-glow: 0 0 30px rgba(14, 165, 233, 0.3);
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
min-height: 100vh;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
h1, h2, h3, h4 { font-family: 'Outfit', sans-serif; }
|
|
|
|
/* Buttons */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-2);
|
|
padding: var(--space-4) var(--space-8);
|
|
border-radius: var(--radius-full);
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
font-family: 'Outfit', sans-serif;
|
|
cursor: pointer;
|
|
border: none;
|
|
transition: all var(--transition-base);
|
|
min-height: 56px;
|
|
min-width: 160px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
|
color: white;
|
|
box-shadow: var(--shadow-blue);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 25px rgba(59, 130, 246, 0.4);
|
|
}
|
|
|
|
.btn-primary:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--bg-card);
|
|
color: var(--text-primary);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.btn-ghost {
|
|
background: transparent;
|
|
color: var(--text-secondary);
|
|
min-width: auto;
|
|
padding: var(--space-3) var(--space-4);
|
|
}
|
|
|
|
.btn-icon {
|
|
width: 56px;
|
|
height: 56px;
|
|
padding: 0;
|
|
border-radius: var(--radius-lg);
|
|
}
|
|
|
|
.btn svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
stroke: currentColor;
|
|
fill: none;
|
|
}
|
|
|
|
/* Header */
|
|
.header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: var(--space-4) var(--space-6);
|
|
background: var(--bg-card);
|
|
box-shadow: var(--shadow-sm);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
}
|
|
|
|
.back-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding: var(--space-2) var(--space-4);
|
|
background: var(--bg-secondary);
|
|
border: none;
|
|
border-radius: var(--radius-full);
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: var(--transition-fast);
|
|
}
|
|
|
|
.back-btn:hover { background: var(--primary-light); color: white; }
|
|
|
|
.header-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.undo-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding: var(--space-3) var(--space-5);
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
|
border: none;
|
|
border-radius: var(--radius-full);
|
|
color: white;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
box-shadow: var(--shadow-md), 0 4px 15px rgba(14, 165, 233, 0.3);
|
|
transition: var(--transition-base);
|
|
}
|
|
|
|
.undo-btn:hover:not(:disabled) {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-lg), 0 6px 20px rgba(14, 165, 233, 0.4);
|
|
}
|
|
|
|
.undo-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
|
|
|
|
/* Main Container */
|
|
.main-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: calc(100vh - 70px);
|
|
padding: var(--space-6);
|
|
}
|
|
|
|
/* Progress Header */
|
|
.progress-header {
|
|
text-align: center;
|
|
margin-bottom: var(--space-8);
|
|
}
|
|
|
|
.progress-header h2 {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.progress-header p {
|
|
color: var(--text-muted);
|
|
font-size: 15px;
|
|
}
|
|
|
|
/* Card Stack */
|
|
.card-area {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
padding: var(--space-8) 0;
|
|
}
|
|
|
|
.card-stack {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 320px;
|
|
height: 420px;
|
|
}
|
|
|
|
/* File Card */
|
|
.file-card {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: var(--bg-card);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow-lg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
touch-action: pan-y;
|
|
cursor: grab;
|
|
user-select: none;
|
|
transition: transform 0.1s ease, box-shadow 0.2s ease;
|
|
}
|
|
|
|
.file-card:active { cursor: grabbing; }
|
|
|
|
/* Card Visual Stack Effect */
|
|
.file-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 8px;
|
|
left: 8px;
|
|
right: 8px;
|
|
bottom: -8px;
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-xl);
|
|
z-index: -1;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.file-card::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 4px;
|
|
left: 4px;
|
|
right: 4px;
|
|
bottom: -4px;
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-xl);
|
|
z-index: -2;
|
|
opacity: 0.3;
|
|
}
|
|
|
|
/* Card Header with Type Badge */
|
|
.card-header {
|
|
padding: var(--space-5) var(--space-6);
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.file-type-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
background: rgba(255,255,255,0.2);
|
|
padding: var(--space-2) var(--space-3);
|
|
border-radius: var(--radius-full);
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.file-type-badge .icon { font-size: 16px; }
|
|
|
|
.file-size-badge {
|
|
font-size: 12px;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
/* Card Body */
|
|
.card-body {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: var(--space-8);
|
|
}
|
|
|
|
.file-icon-container {
|
|
width: 120px;
|
|
height: 120px;
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-lg);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: var(--space-6);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.file-icon-container::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
left: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: linear-gradient(45deg, transparent 40%, rgba(255,255,255,0.3) 50%, transparent 60%);
|
|
animation: shimmer 2s infinite;
|
|
}
|
|
|
|
@keyframes shimmer {
|
|
0% { transform: translateX(-100%) translateY(-100%); }
|
|
100% { transform: translateX(100%) translateY(100%); }
|
|
}
|
|
|
|
.file-icon-container .icon { font-size: 64px; }
|
|
|
|
.file-name {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
text-align: center;
|
|
margin-bottom: var(--space-3);
|
|
line-height: 1.3;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.file-date {
|
|
color: var(--text-muted);
|
|
font-size: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.file-date svg { width: 16px; height: 16px; }
|
|
|
|
/* Swipe Indicators */
|
|
.swipe-indicators {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: var(--space-4);
|
|
right: var(--space-4);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
transform: translateY(-50%);
|
|
pointer-events: none;
|
|
z-index: 10;
|
|
}
|
|
|
|
.swipe-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 56px;
|
|
height: 56px;
|
|
border-radius: 50%;
|
|
opacity: 0;
|
|
transform: scale(0.5);
|
|
transition: all var(--transition-base);
|
|
}
|
|
|
|
.swipe-indicator.delete {
|
|
background: var(--accent-danger);
|
|
color: white;
|
|
}
|
|
|
|
.swipe-indicator.keep {
|
|
background: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.swipe-indicator.active {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.swipe-indicator svg { width: 28px; height: 28px; }
|
|
|
|
/* Card Footer */
|
|
.card-footer {
|
|
padding: var(--space-4) var(--space-6);
|
|
background: var(--bg-secondary);
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: var(--space-6);
|
|
}
|
|
|
|
.swipe-hint {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
color: var(--text-muted);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.swipe-hint .arrow {
|
|
font-size: 18px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* Action Buttons Alternative */
|
|
.action-buttons {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: var(--space-6);
|
|
margin-top: var(--space-8);
|
|
}
|
|
|
|
.action-btn {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 50%;
|
|
border: none;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: var(--transition-bounce);
|
|
box-shadow: var(--shadow-md);
|
|
}
|
|
|
|
.action-btn:active {
|
|
transform: scale(0.9);
|
|
}
|
|
|
|
.action-btn.delete {
|
|
background: linear-gradient(135deg, var(--accent-danger), #ef4444);
|
|
color: white;
|
|
}
|
|
|
|
.action-btn.keep {
|
|
background: linear-gradient(135deg, var(--accent), #10b981);
|
|
color: white;
|
|
}
|
|
|
|
.action-btn svg { width: 36px; height: 36px; }
|
|
|
|
/* Swipe Overlay on Card */
|
|
.card-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease;
|
|
pointer-events: none;
|
|
border-radius: var(--radius-xl);
|
|
z-index: 5;
|
|
}
|
|
|
|
.card-overlay.delete {
|
|
background: linear-gradient(135deg, rgba(248,113,113,0.95), rgba(239,68,68,0.95));
|
|
}
|
|
|
|
.card-overlay.keep {
|
|
background: linear-gradient(135deg, rgba(52,211,153,0.95), rgba(16,185,129,0.95));
|
|
}
|
|
|
|
.card-overlay.show { opacity: 1; }
|
|
|
|
.card-overlay .action-label {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: white;
|
|
text-transform: uppercase;
|
|
letter-spacing: 3px;
|
|
}
|
|
|
|
/* Progress Footer */
|
|
.progress-footer {
|
|
padding: var(--space-5);
|
|
background: var(--bg-card);
|
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
|
box-shadow: 0 -4px 20px rgba(30, 27, 46, 0.08);
|
|
}
|
|
|
|
.progress-bar-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
.progress-bar-wrapper {
|
|
flex: 1;
|
|
height: 8px;
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-full);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-bar-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--primary), var(--secondary));
|
|
border-radius: var(--radius-full);
|
|
transition: width 0.5s var(--transition-bounce);
|
|
}
|
|
|
|
.progress-text {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--primary);
|
|
min-width: 60px;
|
|
text-align: right;
|
|
}
|
|
|
|
/* Empty State */
|
|
.empty-state {
|
|
display: none;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
padding: var(--space-16);
|
|
}
|
|
|
|
.empty-state.show { display: flex; }
|
|
|
|
.empty-state .icon-wrapper {
|
|
width: 120px;
|
|
height: 120px;
|
|
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: var(--space-6);
|
|
animation: pulse 2s ease infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(14, 165, 233, 0.4); }
|
|
50% { transform: scale(1.05); box-shadow: 0 0 0 20px rgba(14, 165, 233, 0); }
|
|
}
|
|
|
|
.empty-state .icon-wrapper svg { width: 60px; height: 60px; color: white; }
|
|
|
|
.empty-state h2 {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.empty-state p {
|
|
color: var(--text-muted);
|
|
font-size: 16px;
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.celebrate-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding: var(--space-4) var(--space-6);
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
|
border: none;
|
|
border-radius: var(--radius-full);
|
|
color: white;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
box-shadow: var(--shadow-md);
|
|
transition: var(--transition-base);
|
|
}
|
|
|
|
.celebrate-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
/* Toast */
|
|
.toast {
|
|
position: fixed;
|
|
top: 90px;
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(-20px);
|
|
background: var(--text-primary);
|
|
color: var(--bg-card);
|
|
padding: var(--space-3) var(--space-6);
|
|
border-radius: var(--radius-full);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
box-shadow: var(--shadow-lg);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: all var(--transition-base);
|
|
z-index: 200;
|
|
}
|
|
|
|
.toast.show {
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(0);
|
|
}
|
|
|
|
/* Animations */
|
|
.card-exit-left {
|
|
animation: cardExitLeft 0.4s ease forwards;
|
|
}
|
|
|
|
.card-exit-right {
|
|
animation: cardExitRight 0.4s ease forwards;
|
|
}
|
|
|
|
@keyframes cardExitLeft {
|
|
to { transform: translateX(-150%) rotate(-15deg); opacity: 0; }
|
|
}
|
|
|
|
@keyframes cardExitRight {
|
|
to { transform: translateX(150%) rotate(15deg); opacity: 0; }
|
|
}
|
|
|
|
.card-enter {
|
|
animation: cardEnter 0.4s var(--transition-bounce) forwards;
|
|
}
|
|
|
|
@keyframes cardEnter {
|
|
from { transform: scale(0.9) translateY(30px); opacity: 0; }
|
|
to { transform: scale(1) translateY(0); opacity: 1; }
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 380px) {
|
|
.card-stack { height: 380px; }
|
|
.action-btn { width: 70px; height: 70px; }
|
|
.action-btn svg { width: 30px; height: 30px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="header">
|
|
<button class="back-btn" onclick="window.location.href='index.html'">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
</svg>
|
|
뒤로
|
|
</button>
|
|
<h1 class="header-title">스와이프 모드</h1>
|
|
<button class="undo-btn" id="undoBtn" disabled>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M3 7v6h6M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
|
|
</svg>
|
|
실행 취소
|
|
</button>
|
|
</header>
|
|
|
|
<main class="main-container">
|
|
<div class="progress-header">
|
|
<h2 id="currentFileName">project_proposal.pdf</h2>
|
|
<p>왼쪽으로 스와이프하면 삭제, 오른쪽으로 스와이프하면 보관</p>
|
|
</div>
|
|
|
|
<div class="card-area">
|
|
<div class="card-stack" id="cardStack"></div>
|
|
|
|
<div class="empty-state" id="emptyState">
|
|
<div class="icon-wrapper">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
</svg>
|
|
</div>
|
|
<h2>모두 완료!</h2>
|
|
<p>모든 파일을 검토했습니다</p>
|
|
<button class="celebrate-btn" onclick="resetFiles()">
|
|
다시 시작
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="action-buttons" id="actionButtons">
|
|
<button class="action-btn delete" onclick="handleSwipe('delete')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="3 6 5 6 21 6"/>
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
|
</svg>
|
|
</button>
|
|
<button class="action-btn keep" onclick="handleSwipe('keep')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</main>
|
|
|
|
<footer class="progress-footer">
|
|
<div class="progress-bar-container">
|
|
<div class="progress-bar-wrapper">
|
|
<div class="progress-bar-fill" id="progressFill" style="width: 0%"></div>
|
|
</div>
|
|
<div class="progress-text" id="progressText">0%</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<div class="toast" id="toast">파일 보관됨</div>
|
|
|
|
<script>
|
|
const mockFiles = [
|
|
{ id: 1, name: 'project_proposal.pdf', type: 'pdf', size: '2.4 MB', date: '2024-01-15', icon: '📄' },
|
|
{ id: 2, name: 'meeting_notes.docx', type: 'doc', size: '156 KB', date: '2024-01-14', icon: '📝' },
|
|
{ id: 3, name: 'vacation_photo.jpg', type: 'image', size: '3.2 MB', date: '2024-01-13', icon: '🖼️' },
|
|
{ id: 4, name: 'budget_2024.xlsx', type: 'excel', size: '89 KB', date: '2024-01-12', icon: '📊' },
|
|
{ id: 5, name: 'presentation_final.pptx', type: 'ppt', size: '5.2 MB', date: '2024-01-11', icon: '📽️' },
|
|
{ id: 6, name: 'backup_2023.zip', type: 'archive', size: '156 MB', date: '2024-01-10', icon: '📦' },
|
|
{ id: 7, name: 'contract_signed.pdf', type: 'pdf', size: '1.1 MB', date: '2024-01-09', icon: '📋' },
|
|
];
|
|
|
|
let files = [...mockFiles];
|
|
let historyStack = [];
|
|
let currentIndex = 0;
|
|
|
|
const cardStack = document.getElementById('cardStack');
|
|
const emptyState = document.getElementById('emptyState');
|
|
const actionButtons = document.getElementById('actionButtons');
|
|
const undoBtn = document.getElementById('undoBtn');
|
|
const toast = document.getElementById('toast');
|
|
const progressFill = document.getElementById('progressFill');
|
|
const progressText = document.getElementById('progressText');
|
|
const currentFileName = document.getElementById('currentFileName');
|
|
|
|
function getFileTypeInfo(type) {
|
|
const types = {
|
|
pdf: { label: 'PDF', color: '#ef4444' },
|
|
doc: { label: 'Doc', color: '#3b82f6' },
|
|
image: { label: 'Image', color: '#8b5cf6' },
|
|
excel: { label: 'Excel', color: '#10b981' },
|
|
ppt: { label: 'PPT', color: '#f97316' },
|
|
archive: { label: 'Archive', color: '#6b7280' },
|
|
};
|
|
return types[type] || { label: 'File', color: '#6366f1' };
|
|
}
|
|
|
|
function createCard(file) {
|
|
const typeInfo = getFileTypeInfo(file.type);
|
|
return `
|
|
<div class="file-card" data-id="${file.id}" style="transform: translateX(0) rotate(0deg);">
|
|
<div class="card-overlay delete" id="overlayDelete">
|
|
<span class="action-label">삭제</span>
|
|
</div>
|
|
<div class="card-overlay keep" id="overlayKeep">
|
|
<span class="action-label">보관</span>
|
|
</div>
|
|
|
|
<div class="card-header" style="background: linear-gradient(135deg, ${typeInfo.color}, ${typeInfo.color}dd)">
|
|
<div class="file-type-badge">
|
|
<span class="icon">${file.icon}</span>
|
|
<span>${typeInfo.label}</span>
|
|
</div>
|
|
<span class="file-size-badge">${file.size}</span>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<div class="file-icon-container">
|
|
<span class="icon">${file.icon}</span>
|
|
</div>
|
|
<div class="file-name">${file.name}</div>
|
|
<div class="file-date">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
|
<line x1="16" y1="2" x2="16" y2="6"/>
|
|
<line x1="8" y1="2" x2="8" y2="6"/>
|
|
<line x1="3" y1="10" x2="21" y2="10"/>
|
|
</svg>
|
|
${file.date}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-footer">
|
|
<span class="swipe-hint">
|
|
<span class="arrow">←</span> 삭제
|
|
</span>
|
|
<span class="swipe-hint">
|
|
보관 <span class="arrow">→</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderCard() {
|
|
if (files.length === 0) {
|
|
cardStack.style.display = 'none';
|
|
emptyState.classList.add('show');
|
|
actionButtons.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
cardStack.style.display = 'block';
|
|
emptyState.classList.remove('show');
|
|
actionButtons.style.display = 'flex';
|
|
|
|
const file = files[0];
|
|
cardStack.innerHTML = createCard(file);
|
|
currentFileName.textContent = file.name;
|
|
|
|
setupSwipeGestures();
|
|
updateProgress();
|
|
}
|
|
|
|
function setupSwipeGestures() {
|
|
const card = cardStack.querySelector('.file-card');
|
|
if (!card) return;
|
|
|
|
let startX = 0;
|
|
let currentX = 0;
|
|
let isDragging = false;
|
|
|
|
const overlayDelete = card.querySelector('#overlayDelete');
|
|
const overlayKeep = card.querySelector('#overlayKeep');
|
|
|
|
const handleStart = (e) => {
|
|
startX = e.type === 'mousedown' ? e.clientX : e.touches[0].clientX;
|
|
isDragging = true;
|
|
card.style.transition = 'none';
|
|
};
|
|
|
|
const handleMove = (e) => {
|
|
if (!isDragging) return;
|
|
e.preventDefault();
|
|
|
|
const x = e.type === 'mousemove' ? e.clientX : e.touches[0].clientX;
|
|
currentX = x - startX;
|
|
|
|
const rotation = currentX * 0.05;
|
|
card.style.transform = `translateX(${currentX}px) rotate(${rotation}deg)`;
|
|
|
|
const threshold = 100;
|
|
const progress = Math.min(Math.abs(currentX) / threshold, 1);
|
|
|
|
if (currentX < 0) {
|
|
overlayDelete.style.opacity = progress;
|
|
overlayKeep.style.opacity = 0;
|
|
} else {
|
|
overlayKeep.style.opacity = progress;
|
|
overlayDelete.style.opacity = 0;
|
|
}
|
|
};
|
|
|
|
const handleEnd = () => {
|
|
if (!isDragging) return;
|
|
isDragging = false;
|
|
card.style.transition = 'transform 0.3s ease';
|
|
|
|
const threshold = 100;
|
|
|
|
if (currentX < -threshold) {
|
|
handleSwipe('delete');
|
|
} else if (currentX > threshold) {
|
|
handleSwipe('keep');
|
|
} else {
|
|
card.style.transform = 'translateX(0) rotate(0deg)';
|
|
overlayDelete.style.opacity = 0;
|
|
overlayKeep.style.opacity = 0;
|
|
}
|
|
|
|
currentX = 0;
|
|
};
|
|
|
|
card.addEventListener('mousedown', handleStart);
|
|
card.addEventListener('touchstart', handleStart, { passive: true });
|
|
|
|
document.addEventListener('mousemove', handleMove);
|
|
document.addEventListener('touchmove', handleMove, { passive: false });
|
|
|
|
document.addEventListener('mouseup', handleEnd);
|
|
document.addEventListener('touchend', handleEnd);
|
|
}
|
|
|
|
function handleSwipe(action) {
|
|
if (files.length === 0) return;
|
|
|
|
const card = cardStack.querySelector('.file-card');
|
|
const file = files.shift();
|
|
|
|
historyStack.push({ file, action });
|
|
undoBtn.disabled = false;
|
|
|
|
if (action === 'delete') {
|
|
card.classList.add('card-exit-left');
|
|
showToast('파일 삭제됨');
|
|
} else {
|
|
card.classList.add('card-exit-right');
|
|
showToast('파일 보관됨');
|
|
}
|
|
|
|
setTimeout(() => {
|
|
renderCard();
|
|
}, 400);
|
|
}
|
|
|
|
function undo() {
|
|
if (historyStack.length === 0) return;
|
|
|
|
const last = historyStack.pop();
|
|
files.unshift(last.file);
|
|
|
|
if (historyStack.length === 0) {
|
|
undoBtn.disabled = true;
|
|
}
|
|
|
|
showToast(`${last.file.name} 복원됨`);
|
|
renderCard();
|
|
}
|
|
|
|
function showToast(message) {
|
|
toast.textContent = message;
|
|
toast.classList.add('show');
|
|
setTimeout(() => toast.classList.remove('show'), 2000);
|
|
}
|
|
|
|
function updateProgress() {
|
|
const total = mockFiles.length;
|
|
const completed = total - files.length;
|
|
const percent = Math.round((completed / total) * 100);
|
|
|
|
progressFill.style.width = `${percent}%`;
|
|
progressText.textContent = `${percent}%`;
|
|
}
|
|
|
|
function resetFiles() {
|
|
files = [...mockFiles];
|
|
historyStack = [];
|
|
undoBtn.disabled = true;
|
|
renderCard();
|
|
}
|
|
|
|
undoBtn.addEventListener('click', undo);
|
|
renderCard();
|
|
</script>
|
|
</body>
|
|
</html> |