|
|
<!DOCTYPE html>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>Step 2: Upload Event Photos - Smart Photo Selection</title>
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
|
background: #f5f7fa;
|
|
|
min-height: 100vh;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
|
|
|
.header {
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
padding: 30px 20px;
|
|
|
text-align: center;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.header h1 {
|
|
|
font-size: 28px;
|
|
|
font-weight: 600;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.header h1 .highlight {
|
|
|
color: #ffd700;
|
|
|
}
|
|
|
|
|
|
.header p {
|
|
|
opacity: 0.9;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.container {
|
|
|
max-width: 900px;
|
|
|
margin: 0 auto;
|
|
|
padding: 30px 20px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.steps {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: flex-start;
|
|
|
gap: 15px;
|
|
|
margin-bottom: 40px;
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
.step {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
min-width: 80px;
|
|
|
}
|
|
|
|
|
|
.step-circle {
|
|
|
width: 40px;
|
|
|
height: 40px;
|
|
|
border-radius: 50%;
|
|
|
background: #e0e0e0;
|
|
|
color: #999;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
font-weight: 600;
|
|
|
font-size: 16px;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.step.active .step-circle {
|
|
|
background: #667eea;
|
|
|
color: white;
|
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
|
}
|
|
|
|
|
|
.step.completed .step-circle {
|
|
|
background: #4CAF50;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.step-label {
|
|
|
font-size: 12px;
|
|
|
color: #666;
|
|
|
text-align: center;
|
|
|
max-width: 80px;
|
|
|
}
|
|
|
|
|
|
.step.active .step-label {
|
|
|
color: #667eea;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.step-connector {
|
|
|
width: 40px;
|
|
|
height: 2px;
|
|
|
background: #e0e0e0;
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
.step.completed + .step-connector {
|
|
|
background: #4CAF50;
|
|
|
}
|
|
|
|
|
|
|
|
|
.status-banner {
|
|
|
border-radius: 12px;
|
|
|
padding: 15px 20px;
|
|
|
margin-bottom: 25px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.status-banner.has-ref {
|
|
|
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
|
|
}
|
|
|
|
|
|
.status-banner.no-ref {
|
|
|
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
|
|
}
|
|
|
|
|
|
.status-banner .info {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
.status-banner .icon {
|
|
|
font-size: 24px;
|
|
|
}
|
|
|
|
|
|
.status-banner .text {
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.status-banner .text strong {
|
|
|
color: #2e7d32;
|
|
|
}
|
|
|
|
|
|
.status-banner.no-ref .text strong {
|
|
|
color: #e65100;
|
|
|
}
|
|
|
|
|
|
.status-banner a {
|
|
|
color: #667eea;
|
|
|
text-decoration: none;
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.status-banner a:hover {
|
|
|
text-decoration: underline;
|
|
|
}
|
|
|
|
|
|
|
|
|
.card {
|
|
|
background: white;
|
|
|
border-radius: 16px;
|
|
|
box-shadow: 0 2px 20px rgba(0,0,0,0.08);
|
|
|
padding: 40px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.card-icon {
|
|
|
width: 60px;
|
|
|
height: 60px;
|
|
|
margin: 0 auto 20px;
|
|
|
background: #f0f4ff;
|
|
|
border-radius: 16px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
.card-icon svg {
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
color: #667eea;
|
|
|
}
|
|
|
|
|
|
.card h2 {
|
|
|
text-align: center;
|
|
|
font-size: 22px;
|
|
|
font-weight: 600;
|
|
|
margin-bottom: 10px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.card > p {
|
|
|
text-align: center;
|
|
|
color: #666;
|
|
|
margin-bottom: 30px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.upload-tabs {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.upload-tab {
|
|
|
flex: 1;
|
|
|
padding: 12px 20px;
|
|
|
border: 2px solid #e0e0e0;
|
|
|
border-radius: 8px;
|
|
|
background: white;
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s;
|
|
|
font-family: inherit;
|
|
|
}
|
|
|
|
|
|
.upload-tab:hover {
|
|
|
border-color: #667eea;
|
|
|
}
|
|
|
|
|
|
.upload-tab.active {
|
|
|
border-color: #667eea;
|
|
|
background: #f0f4ff;
|
|
|
color: #667eea;
|
|
|
}
|
|
|
|
|
|
|
|
|
.folder-input-area {
|
|
|
border: 2px dashed #d0d7e3;
|
|
|
border-radius: 12px;
|
|
|
padding: 50px 20px;
|
|
|
text-align: center;
|
|
|
background: #fafbfc;
|
|
|
}
|
|
|
|
|
|
.folder-input-area.hidden {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
.folder-input-area h3 {
|
|
|
font-size: 18px;
|
|
|
font-weight: 500;
|
|
|
margin-bottom: 8px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.folder-input-area > p {
|
|
|
color: #888;
|
|
|
font-size: 14px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.folder-path-input {
|
|
|
width: 100%;
|
|
|
max-width: 500px;
|
|
|
padding: 14px 18px;
|
|
|
border: 2px solid #e0e0e0;
|
|
|
border-radius: 8px;
|
|
|
font-size: 14px;
|
|
|
font-family: 'Consolas', monospace;
|
|
|
transition: border-color 0.2s;
|
|
|
}
|
|
|
|
|
|
.folder-path-input:focus {
|
|
|
outline: none;
|
|
|
border-color: #667eea;
|
|
|
}
|
|
|
|
|
|
.folder-hint {
|
|
|
font-size: 12px;
|
|
|
color: #999;
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.upload-area {
|
|
|
border: 2px dashed #d0d7e3;
|
|
|
border-radius: 12px;
|
|
|
padding: 50px 20px;
|
|
|
text-align: center;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
background: #fafbfc;
|
|
|
}
|
|
|
|
|
|
.upload-area.hidden {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
.upload-area:hover {
|
|
|
border-color: #667eea;
|
|
|
background: #f0f4ff;
|
|
|
}
|
|
|
|
|
|
.upload-area.drag-over {
|
|
|
border-color: #667eea;
|
|
|
background: #e8edff;
|
|
|
transform: scale(1.01);
|
|
|
}
|
|
|
|
|
|
.upload-icon {
|
|
|
font-size: 56px;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.upload-area h3 {
|
|
|
font-size: 18px;
|
|
|
font-weight: 500;
|
|
|
margin-bottom: 8px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.upload-area p {
|
|
|
color: #888;
|
|
|
font-size: 14px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
padding: 12px 28px;
|
|
|
border: none;
|
|
|
border-radius: 8px;
|
|
|
font-size: 15px;
|
|
|
font-weight: 500;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s;
|
|
|
font-family: inherit;
|
|
|
}
|
|
|
|
|
|
.btn-primary {
|
|
|
background: #667eea;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.btn-primary:hover {
|
|
|
background: #5a6fd6;
|
|
|
transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
.btn-success {
|
|
|
background: #4CAF50;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.btn-success:hover {
|
|
|
background: #43a047;
|
|
|
}
|
|
|
|
|
|
.btn-secondary {
|
|
|
background: #f0f0f0;
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
.btn-secondary:hover {
|
|
|
background: #e0e0e0;
|
|
|
}
|
|
|
|
|
|
|
|
|
.settings-panel {
|
|
|
margin-top: 30px;
|
|
|
padding-top: 30px;
|
|
|
border-top: 1px solid #eee;
|
|
|
}
|
|
|
|
|
|
.settings-panel h3 {
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
margin-bottom: 8px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.settings-panel > p {
|
|
|
color: #888;
|
|
|
font-size: 13px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.quality-buttons {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
gap: 12px;
|
|
|
margin-bottom: 25px;
|
|
|
}
|
|
|
|
|
|
.quality-btn {
|
|
|
padding: 15px;
|
|
|
border: 2px solid #e0e0e0;
|
|
|
border-radius: 12px;
|
|
|
background: white;
|
|
|
cursor: pointer;
|
|
|
text-align: center;
|
|
|
transition: all 0.2s;
|
|
|
}
|
|
|
|
|
|
.quality-btn:hover {
|
|
|
border-color: #667eea;
|
|
|
}
|
|
|
|
|
|
.quality-btn.active {
|
|
|
border-color: #667eea;
|
|
|
background: #f0f4ff;
|
|
|
}
|
|
|
|
|
|
.quality-btn .icon {
|
|
|
font-size: 28px;
|
|
|
margin-bottom: 8px;
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
.quality-btn .label {
|
|
|
font-weight: 600;
|
|
|
font-size: 14px;
|
|
|
color: #333;
|
|
|
display: block;
|
|
|
margin-bottom: 4px;
|
|
|
}
|
|
|
|
|
|
.quality-btn .desc {
|
|
|
font-size: 11px;
|
|
|
color: #888;
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
.quality-btn.active .label {
|
|
|
color: #667eea;
|
|
|
}
|
|
|
|
|
|
|
|
|
.slider-group {
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
.slider-group label {
|
|
|
display: block;
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
margin-bottom: 10px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.slider-group input[type="range"] {
|
|
|
width: 100%;
|
|
|
height: 6px;
|
|
|
border-radius: 3px;
|
|
|
background: #e0e0e0;
|
|
|
outline: none;
|
|
|
-webkit-appearance: none;
|
|
|
}
|
|
|
|
|
|
.slider-group input[type="range"]::-webkit-slider-thumb {
|
|
|
-webkit-appearance: none;
|
|
|
width: 20px;
|
|
|
height: 20px;
|
|
|
border-radius: 50%;
|
|
|
background: #667eea;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.slider-labels {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
margin-top: 8px;
|
|
|
font-size: 12px;
|
|
|
color: #888;
|
|
|
}
|
|
|
|
|
|
.slider-labels .value {
|
|
|
color: #667eea;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
|
|
|
.file-preview {
|
|
|
margin-top: 30px;
|
|
|
padding-top: 30px;
|
|
|
border-top: 1px solid #eee;
|
|
|
}
|
|
|
|
|
|
.file-preview.hidden {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
.file-preview-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.file-preview h3 {
|
|
|
font-size: 16px;
|
|
|
font-weight: 500;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.file-count {
|
|
|
background: #667eea;
|
|
|
color: white;
|
|
|
padding: 4px 12px;
|
|
|
border-radius: 20px;
|
|
|
font-size: 13px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.preview-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
|
|
gap: 10px;
|
|
|
max-height: 200px;
|
|
|
overflow-y: auto;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.preview-item {
|
|
|
aspect-ratio: 1;
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
background: #f0f0f0;
|
|
|
}
|
|
|
|
|
|
.preview-item img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: cover;
|
|
|
}
|
|
|
|
|
|
.more-indicator {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
background: #667eea;
|
|
|
color: white;
|
|
|
font-weight: 600;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
|
|
|
.processing-overlay {
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
bottom: 0;
|
|
|
background: rgba(255,255,255,0.98);
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
z-index: 1000;
|
|
|
}
|
|
|
|
|
|
.processing-overlay.hidden {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
.spinner {
|
|
|
width: 60px;
|
|
|
height: 60px;
|
|
|
border: 4px solid #f0f0f0;
|
|
|
border-top-color: #667eea;
|
|
|
border-radius: 50%;
|
|
|
animation: spin 1s linear infinite;
|
|
|
}
|
|
|
|
|
|
@keyframes spin {
|
|
|
to { transform: rotate(360deg); }
|
|
|
}
|
|
|
|
|
|
.processing-overlay h2 {
|
|
|
margin-top: 25px;
|
|
|
font-size: 20px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.progress-container {
|
|
|
width: 300px;
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
.progress-bar {
|
|
|
height: 8px;
|
|
|
background: #e0e0e0;
|
|
|
border-radius: 4px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.progress-fill {
|
|
|
height: 100%;
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
transition: width 0.3s;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.progress-text {
|
|
|
text-align: center;
|
|
|
margin-top: 10px;
|
|
|
font-size: 14px;
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
.progress-text span {
|
|
|
font-weight: 600;
|
|
|
color: #667eea;
|
|
|
}
|
|
|
|
|
|
|
|
|
.how-it-works {
|
|
|
background: white;
|
|
|
border-radius: 16px;
|
|
|
box-shadow: 0 2px 20px rgba(0,0,0,0.08);
|
|
|
padding: 30px 40px;
|
|
|
}
|
|
|
|
|
|
.how-it-works h3 {
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
margin-bottom: 20px;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.how-it-works ol {
|
|
|
list-style: none;
|
|
|
padding: 0;
|
|
|
margin: 0;
|
|
|
}
|
|
|
|
|
|
.how-it-works li {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
gap: 12px;
|
|
|
padding: 10px 0;
|
|
|
color: #555;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.how-it-works .step-num {
|
|
|
width: 24px;
|
|
|
height: 24px;
|
|
|
background: #f0f4ff;
|
|
|
color: #667eea;
|
|
|
border-radius: 50%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
font-size: 12px;
|
|
|
font-weight: 600;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
@media (max-width: 600px) {
|
|
|
.card {
|
|
|
padding: 25px 20px;
|
|
|
}
|
|
|
|
|
|
.quality-buttons {
|
|
|
grid-template-columns: 1fr;
|
|
|
}
|
|
|
|
|
|
.status-banner {
|
|
|
flex-direction: column;
|
|
|
text-align: center;
|
|
|
}
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
|
|
|
<div class="header">
|
|
|
<h1>Create a Personalized Photo Book for <span class="highlight">Your Child</span>!</h1>
|
|
|
<p>AI-powered photo selection to find the best moments</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
<div class="steps">
|
|
|
<div class="step completed">
|
|
|
<div class="step-circle">✓</div>
|
|
|
<div class="step-label">Reference</div>
|
|
|
</div>
|
|
|
<div class="step-connector" style="background: #4CAF50;"></div>
|
|
|
<div class="step active">
|
|
|
<div class="step-circle">2</div>
|
|
|
<div class="step-label">Pre-processing</div>
|
|
|
</div>
|
|
|
<div class="step-connector"></div>
|
|
|
<div class="step">
|
|
|
<div class="step-circle">3</div>
|
|
|
<div class="step-label">Review</div>
|
|
|
</div>
|
|
|
<div class="step-connector"></div>
|
|
|
<div class="step">
|
|
|
<div class="step-circle">4</div>
|
|
|
<div class="step-label">Detecting Favorites</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
{% if reference_count > 0 %}
|
|
|
<div class="status-banner has-ref">
|
|
|
<div class="info">
|
|
|
<span class="icon">✅</span>
|
|
|
<span class="text"><strong>{{ reference_count }} reference photo(s) loaded</strong> - AI will find your child in uploaded photos</span>
|
|
|
</div>
|
|
|
<a href="/step1">Change</a>
|
|
|
</div>
|
|
|
{% else %}
|
|
|
<div class="status-banner no-ref">
|
|
|
<div class="info">
|
|
|
<span class="icon">⚠</span>
|
|
|
<span class="text"><strong>No reference photos</strong> - Processing all photos without face filtering</span>
|
|
|
</div>
|
|
|
<a href="/step1">Add reference photos</a>
|
|
|
</div>
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
<div class="card-icon">
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
|
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5-7l-3 3.72L9 13l-3 4h12l-4-5z"/>
|
|
|
</svg>
|
|
|
</div>
|
|
|
<h2>Upload Your Event Photos</h2>
|
|
|
<p>Upload all photos from the event - we'll find the best ones of your child</p>
|
|
|
|
|
|
|
|
|
<div class="folder-input-area" id="drive-input-area">
|
|
|
<div class="upload-icon" style="font-size: 48px;">☁</div>
|
|
|
<h3>Import from Google Drive</h3>
|
|
|
<p>Paste a Google Drive folder URL (folder must be shared with the service account)</p>
|
|
|
<input type="text" id="drive-folder-url" class="folder-path-input"
|
|
|
placeholder="https://drive.google.com/drive/folders/1AbCdEfGhIjKlMnOpQrS">
|
|
|
<p class="folder-hint">Paste the folder URL from your browser address bar</p>
|
|
|
<div id="drive-preview" style="margin-top: 15px; display: none;">
|
|
|
<div style="background: #e8f5e9; padding: 12px 16px; border-radius: 8px; text-align: left;">
|
|
|
<strong id="drive-folder-name" style="color: #2e7d32;"></strong>
|
|
|
<span id="drive-image-count" style="color: #666; margin-left: 10px;"></span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<button class="btn btn-secondary" id="preview-drive-btn" onclick="previewDriveFolder()" style="margin-top: 15px;">
|
|
|
Preview Folder
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="settings-panel">
|
|
|
<h3>Selection Settings</h3>
|
|
|
<p>Adjust how strict the quality filter should be</p>
|
|
|
|
|
|
<div class="quality-buttons">
|
|
|
<button class="quality-btn" data-mode="lenient" onclick="setQualityMode('lenient')">
|
|
|
<span class="icon">📷</span>
|
|
|
<span class="label">Keep More</span>
|
|
|
<span class="desc">Lower standards, more photos</span>
|
|
|
</button>
|
|
|
<button class="quality-btn active" data-mode="balanced" onclick="setQualityMode('balanced')">
|
|
|
<span class="icon">✨</span>
|
|
|
<span class="label">Balanced</span>
|
|
|
<span class="desc">Recommended setting</span>
|
|
|
</button>
|
|
|
<button class="quality-btn" data-mode="strict" onclick="setQualityMode('strict')">
|
|
|
<span class="icon">🏆</span>
|
|
|
<span class="label">Best Only</span>
|
|
|
<span class="desc">Highest quality only</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="slider-group">
|
|
|
<label>Duplicate Detection</label>
|
|
|
<input type="range" id="similarity" min="0.80" max="0.98" step="0.02" value="0.92">
|
|
|
<div class="slider-labels">
|
|
|
<span>Keep similar</span>
|
|
|
<span class="value" id="similarity-value">92%</span>
|
|
|
<span>Remove similar</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div id="file-preview" class="file-preview hidden">
|
|
|
<div class="file-preview-header">
|
|
|
<h3>Selected Files</h3>
|
|
|
<span class="file-count" id="file-count">0 photos</span>
|
|
|
</div>
|
|
|
<div id="preview-grid" class="preview-grid"></div>
|
|
|
<button class="btn btn-success" id="process-btn" onclick="startProcessing()">
|
|
|
{% if reference_count > 0 %}
|
|
|
Find My Child & Select Best Photos
|
|
|
{% else %}
|
|
|
Select Best Photos
|
|
|
{% endif %}
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div class="how-it-works">
|
|
|
<h3>How it works:</h3>
|
|
|
<ol>
|
|
|
<li><span class="step-num">1</span> Upload all your event/school photos</li>
|
|
|
<li><span class="step-num">2</span> AI scans every photo looking for your child's face</li>
|
|
|
<li><span class="step-num">3</span> Review the matches and confirm which ones to keep</li>
|
|
|
<li><span class="step-num">4</span> AI selects the best quality photos from your selection</li>
|
|
|
<li><span class="step-num">5</span> Download your curated photo collection</li>
|
|
|
</ol>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div id="processing-overlay" class="processing-overlay hidden">
|
|
|
<div class="spinner"></div>
|
|
|
<h2 id="processing-message">Processing your photos...</h2>
|
|
|
<p id="files-checked" style="color: #667eea; font-size: 18px; font-weight: 600; margin-top: 10px;"></p>
|
|
|
<div class="progress-container">
|
|
|
<div class="progress-bar">
|
|
|
<div class="progress-fill" id="progress-fill" style="width: 0%"></div>
|
|
|
</div>
|
|
|
<p class="progress-text"><span id="progress-percent">0</span>%</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
const hasReferences = {{ reference_count }} > 0;
|
|
|
let qualityMode = 'balanced';
|
|
|
let jobId = null;
|
|
|
let driveFolderVerified = false;
|
|
|
|
|
|
function updateDrivePreview() {
|
|
|
const driveUrl = document.getElementById('drive-folder-url').value.trim();
|
|
|
const preview = document.getElementById('file-preview');
|
|
|
|
|
|
if (driveUrl && driveFolderVerified) {
|
|
|
preview.classList.remove('hidden');
|
|
|
} else {
|
|
|
preview.classList.add('hidden');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function previewDriveFolder() {
|
|
|
const driveUrl = document.getElementById('drive-folder-url').value.trim();
|
|
|
if (!driveUrl) {
|
|
|
alert('Please enter a Google Drive folder URL');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const btn = document.getElementById('preview-drive-btn');
|
|
|
btn.textContent = 'Checking...';
|
|
|
btn.disabled = true;
|
|
|
|
|
|
try {
|
|
|
const response = await fetch('/preview_drive_folder', {
|
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({ folder_url: driveUrl })
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
if (data.error) {
|
|
|
alert('Error: ' + data.error);
|
|
|
driveFolderVerified = false;
|
|
|
} else {
|
|
|
|
|
|
document.getElementById('drive-folder-name').textContent = data.folder_name;
|
|
|
document.getElementById('drive-image-count').textContent = `(${data.image_count} images found)`;
|
|
|
document.getElementById('drive-preview').style.display = 'block';
|
|
|
driveFolderVerified = true;
|
|
|
|
|
|
|
|
|
const preview = document.getElementById('file-preview');
|
|
|
preview.classList.remove('hidden');
|
|
|
document.getElementById('file-count').textContent = `${data.image_count} images`;
|
|
|
document.getElementById('preview-grid').innerHTML = '<div style="padding: 20px; color: #666; font-size: 14px;">Ready to import from: ' + data.folder_name + '</div>';
|
|
|
}
|
|
|
} catch (e) {
|
|
|
console.error('Preview error:', e);
|
|
|
alert('Failed to preview folder. Please check the URL.');
|
|
|
driveFolderVerified = false;
|
|
|
} finally {
|
|
|
btn.textContent = 'Preview Folder';
|
|
|
btn.disabled = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
document.getElementById('drive-folder-url').addEventListener('input', function() {
|
|
|
driveFolderVerified = false;
|
|
|
document.getElementById('drive-preview').style.display = 'none';
|
|
|
document.getElementById('file-preview').classList.add('hidden');
|
|
|
});
|
|
|
|
|
|
function setQualityMode(mode) {
|
|
|
qualityMode = mode;
|
|
|
document.querySelectorAll('.quality-btn').forEach(btn => {
|
|
|
btn.classList.toggle('active', btn.dataset.mode === mode);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
document.getElementById('similarity').addEventListener('input', function() {
|
|
|
document.getElementById('similarity-value').textContent = Math.round(this.value * 100) + '%';
|
|
|
});
|
|
|
|
|
|
async function startProcessing() {
|
|
|
|
|
|
document.getElementById('processing-overlay').classList.remove('hidden');
|
|
|
|
|
|
|
|
|
const driveUrl = document.getElementById('drive-folder-url').value.trim();
|
|
|
if (!driveUrl) {
|
|
|
alert('Please enter a Google Drive folder URL');
|
|
|
document.getElementById('processing-overlay').classList.add('hidden');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!driveFolderVerified) {
|
|
|
alert('Please click "Preview Folder" first to verify access');
|
|
|
document.getElementById('processing-overlay').classList.add('hidden');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
document.getElementById('processing-message').textContent = 'Connecting to Google Drive...';
|
|
|
|
|
|
try {
|
|
|
const response = await fetch('/import_from_drive', {
|
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({
|
|
|
folder_url: driveUrl,
|
|
|
quality_mode: qualityMode,
|
|
|
similarity_threshold: parseFloat(document.getElementById('similarity').value)
|
|
|
})
|
|
|
});
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
if (data.error) {
|
|
|
alert('Error: ' + data.error);
|
|
|
document.getElementById('processing-overlay').classList.add('hidden');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
jobId = data.job_id;
|
|
|
pollStatus();
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Error:', error);
|
|
|
alert('Failed to import from Google Drive. Please try again.');
|
|
|
document.getElementById('processing-overlay').classList.add('hidden');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function pollStatus() {
|
|
|
try {
|
|
|
const response = await fetch(`/status/${jobId}`);
|
|
|
const data = await response.json();
|
|
|
|
|
|
document.getElementById('processing-message').textContent = data.message;
|
|
|
document.getElementById('progress-fill').style.width = `${data.progress}%`;
|
|
|
document.getElementById('progress-percent').textContent = data.progress;
|
|
|
|
|
|
|
|
|
const filesCheckedEl = document.getElementById('files-checked');
|
|
|
if (data.total_photos > 0) {
|
|
|
filesCheckedEl.textContent = `${data.photos_checked || 0} / ${data.total_photos} files checked`;
|
|
|
} else {
|
|
|
filesCheckedEl.textContent = '';
|
|
|
}
|
|
|
|
|
|
if (data.status === 'review_pending') {
|
|
|
window.location.href = `/step3_review/${jobId}`;
|
|
|
} else if (data.status === 'complete') {
|
|
|
window.location.href = `/step4_results/${jobId}`;
|
|
|
} else if (data.status === 'error') {
|
|
|
alert('Error: ' + data.message);
|
|
|
document.getElementById('processing-overlay').classList.add('hidden');
|
|
|
} else {
|
|
|
setTimeout(pollStatus, 1000);
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('Poll error:', error);
|
|
|
setTimeout(pollStatus, 2000);
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|
|
|
|