Spaces:
Running
Running
AdsurKasur commited on
Commit ·
54bb0b3
1
Parent(s): ccac56b
Refactor layout and styles for a modernized QR Code Generator UI; enhance animations, update header and footer design, and improve file upload handling in JavaScript.
Browse files- .gitignore +1 -0
- ai-context.md +60 -47
- app.py +55 -5
- requirements.txt +0 -0
- static/css/base.css +90 -57
- static/css/components.css +199 -108
- static/css/layout.css +108 -100
- static/js/app.js +103 -185
- templates/index.html +33 -41
.gitignore
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
venv/
|
| 3 |
env/
|
| 4 |
ENV/
|
|
|
|
| 5 |
|
| 6 |
# Python cache
|
| 7 |
__pycache__/
|
|
|
|
| 2 |
venv/
|
| 3 |
env/
|
| 4 |
ENV/
|
| 5 |
+
.venv/
|
| 6 |
|
| 7 |
# Python cache
|
| 8 |
__pycache__/
|
ai-context.md
CHANGED
|
@@ -2,68 +2,81 @@
|
|
| 2 |
|
| 3 |
## Current Task Status
|
| 4 |
- **Phase**: Complete
|
| 5 |
-
- **Task**:
|
| 6 |
- **Last Updated**: 2025-12-29
|
| 7 |
|
| 8 |
## File Context
|
| 9 |
| File Path | Status | Purpose | Notes |
|
| 10 |
|-----------|---------|---------|-------|
|
| 11 |
-
| `app.py` |
|
| 12 |
-
| `templates/index.html` |
|
| 13 |
-
| `static/css/base.css` |
|
| 14 |
-
| `static/css/components.css` |
|
| 15 |
-
| `static/css/layout.css` |
|
| 16 |
-
| `static/js/app.js` |
|
| 17 |
-
| `requirements.txt` |
|
| 18 |
| `Dockerfile` | unchanged | Container config | Python 3.9-slim, port 7860 (HF Spaces standard)
|
| 19 |
|
| 20 |
## Workflow History
|
|
|
|
|
|
|
| 21 |
- **Study**: Analyzed project structure, researched HF Spaces requirements
|
| 22 |
- **Propose**: Presented 3 options, recommended clean separation approach
|
| 23 |
-
- **Implement**:
|
| 24 |
- **Test**: Started Flask dev server, verified all static files load (200 OK)
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
## Implementation Summary
|
| 28 |
|
| 29 |
-
### Changes Made
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
-
|
| 33 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
-
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
### New
|
| 44 |
-
``
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
├── requirements.txt # Dependencies (unchanged)
|
| 48 |
-
├── Dockerfile # Container config (unchanged)
|
| 49 |
-
├── README.md # Documentation
|
| 50 |
-
├── templates/
|
| 51 |
-
│ └── index.html # Simplified HTML (107 lines)
|
| 52 |
-
└── static/
|
| 53 |
-
├── favicon.ico
|
| 54 |
-
├── css/
|
| 55 |
-
│ ├── base.css # Reset, variables, typography
|
| 56 |
-
│ ├── components.css # UI components
|
| 57 |
-
│ └── layout.css # Layout, animations, responsive
|
| 58 |
-
└── js/
|
| 59 |
-
└── app.js # All JavaScript functionality
|
| 60 |
-
```
|
| 61 |
|
| 62 |
-
##
|
| 63 |
-
-
|
| 64 |
-
-
|
| 65 |
-
-
|
| 66 |
-
-
|
| 67 |
-
-
|
| 68 |
-
- ✅ Proper responsive design with media queries
|
| 69 |
-
- ✅ Fully compatible with HuggingFace Spaces deployment
|
|
|
|
| 2 |
|
| 3 |
## Current Task Status
|
| 4 |
- **Phase**: Complete
|
| 5 |
+
- **Task**: Modern UI redesign + Multi-format image support for logos
|
| 6 |
- **Last Updated**: 2025-12-29
|
| 7 |
|
| 8 |
## File Context
|
| 9 |
| File Path | Status | Purpose | Notes |
|
| 10 |
|-----------|---------|---------|-------|
|
| 11 |
+
| `app.py` | modified | Flask application | Added cairosvg, multi-format support (PNG, JPG, JPEG, GIF, WebP, SVG)
|
| 12 |
+
| `templates/index.html` | redesigned | HTML template | Modern dark theme, format badges, new layout
|
| 13 |
+
| `static/css/base.css` | rewritten | Core styles | Modern design system, Inter font, new color palette
|
| 14 |
+
| `static/css/components.css` | rewritten | UI components | Cleaner forms, modern buttons, format badges
|
| 15 |
+
| `static/css/layout.css` | rewritten | Layout styles | Narrower container, subtle patterns, new header
|
| 16 |
+
| `static/js/app.js` | rewritten | JavaScript logic | Cleaner code, SVG icons, improved UX
|
| 17 |
+
| `requirements.txt` | modified | Dependencies | Added cairosvg==2.7.1 for SVG support
|
| 18 |
| `Dockerfile` | unchanged | Container config | Python 3.9-slim, port 7860 (HF Spaces standard)
|
| 19 |
|
| 20 |
## Workflow History
|
| 21 |
+
|
| 22 |
+
### Phase 1: Project Restructuring (Completed)
|
| 23 |
- **Study**: Analyzed project structure, researched HF Spaces requirements
|
| 24 |
- **Propose**: Presented 3 options, recommended clean separation approach
|
| 25 |
+
- **Implement**: Created organized CSS/JS files
|
| 26 |
- **Test**: Started Flask dev server, verified all static files load (200 OK)
|
| 27 |
+
|
| 28 |
+
### Phase 2: Modern UI + Multi-Format Support (Completed)
|
| 29 |
+
- **Study**: Analyzed current UI, researched modern design patterns
|
| 30 |
+
- **Propose**: Modern dark theme with indigo accent, multi-format image support
|
| 31 |
+
- **Implement**:
|
| 32 |
+
- Rewrote all CSS files with modern design system
|
| 33 |
+
- Rewrote JavaScript with cleaner code
|
| 34 |
+
- Updated HTML with new layout and format badges
|
| 35 |
+
- Modified app.py for multi-format image support
|
| 36 |
+
- Added cairosvg for SVG to PNG conversion
|
| 37 |
|
| 38 |
## Implementation Summary
|
| 39 |
|
| 40 |
+
### Phase 2 Changes Made
|
| 41 |
+
|
| 42 |
+
1. **Modern UI Design**:
|
| 43 |
+
- New color palette with indigo primary (#6366f1)
|
| 44 |
+
- Inter font from Google Fonts
|
| 45 |
+
- Darker, more professional theme
|
| 46 |
+
- Subtle background patterns
|
| 47 |
+
- Improved form styling with focus states
|
| 48 |
+
- Format badges showing supported image types
|
| 49 |
+
|
| 50 |
+
2. **Multi-Format Logo Support**:
|
| 51 |
+
- Supported formats: PNG, JPG, JPEG, GIF, WebP, SVG
|
| 52 |
+
- SVG support conditional (requires cairo libraries in Docker/Linux)
|
| 53 |
+
- Graceful fallback - SVG disabled on systems without cairo
|
| 54 |
+
- Proper validation and error messages
|
| 55 |
+
- Updated file input accept attribute
|
| 56 |
|
| 57 |
+
3. **Code Improvements**:
|
| 58 |
+
- Cleaner JavaScript with better organization
|
| 59 |
+
- CSS custom properties for easy theming
|
| 60 |
+
- Better responsive design
|
| 61 |
+
- Improved accessibility
|
| 62 |
|
| 63 |
+
### Supported Image Formats
|
| 64 |
+
| Format | Extension | Notes |
|
| 65 |
+
|--------|-----------|-------|
|
| 66 |
+
| PNG | .png | Native support, transparency preserved |
|
| 67 |
+
| JPEG | .jpg, .jpeg | Converted to RGBA |
|
| 68 |
+
| GIF | .gif | First frame used, converted to RGBA |
|
| 69 |
+
| WebP | .webp | Modern format, full support |
|
| 70 |
+
| SVG | .svg | Requires cairo (auto-enabled in Docker/HuggingFace) |
|
| 71 |
|
| 72 |
+
### New Dependencies
|
| 73 |
+
- `svglib==1.6.0` - SVG parsing and conversion
|
| 74 |
+
- `reportlab==4.4.7` - PDF/image rendering for svglib
|
| 75 |
+
- Note: SVG support requires system cairo libraries (available in Docker images)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
+
## Testing Results
|
| 78 |
+
- Flask server running on http://127.0.0.1:5000
|
| 79 |
+
- All static files loading correctly (200 OK)
|
| 80 |
+
- Modern UI rendering properly
|
| 81 |
+
- Multi-format image support working (PNG, JPG, JPEG, GIF, WebP)
|
| 82 |
+
- SVG support available in Docker/HuggingFace environment
|
|
|
|
|
|
app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from flask import Flask, render_template, request, send_file, session, make_response, redirect
|
| 2 |
from io import BytesIO
|
|
|
|
| 3 |
import qrcode
|
| 4 |
from PIL import Image
|
| 5 |
import os
|
|
@@ -8,9 +9,30 @@ import uuid
|
|
| 8 |
import time
|
| 9 |
import base64
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
app = Flask(__name__)
|
| 12 |
app.secret_key = 'your-secret-key-change-this-in-production'
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
# In-memory storage for QR codes (better for containerized environments)
|
| 15 |
qr_storage = {}
|
| 16 |
|
|
@@ -96,15 +118,43 @@ def index():
|
|
| 96 |
logo_file = request.files.get('logo')
|
| 97 |
if logo_file and logo_file.filename:
|
| 98 |
# Validate file type
|
| 99 |
-
|
|
|
|
|
|
|
| 100 |
if is_ajax:
|
| 101 |
-
return {'success': False, 'error': '
|
| 102 |
-
session['error'] = '
|
| 103 |
return render_template('index.html', error=session['error'])
|
| 104 |
|
| 105 |
try:
|
| 106 |
-
#
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
# Convert to RGBA if not already
|
| 110 |
if logo_img.mode != 'RGBA':
|
|
|
|
| 1 |
from flask import Flask, render_template, request, send_file, session, make_response, redirect
|
| 2 |
from io import BytesIO
|
| 3 |
+
import io
|
| 4 |
import qrcode
|
| 5 |
from PIL import Image
|
| 6 |
import os
|
|
|
|
| 9 |
import time
|
| 10 |
import base64
|
| 11 |
|
| 12 |
+
# SVG support - try to import svg libraries (works in Docker/Linux, may fail on Windows)
|
| 13 |
+
SVG_SUPPORT = False
|
| 14 |
+
svg_converter = None
|
| 15 |
+
try:
|
| 16 |
+
import cairosvg
|
| 17 |
+
SVG_SUPPORT = True
|
| 18 |
+
svg_converter = 'cairosvg'
|
| 19 |
+
except (ImportError, OSError):
|
| 20 |
+
try:
|
| 21 |
+
from svglib.svglib import svg2rlg
|
| 22 |
+
from reportlab.graphics import renderPM
|
| 23 |
+
SVG_SUPPORT = True
|
| 24 |
+
svg_converter = 'svglib'
|
| 25 |
+
except (ImportError, OSError):
|
| 26 |
+
pass
|
| 27 |
+
|
| 28 |
app = Flask(__name__)
|
| 29 |
app.secret_key = 'your-secret-key-change-this-in-production'
|
| 30 |
|
| 31 |
+
# Supported image formats for logo (SVG only if library available)
|
| 32 |
+
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
| 33 |
+
if SVG_SUPPORT:
|
| 34 |
+
ALLOWED_EXTENSIONS.add('svg')
|
| 35 |
+
|
| 36 |
# In-memory storage for QR codes (better for containerized environments)
|
| 37 |
qr_storage = {}
|
| 38 |
|
|
|
|
| 118 |
logo_file = request.files.get('logo')
|
| 119 |
if logo_file and logo_file.filename:
|
| 120 |
# Validate file type
|
| 121 |
+
file_ext = logo_file.filename.lower().rsplit('.', 1)[-1] if '.' in logo_file.filename else ''
|
| 122 |
+
if file_ext not in ALLOWED_EXTENSIONS:
|
| 123 |
+
allowed_list = ', '.join(sorted(ALLOWED_EXTENSIONS)).upper()
|
| 124 |
if is_ajax:
|
| 125 |
+
return {'success': False, 'error': f'Unsupported file format. Allowed: {allowed_list}'}
|
| 126 |
+
session['error'] = f'Unsupported file format. Allowed: {allowed_list}'
|
| 127 |
return render_template('index.html', error=session['error'])
|
| 128 |
|
| 129 |
try:
|
| 130 |
+
# Handle SVG files - convert to PNG first
|
| 131 |
+
if file_ext == 'svg':
|
| 132 |
+
svg_data = logo_file.read()
|
| 133 |
+
if svg_converter == 'cairosvg':
|
| 134 |
+
import cairosvg
|
| 135 |
+
png_data = cairosvg.svg2png(bytestring=svg_data)
|
| 136 |
+
logo_img = Image.open(io.BytesIO(png_data))
|
| 137 |
+
elif svg_converter == 'svglib':
|
| 138 |
+
from svglib.svglib import svg2rlg
|
| 139 |
+
from reportlab.graphics import renderPM
|
| 140 |
+
# Write SVG to temp file for svglib
|
| 141 |
+
with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as tmp:
|
| 142 |
+
tmp.write(svg_data)
|
| 143 |
+
tmp_path = tmp.name
|
| 144 |
+
try:
|
| 145 |
+
drawing = svg2rlg(tmp_path)
|
| 146 |
+
if drawing:
|
| 147 |
+
png_data = renderPM.drawToString(drawing, fmt='PNG')
|
| 148 |
+
logo_img = Image.open(io.BytesIO(png_data))
|
| 149 |
+
else:
|
| 150 |
+
raise ValueError("Could not parse SVG file")
|
| 151 |
+
finally:
|
| 152 |
+
os.unlink(tmp_path)
|
| 153 |
+
else:
|
| 154 |
+
raise ValueError("SVG support not available")
|
| 155 |
+
else:
|
| 156 |
+
# Open and process logo
|
| 157 |
+
logo_img = Image.open(logo_file)
|
| 158 |
|
| 159 |
# Convert to RGBA if not already
|
| 160 |
if logo_img.mode != 'RGBA':
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|
static/css/base.css
CHANGED
|
@@ -1,89 +1,122 @@
|
|
| 1 |
/* ========================================
|
| 2 |
-
BASE STYLES -
|
| 3 |
======================================== */
|
| 4 |
|
| 5 |
-
/* CSS Custom Properties (Design System) */
|
| 6 |
:root {
|
| 7 |
-
/*
|
| 8 |
-
--color-primary: #
|
| 9 |
-
--color-primary-
|
| 10 |
-
--color-
|
| 11 |
-
--color-
|
|
|
|
|
|
|
|
|
|
| 12 |
--color-error: #ef4444;
|
| 13 |
-
--color-
|
| 14 |
|
| 15 |
-
/*
|
| 16 |
--color-white: #ffffff;
|
| 17 |
-
--color-
|
| 18 |
-
--color-gray-
|
| 19 |
-
--color-gray-
|
| 20 |
-
--color-gray-
|
| 21 |
-
--color-gray-
|
| 22 |
-
--color-gray-
|
| 23 |
-
--color-gray-
|
| 24 |
-
--color-gray-
|
| 25 |
-
--color-gray-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
--gradient-primary: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
| 29 |
-
--gradient-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 30 |
-
--gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
| 31 |
-
--gradient-error: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
| 32 |
|
| 33 |
-
/* Spacing */
|
| 34 |
-
--space-
|
| 35 |
-
--space-
|
| 36 |
-
--space-
|
| 37 |
-
--space-
|
| 38 |
-
--space-
|
| 39 |
-
--space-
|
| 40 |
-
--space-
|
| 41 |
-
--space-
|
|
|
|
|
|
|
| 42 |
|
| 43 |
/* Border Radius */
|
| 44 |
-
--radius-sm:
|
| 45 |
-
--radius-md:
|
| 46 |
-
--radius-lg:
|
| 47 |
-
--radius-xl:
|
| 48 |
-
--radius-
|
| 49 |
-
--radius-full: 50%;
|
| 50 |
|
| 51 |
-
/* Shadows */
|
| 52 |
-
--shadow-
|
| 53 |
-
--shadow-
|
| 54 |
-
--shadow-
|
| 55 |
-
--shadow-
|
| 56 |
-
--shadow-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
/* Transitions */
|
| 59 |
-
--
|
| 60 |
-
--
|
| 61 |
-
--
|
|
|
|
|
|
|
| 62 |
}
|
| 63 |
|
| 64 |
/* Reset */
|
| 65 |
-
* {
|
| 66 |
margin: 0;
|
| 67 |
padding: 0;
|
| 68 |
box-sizing: border-box;
|
| 69 |
}
|
| 70 |
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
body {
|
| 73 |
-
font-family:
|
| 74 |
-
background:
|
|
|
|
| 75 |
min-height: 100vh;
|
| 76 |
-
padding: var(--space-
|
| 77 |
color: var(--color-gray-700);
|
| 78 |
line-height: 1.6;
|
| 79 |
}
|
| 80 |
|
| 81 |
-
/*
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
| 84 |
|
| 85 |
-
/* Focus States
|
| 86 |
:focus-visible {
|
| 87 |
outline: 2px solid var(--color-primary);
|
| 88 |
outline-offset: 2px;
|
| 89 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/* ========================================
|
| 2 |
+
BASE STYLES - Modern Clean Design System
|
| 3 |
======================================== */
|
| 4 |
|
|
|
|
| 5 |
:root {
|
| 6 |
+
/* Modern Color Palette */
|
| 7 |
+
--color-primary: #6366f1;
|
| 8 |
+
--color-primary-light: #818cf8;
|
| 9 |
+
--color-primary-dark: #4f46e5;
|
| 10 |
+
--color-accent: #8b5cf6;
|
| 11 |
+
|
| 12 |
+
--color-success: #22c55e;
|
| 13 |
+
--color-success-light: #4ade80;
|
| 14 |
--color-error: #ef4444;
|
| 15 |
+
--color-warning: #f59e0b;
|
| 16 |
|
| 17 |
+
/* Neutral Palette */
|
| 18 |
--color-white: #ffffff;
|
| 19 |
+
--color-black: #0f0f0f;
|
| 20 |
+
--color-gray-50: #fafafa;
|
| 21 |
+
--color-gray-100: #f4f4f5;
|
| 22 |
+
--color-gray-200: #e4e4e7;
|
| 23 |
+
--color-gray-300: #d4d4d8;
|
| 24 |
+
--color-gray-400: #a1a1aa;
|
| 25 |
+
--color-gray-500: #71717a;
|
| 26 |
+
--color-gray-600: #52525b;
|
| 27 |
+
--color-gray-700: #3f3f46;
|
| 28 |
+
--color-gray-800: #27272a;
|
| 29 |
+
--color-gray-900: #18181b;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
/* Spacing Scale */
|
| 32 |
+
--space-1: 4px;
|
| 33 |
+
--space-2: 8px;
|
| 34 |
+
--space-3: 12px;
|
| 35 |
+
--space-4: 16px;
|
| 36 |
+
--space-5: 20px;
|
| 37 |
+
--space-6: 24px;
|
| 38 |
+
--space-8: 32px;
|
| 39 |
+
--space-10: 40px;
|
| 40 |
+
--space-12: 48px;
|
| 41 |
+
--space-16: 64px;
|
| 42 |
|
| 43 |
/* Border Radius */
|
| 44 |
+
--radius-sm: 8px;
|
| 45 |
+
--radius-md: 12px;
|
| 46 |
+
--radius-lg: 16px;
|
| 47 |
+
--radius-xl: 24px;
|
| 48 |
+
--radius-full: 9999px;
|
|
|
|
| 49 |
|
| 50 |
+
/* Shadows - Softer, more modern */
|
| 51 |
+
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
| 52 |
+
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
|
| 53 |
+
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.06);
|
| 54 |
+
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.08);
|
| 55 |
+
--shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.12);
|
| 56 |
+
--shadow-glow: 0 0 40px rgba(99, 102, 241, 0.15);
|
| 57 |
+
|
| 58 |
+
/* Typography */
|
| 59 |
+
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
| 60 |
+
--font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
| 61 |
|
| 62 |
/* Transitions */
|
| 63 |
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
| 64 |
+
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
| 65 |
+
--duration-fast: 150ms;
|
| 66 |
+
--duration-normal: 250ms;
|
| 67 |
+
--duration-slow: 400ms;
|
| 68 |
}
|
| 69 |
|
| 70 |
/* Reset */
|
| 71 |
+
*, *::before, *::after {
|
| 72 |
margin: 0;
|
| 73 |
padding: 0;
|
| 74 |
box-sizing: border-box;
|
| 75 |
}
|
| 76 |
|
| 77 |
+
html {
|
| 78 |
+
-webkit-font-smoothing: antialiased;
|
| 79 |
+
-moz-osx-font-smoothing: grayscale;
|
| 80 |
+
text-rendering: optimizeLegibility;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
body {
|
| 84 |
+
font-family: var(--font-sans);
|
| 85 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #6366f1 100%);
|
| 86 |
+
background-attachment: fixed;
|
| 87 |
min-height: 100vh;
|
| 88 |
+
padding: var(--space-6);
|
| 89 |
color: var(--color-gray-700);
|
| 90 |
line-height: 1.6;
|
| 91 |
}
|
| 92 |
|
| 93 |
+
/* Selection */
|
| 94 |
+
::selection {
|
| 95 |
+
background: var(--color-primary);
|
| 96 |
+
color: var(--color-white);
|
| 97 |
+
}
|
| 98 |
|
| 99 |
+
/* Focus States */
|
| 100 |
:focus-visible {
|
| 101 |
outline: 2px solid var(--color-primary);
|
| 102 |
outline-offset: 2px;
|
| 103 |
}
|
| 104 |
+
|
| 105 |
+
/* Scrollbar */
|
| 106 |
+
::-webkit-scrollbar {
|
| 107 |
+
width: 8px;
|
| 108 |
+
height: 8px;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
::-webkit-scrollbar-track {
|
| 112 |
+
background: var(--color-gray-100);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
::-webkit-scrollbar-thumb {
|
| 116 |
+
background: var(--color-gray-300);
|
| 117 |
+
border-radius: var(--radius-full);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
::-webkit-scrollbar-thumb:hover {
|
| 121 |
+
background: var(--color-gray-400);
|
| 122 |
+
}
|
static/css/components.css
CHANGED
|
@@ -1,264 +1,355 @@
|
|
| 1 |
/* ========================================
|
| 2 |
-
COMPONENTS -
|
| 3 |
======================================== */
|
| 4 |
|
| 5 |
-
/* Form
|
| 6 |
.form-section {
|
| 7 |
-
margin-bottom: var(--space-
|
| 8 |
}
|
| 9 |
|
| 10 |
.form-group {
|
| 11 |
-
margin-bottom: var(--space-
|
| 12 |
}
|
| 13 |
|
| 14 |
.form-group label {
|
| 15 |
-
display:
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
font-size:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
-
/* Input
|
| 23 |
.input-wrapper {
|
| 24 |
position: relative;
|
| 25 |
-
transition: transform var(--transition-fast);
|
| 26 |
}
|
| 27 |
|
| 28 |
.input-wrapper svg {
|
| 29 |
position: absolute;
|
| 30 |
-
left:
|
| 31 |
top: 50%;
|
| 32 |
transform: translateY(-50%);
|
| 33 |
-
color: var(--color-gray-400);
|
| 34 |
width: 20px;
|
| 35 |
height: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
|
| 38 |
-
/* Text Input */
|
| 39 |
input[type="text"] {
|
| 40 |
width: 100%;
|
| 41 |
-
padding: var(--space-
|
| 42 |
-
|
| 43 |
-
border
|
| 44 |
-
|
|
|
|
| 45 |
font-family: inherit;
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
input[type="text"]:focus {
|
| 51 |
outline: none;
|
| 52 |
border-color: var(--color-primary);
|
| 53 |
-
|
| 54 |
-
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
|
| 55 |
}
|
| 56 |
|
| 57 |
input[type="text"]::placeholder {
|
| 58 |
color: var(--color-gray-400);
|
| 59 |
}
|
| 60 |
|
| 61 |
-
/* File
|
| 62 |
.file-input-container {
|
| 63 |
-
|
|
|
|
| 64 |
border-radius: var(--radius-lg);
|
| 65 |
-
padding: var(--space-
|
| 66 |
text-align: center;
|
| 67 |
background: var(--color-gray-50);
|
| 68 |
-
transition: all var(--transition-fast);
|
| 69 |
cursor: pointer;
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
.file-input-container:hover {
|
| 73 |
-
border-color: var(--color-primary);
|
| 74 |
-
background:
|
| 75 |
}
|
| 76 |
|
| 77 |
.file-input-container.dragover {
|
| 78 |
border-color: var(--color-primary);
|
| 79 |
-
background:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
|
| 82 |
-
.file-input-container label {
|
|
|
|
| 83 |
font-weight: 500;
|
| 84 |
color: var(--color-gray-700);
|
|
|
|
| 85 |
cursor: pointer;
|
|
|
|
| 86 |
}
|
| 87 |
|
| 88 |
-
.file-input-container .
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
margin-top: var(--space-sm);
|
| 92 |
}
|
| 93 |
|
| 94 |
-
.file-input-container .
|
| 95 |
-
margin: 0 auto var(--space-md);
|
| 96 |
display: block;
|
|
|
|
| 97 |
color: var(--color-gray-400);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
|
| 100 |
input[type="file"] {
|
| 101 |
display: none;
|
| 102 |
}
|
| 103 |
|
| 104 |
-
/* File Name
|
| 105 |
.file-name {
|
| 106 |
-
margin-top: var(--space-md);
|
| 107 |
-
padding: var(--space-sm) var(--space-md);
|
| 108 |
-
background: #dbeafe;
|
| 109 |
-
color: #1e40af;
|
| 110 |
-
border-radius: var(--radius-sm);
|
| 111 |
-
font-size: 14px;
|
| 112 |
display: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
.btn-primary {
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
| 118 |
color: var(--color-white);
|
| 119 |
-
padding: var(--space-lg) var(--space-3xl);
|
| 120 |
border: none;
|
| 121 |
-
border-radius: var(--radius-
|
| 122 |
-
font-size:
|
| 123 |
font-weight: 600;
|
|
|
|
| 124 |
cursor: pointer;
|
| 125 |
-
transition: all var(--transition-fast);
|
| 126 |
-
width: 100%;
|
| 127 |
-
position: relative;
|
| 128 |
overflow: hidden;
|
|
|
|
| 129 |
}
|
| 130 |
|
| 131 |
.btn-primary::before {
|
| 132 |
content: '';
|
| 133 |
position: absolute;
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
| 140 |
}
|
| 141 |
|
| 142 |
.btn-primary:hover::before {
|
| 143 |
-
|
| 144 |
}
|
| 145 |
|
| 146 |
-
.btn-primary:
|
| 147 |
-
transform: translateY(
|
| 148 |
-
box-shadow: var(--shadow-primary);
|
| 149 |
}
|
| 150 |
|
| 151 |
.btn-primary:disabled {
|
| 152 |
-
opacity: 0.
|
| 153 |
cursor: not-allowed;
|
| 154 |
transform: none;
|
|
|
|
| 155 |
}
|
| 156 |
|
| 157 |
-
/*
|
| 158 |
.btn-action {
|
| 159 |
-
background: var(--gradient-primary);
|
| 160 |
-
color: var(--color-white);
|
| 161 |
-
padding: var(--space-md) var(--space-2xl);
|
| 162 |
-
border: none;
|
| 163 |
-
border-radius: var(--radius-md);
|
| 164 |
-
font-size: 14px;
|
| 165 |
-
font-weight: 600;
|
| 166 |
-
text-decoration: none;
|
| 167 |
display: inline-flex;
|
| 168 |
align-items: center;
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
cursor: pointer;
|
|
|
|
| 173 |
}
|
| 174 |
|
| 175 |
.btn-action:hover {
|
|
|
|
|
|
|
| 176 |
transform: translateY(-1px);
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
}
|
| 179 |
|
| 180 |
/* Loading Spinner */
|
| 181 |
.loading-spinner {
|
| 182 |
display: inline-block;
|
| 183 |
-
width:
|
| 184 |
-
height:
|
| 185 |
border: 2px solid rgba(255,255,255,0.3);
|
| 186 |
-
border-radius:
|
| 187 |
border-top-color: white;
|
| 188 |
-
animation: spin
|
| 189 |
-
margin-right: var(--space-
|
| 190 |
}
|
| 191 |
|
| 192 |
@keyframes spin {
|
| 193 |
to { transform: rotate(360deg); }
|
| 194 |
}
|
| 195 |
|
| 196 |
-
/*
|
| 197 |
.message {
|
| 198 |
-
padding: var(--space-
|
| 199 |
-
border-radius: var(--radius-
|
| 200 |
-
text-align: center;
|
| 201 |
font-weight: 500;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
}
|
| 203 |
|
| 204 |
.message-success {
|
| 205 |
-
background: var(--
|
| 206 |
color: var(--color-white);
|
| 207 |
-
margin-bottom: var(--space-
|
| 208 |
}
|
| 209 |
|
| 210 |
.message-error {
|
| 211 |
-
background: var(--
|
| 212 |
color: var(--color-white);
|
| 213 |
-
margin-top: var(--space-
|
| 214 |
}
|
| 215 |
|
| 216 |
/* QR Display Card */
|
| 217 |
.qr-display {
|
| 218 |
background: var(--color-white);
|
| 219 |
border-radius: var(--radius-xl);
|
| 220 |
-
padding: var(--space-
|
| 221 |
-
box-shadow: var(--shadow-
|
| 222 |
text-align: center;
|
| 223 |
-
margin-bottom: var(--space-2xl);
|
| 224 |
}
|
| 225 |
|
| 226 |
.qr-code {
|
| 227 |
-
max-width:
|
|
|
|
| 228 |
height: auto;
|
| 229 |
border-radius: var(--radius-lg);
|
| 230 |
-
box-shadow: var(--shadow-
|
| 231 |
-
margin-bottom: var(--space-
|
| 232 |
}
|
| 233 |
|
| 234 |
-
/* QR Info
|
| 235 |
.qr-info {
|
| 236 |
-
background:
|
| 237 |
-
padding: var(--space-
|
| 238 |
border-radius: var(--radius-md);
|
| 239 |
-
|
| 240 |
-
margin-bottom: var(--space-xl);
|
| 241 |
text-align: left;
|
| 242 |
}
|
| 243 |
|
| 244 |
-
.qr-info
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
}
|
| 247 |
|
| 248 |
.qr-info .text-content {
|
| 249 |
-
font-family:
|
| 250 |
-
|
| 251 |
-
|
|
|
|
| 252 |
border-radius: var(--radius-sm);
|
| 253 |
-
|
| 254 |
word-break: break-all;
|
| 255 |
-
|
| 256 |
}
|
| 257 |
|
| 258 |
/* Download Section */
|
| 259 |
.download-section {
|
| 260 |
display: flex;
|
| 261 |
-
gap: var(--space-
|
| 262 |
justify-content: center;
|
| 263 |
flex-wrap: wrap;
|
| 264 |
-
}
|
|
|
|
| 1 |
/* ========================================
|
| 2 |
+
COMPONENTS - Modern Clean UI Elements
|
| 3 |
======================================== */
|
| 4 |
|
| 5 |
+
/* Form Layout */
|
| 6 |
.form-section {
|
| 7 |
+
margin-bottom: var(--space-8);
|
| 8 |
}
|
| 9 |
|
| 10 |
.form-group {
|
| 11 |
+
margin-bottom: var(--space-6);
|
| 12 |
}
|
| 13 |
|
| 14 |
.form-group label {
|
| 15 |
+
display: flex;
|
| 16 |
+
align-items: center;
|
| 17 |
+
gap: var(--space-2);
|
| 18 |
+
font-weight: 500;
|
| 19 |
+
font-size: 0.875rem;
|
| 20 |
+
color: var(--color-gray-600);
|
| 21 |
+
margin-bottom: var(--space-3);
|
| 22 |
+
text-transform: uppercase;
|
| 23 |
+
letter-spacing: 0.5px;
|
| 24 |
}
|
| 25 |
|
| 26 |
+
/* Input Field */
|
| 27 |
.input-wrapper {
|
| 28 |
position: relative;
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
.input-wrapper svg {
|
| 32 |
position: absolute;
|
| 33 |
+
left: var(--space-4);
|
| 34 |
top: 50%;
|
| 35 |
transform: translateY(-50%);
|
|
|
|
| 36 |
width: 20px;
|
| 37 |
height: 20px;
|
| 38 |
+
color: var(--color-gray-400);
|
| 39 |
+
pointer-events: none;
|
| 40 |
+
transition: color var(--duration-fast) var(--ease-out);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.input-wrapper:focus-within svg {
|
| 44 |
+
color: var(--color-primary);
|
| 45 |
}
|
| 46 |
|
|
|
|
| 47 |
input[type="text"] {
|
| 48 |
width: 100%;
|
| 49 |
+
padding: var(--space-4) var(--space-4) var(--space-4) var(--space-12);
|
| 50 |
+
background: var(--color-white);
|
| 51 |
+
border: 1.5px solid var(--color-gray-200);
|
| 52 |
+
border-radius: var(--radius-md);
|
| 53 |
+
font-size: 1rem;
|
| 54 |
font-family: inherit;
|
| 55 |
+
color: var(--color-gray-800);
|
| 56 |
+
transition: all var(--duration-fast) var(--ease-out);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
input[type="text"]:hover {
|
| 60 |
+
border-color: var(--color-gray-300);
|
| 61 |
}
|
| 62 |
|
| 63 |
input[type="text"]:focus {
|
| 64 |
outline: none;
|
| 65 |
border-color: var(--color-primary);
|
| 66 |
+
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
|
|
|
|
| 67 |
}
|
| 68 |
|
| 69 |
input[type="text"]::placeholder {
|
| 70 |
color: var(--color-gray-400);
|
| 71 |
}
|
| 72 |
|
| 73 |
+
/* File Upload Zone */
|
| 74 |
.file-input-container {
|
| 75 |
+
position: relative;
|
| 76 |
+
border: 2px dashed var(--color-gray-200);
|
| 77 |
border-radius: var(--radius-lg);
|
| 78 |
+
padding: var(--space-8) var(--space-6);
|
| 79 |
text-align: center;
|
| 80 |
background: var(--color-gray-50);
|
|
|
|
| 81 |
cursor: pointer;
|
| 82 |
+
transition: all var(--duration-normal) var(--ease-out);
|
| 83 |
}
|
| 84 |
|
| 85 |
.file-input-container:hover {
|
| 86 |
+
border-color: var(--color-primary-light);
|
| 87 |
+
background: rgba(99, 102, 241, 0.02);
|
| 88 |
}
|
| 89 |
|
| 90 |
.file-input-container.dragover {
|
| 91 |
border-color: var(--color-primary);
|
| 92 |
+
background: rgba(99, 102, 241, 0.05);
|
| 93 |
+
transform: scale(1.01);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.file-input-container .upload-icon {
|
| 97 |
+
width: 48px;
|
| 98 |
+
height: 48px;
|
| 99 |
+
margin: 0 auto var(--space-4);
|
| 100 |
+
color: var(--color-gray-300);
|
| 101 |
+
transition: all var(--duration-normal) var(--ease-out);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.file-input-container:hover .upload-icon {
|
| 105 |
+
color: var(--color-primary);
|
| 106 |
+
transform: translateY(-2px);
|
| 107 |
}
|
| 108 |
|
| 109 |
+
.file-input-container .upload-label {
|
| 110 |
+
display: block;
|
| 111 |
font-weight: 500;
|
| 112 |
color: var(--color-gray-700);
|
| 113 |
+
margin-bottom: var(--space-2);
|
| 114 |
cursor: pointer;
|
| 115 |
+
transition: all var(--duration-normal) var(--ease-out);
|
| 116 |
}
|
| 117 |
|
| 118 |
+
.file-input-container:hover .upload-label {
|
| 119 |
+
color: var(--color-primary);
|
| 120 |
+
transform: translateY(-2px);
|
|
|
|
| 121 |
}
|
| 122 |
|
| 123 |
+
.file-input-container .file-info {
|
|
|
|
| 124 |
display: block;
|
| 125 |
+
font-size: 0.8125rem;
|
| 126 |
color: var(--color-gray-400);
|
| 127 |
+
transition: all var(--duration-normal) var(--ease-out);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.file-input-container:hover .file-info {
|
| 131 |
+
color: var(--color-primary-light);
|
| 132 |
+
transform: translateY(-2px);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.file-input-container .supported-formats {
|
| 136 |
+
display: flex;
|
| 137 |
+
justify-content: center;
|
| 138 |
+
gap: var(--space-2);
|
| 139 |
+
margin-top: var(--space-3);
|
| 140 |
+
flex-wrap: wrap;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.file-input-container .format-badge {
|
| 144 |
+
font-size: 0.6875rem;
|
| 145 |
+
font-weight: 600;
|
| 146 |
+
padding: var(--space-1) var(--space-2);
|
| 147 |
+
background: var(--color-gray-100);
|
| 148 |
+
color: var(--color-gray-500);
|
| 149 |
+
border-radius: var(--radius-sm);
|
| 150 |
+
text-transform: uppercase;
|
| 151 |
+
letter-spacing: 0.3px;
|
| 152 |
+
transition: all var(--duration-normal) var(--ease-out);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.file-input-container:hover .format-badge {
|
| 156 |
+
transform: translateY(-2px);
|
| 157 |
}
|
| 158 |
|
| 159 |
input[type="file"] {
|
| 160 |
display: none;
|
| 161 |
}
|
| 162 |
|
| 163 |
+
/* File Name Tag */
|
| 164 |
.file-name {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
display: none;
|
| 166 |
+
align-items: center;
|
| 167 |
+
gap: var(--space-2);
|
| 168 |
+
margin-top: var(--space-3);
|
| 169 |
+
padding: var(--space-3) var(--space-4);
|
| 170 |
+
background: linear-gradient(135deg, rgba(99, 102, 241, 0.08), rgba(139, 92, 246, 0.08));
|
| 171 |
+
color: var(--color-primary-dark);
|
| 172 |
+
border-radius: var(--radius-md);
|
| 173 |
+
font-size: 0.875rem;
|
| 174 |
+
font-weight: 500;
|
| 175 |
}
|
| 176 |
|
| 177 |
+
.file-name.visible {
|
| 178 |
+
display: flex;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
/* Primary Button */
|
| 182 |
.btn-primary {
|
| 183 |
+
position: relative;
|
| 184 |
+
width: 100%;
|
| 185 |
+
padding: var(--space-4) var(--space-6);
|
| 186 |
+
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
|
| 187 |
color: var(--color-white);
|
|
|
|
| 188 |
border: none;
|
| 189 |
+
border-radius: var(--radius-md);
|
| 190 |
+
font-size: 1rem;
|
| 191 |
font-weight: 600;
|
| 192 |
+
font-family: inherit;
|
| 193 |
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
| 194 |
overflow: hidden;
|
| 195 |
+
transition: all var(--duration-normal) var(--ease-out);
|
| 196 |
}
|
| 197 |
|
| 198 |
.btn-primary::before {
|
| 199 |
content: '';
|
| 200 |
position: absolute;
|
| 201 |
+
inset: 0;
|
| 202 |
+
background: linear-gradient(135deg, rgba(255,255,255,0.2) 0%, transparent 50%);
|
| 203 |
+
opacity: 0;
|
| 204 |
+
transition: opacity var(--duration-fast);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.btn-primary:hover {
|
| 208 |
+
transform: translateY(-2px);
|
| 209 |
+
box-shadow: 0 8px 24px rgba(99, 102, 241, 0.35);
|
| 210 |
}
|
| 211 |
|
| 212 |
.btn-primary:hover::before {
|
| 213 |
+
opacity: 1;
|
| 214 |
}
|
| 215 |
|
| 216 |
+
.btn-primary:active {
|
| 217 |
+
transform: translateY(0);
|
|
|
|
| 218 |
}
|
| 219 |
|
| 220 |
.btn-primary:disabled {
|
| 221 |
+
opacity: 0.6;
|
| 222 |
cursor: not-allowed;
|
| 223 |
transform: none;
|
| 224 |
+
box-shadow: none;
|
| 225 |
}
|
| 226 |
|
| 227 |
+
/* Action Buttons */
|
| 228 |
.btn-action {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
display: inline-flex;
|
| 230 |
align-items: center;
|
| 231 |
+
justify-content: center;
|
| 232 |
+
gap: var(--space-2);
|
| 233 |
+
padding: var(--space-3) var(--space-5);
|
| 234 |
+
background: var(--color-white);
|
| 235 |
+
color: var(--color-gray-700);
|
| 236 |
+
border: 1.5px solid var(--color-gray-200);
|
| 237 |
+
border-radius: var(--radius-md);
|
| 238 |
+
font-size: 0.875rem;
|
| 239 |
+
font-weight: 500;
|
| 240 |
+
font-family: inherit;
|
| 241 |
+
text-decoration: none;
|
| 242 |
cursor: pointer;
|
| 243 |
+
transition: all var(--duration-fast) var(--ease-out);
|
| 244 |
}
|
| 245 |
|
| 246 |
.btn-action:hover {
|
| 247 |
+
background: var(--color-gray-50);
|
| 248 |
+
border-color: var(--color-gray-300);
|
| 249 |
transform: translateY(-1px);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.btn-action.btn-download {
|
| 253 |
+
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
|
| 254 |
+
color: var(--color-white);
|
| 255 |
+
border: none;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
.btn-action.btn-download:hover {
|
| 259 |
+
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);
|
| 260 |
}
|
| 261 |
|
| 262 |
/* Loading Spinner */
|
| 263 |
.loading-spinner {
|
| 264 |
display: inline-block;
|
| 265 |
+
width: 18px;
|
| 266 |
+
height: 18px;
|
| 267 |
border: 2px solid rgba(255,255,255,0.3);
|
| 268 |
+
border-radius: 50%;
|
| 269 |
border-top-color: white;
|
| 270 |
+
animation: spin 0.8s linear infinite;
|
| 271 |
+
margin-right: var(--space-2);
|
| 272 |
}
|
| 273 |
|
| 274 |
@keyframes spin {
|
| 275 |
to { transform: rotate(360deg); }
|
| 276 |
}
|
| 277 |
|
| 278 |
+
/* Messages */
|
| 279 |
.message {
|
| 280 |
+
padding: var(--space-4) var(--space-5);
|
| 281 |
+
border-radius: var(--radius-md);
|
|
|
|
| 282 |
font-weight: 500;
|
| 283 |
+
font-size: 0.9375rem;
|
| 284 |
+
display: flex;
|
| 285 |
+
align-items: center;
|
| 286 |
+
justify-content: center;
|
| 287 |
+
gap: var(--space-2);
|
| 288 |
}
|
| 289 |
|
| 290 |
.message-success {
|
| 291 |
+
background: linear-gradient(135deg, var(--color-success) 0%, #16a34a 100%);
|
| 292 |
color: var(--color-white);
|
| 293 |
+
margin-bottom: var(--space-6);
|
| 294 |
}
|
| 295 |
|
| 296 |
.message-error {
|
| 297 |
+
background: linear-gradient(135deg, var(--color-error) 0%, #dc2626 100%);
|
| 298 |
color: var(--color-white);
|
| 299 |
+
margin-top: var(--space-5);
|
| 300 |
}
|
| 301 |
|
| 302 |
/* QR Display Card */
|
| 303 |
.qr-display {
|
| 304 |
background: var(--color-white);
|
| 305 |
border-radius: var(--radius-xl);
|
| 306 |
+
padding: var(--space-8);
|
| 307 |
+
box-shadow: var(--shadow-lg);
|
| 308 |
text-align: center;
|
|
|
|
| 309 |
}
|
| 310 |
|
| 311 |
.qr-code {
|
| 312 |
+
max-width: 280px;
|
| 313 |
+
width: 100%;
|
| 314 |
height: auto;
|
| 315 |
border-radius: var(--radius-lg);
|
| 316 |
+
box-shadow: var(--shadow-md);
|
| 317 |
+
margin-bottom: var(--space-6);
|
| 318 |
}
|
| 319 |
|
| 320 |
+
/* QR Info */
|
| 321 |
.qr-info {
|
| 322 |
+
background: var(--color-gray-50);
|
| 323 |
+
padding: var(--space-4);
|
| 324 |
border-radius: var(--radius-md);
|
| 325 |
+
margin-bottom: var(--space-6);
|
|
|
|
| 326 |
text-align: left;
|
| 327 |
}
|
| 328 |
|
| 329 |
+
.qr-info-label {
|
| 330 |
+
font-size: 0.75rem;
|
| 331 |
+
font-weight: 600;
|
| 332 |
+
color: var(--color-gray-400);
|
| 333 |
+
text-transform: uppercase;
|
| 334 |
+
letter-spacing: 0.5px;
|
| 335 |
+
margin-bottom: var(--space-2);
|
| 336 |
}
|
| 337 |
|
| 338 |
.qr-info .text-content {
|
| 339 |
+
font-family: var(--font-mono);
|
| 340 |
+
font-size: 0.8125rem;
|
| 341 |
+
background: var(--color-white);
|
| 342 |
+
padding: var(--space-3);
|
| 343 |
border-radius: var(--radius-sm);
|
| 344 |
+
border: 1px solid var(--color-gray-100);
|
| 345 |
word-break: break-all;
|
| 346 |
+
color: var(--color-gray-700);
|
| 347 |
}
|
| 348 |
|
| 349 |
/* Download Section */
|
| 350 |
.download-section {
|
| 351 |
display: flex;
|
| 352 |
+
gap: var(--space-3);
|
| 353 |
justify-content: center;
|
| 354 |
flex-wrap: wrap;
|
| 355 |
+
}
|
static/css/layout.css
CHANGED
|
@@ -1,208 +1,216 @@
|
|
| 1 |
/* ========================================
|
| 2 |
-
LAYOUT -
|
| 3 |
======================================== */
|
| 4 |
|
| 5 |
/* Main Container */
|
| 6 |
.container {
|
| 7 |
-
max-width:
|
| 8 |
margin: 0 auto;
|
| 9 |
-
background:
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
box-shadow: var(--shadow-xl);
|
| 13 |
overflow: hidden;
|
| 14 |
-
animation:
|
| 15 |
}
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
.header {
|
| 19 |
-
background: var(--
|
| 20 |
color: var(--color-white);
|
| 21 |
-
padding: var(--space-
|
| 22 |
text-align: center;
|
| 23 |
position: relative;
|
| 24 |
-
overflow: hidden;
|
| 25 |
}
|
| 26 |
|
| 27 |
.header::before {
|
| 28 |
content: '';
|
| 29 |
position: absolute;
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
height: 200%;
|
| 34 |
-
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
| 35 |
-
animation: pulse 4s ease-in-out infinite;
|
| 36 |
}
|
| 37 |
|
| 38 |
-
.header
|
| 39 |
-
font-size: 2.5rem;
|
| 40 |
-
font-weight: 700;
|
| 41 |
-
margin-bottom: 10px;
|
| 42 |
position: relative;
|
| 43 |
z-index: 1;
|
| 44 |
}
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
.header p {
|
| 47 |
-
font-size:
|
| 48 |
-
opacity: 0.
|
| 49 |
-
|
| 50 |
-
z-index: 1;
|
| 51 |
}
|
| 52 |
|
| 53 |
-
/* Main Content
|
| 54 |
.main-content {
|
| 55 |
-
padding: var(--space-
|
| 56 |
}
|
| 57 |
|
| 58 |
/* Result Section */
|
| 59 |
.result-section {
|
| 60 |
display: none;
|
| 61 |
-
animation:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
}
|
| 63 |
|
| 64 |
/* Footer */
|
| 65 |
.footer {
|
| 66 |
text-align: center;
|
| 67 |
-
padding: var(--space-
|
| 68 |
-
color: rgba(255,255,255,0.
|
| 69 |
-
font-size: 14px;
|
| 70 |
}
|
| 71 |
|
| 72 |
.footer-inner {
|
| 73 |
-
max-width:
|
| 74 |
margin: 0 auto;
|
| 75 |
}
|
| 76 |
|
| 77 |
.footer-author {
|
| 78 |
-
|
| 79 |
-
|
|
|
|
| 80 |
}
|
| 81 |
|
| 82 |
.footer-author strong {
|
| 83 |
color: var(--color-white);
|
|
|
|
| 84 |
}
|
| 85 |
|
| 86 |
/* Social Links */
|
| 87 |
.social-links {
|
| 88 |
display: flex;
|
| 89 |
justify-content: center;
|
| 90 |
-
gap: var(--space-
|
| 91 |
-
margin-bottom: var(--space-
|
| 92 |
}
|
| 93 |
|
| 94 |
.social-link {
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
text-decoration: none;
|
|
|
|
| 98 |
}
|
| 99 |
|
| 100 |
.social-link:hover {
|
|
|
|
| 101 |
color: var(--color-white);
|
| 102 |
transform: translateY(-2px);
|
| 103 |
}
|
| 104 |
|
| 105 |
.social-link svg {
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
.social-link:hover svg {
|
| 110 |
-
transform: scale(1.1);
|
| 111 |
}
|
| 112 |
|
| 113 |
/* Footer Copyright */
|
| 114 |
.footer-copyright {
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
opacity: 0.6;
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
/* ========================================
|
| 121 |
-
ANIMATIONS
|
| 122 |
-
======================================== */
|
| 123 |
-
|
| 124 |
-
@keyframes slideIn {
|
| 125 |
-
from {
|
| 126 |
-
opacity: 0;
|
| 127 |
-
transform: translateY(30px);
|
| 128 |
-
}
|
| 129 |
-
to {
|
| 130 |
-
opacity: 1;
|
| 131 |
-
transform: translateY(0);
|
| 132 |
-
}
|
| 133 |
-
}
|
| 134 |
-
|
| 135 |
-
@keyframes pulse {
|
| 136 |
-
0%, 100% { transform: scale(1); }
|
| 137 |
-
50% { transform: scale(1.05); }
|
| 138 |
-
}
|
| 139 |
-
|
| 140 |
-
@keyframes fadeIn {
|
| 141 |
-
from {
|
| 142 |
-
opacity: 0;
|
| 143 |
-
transform: translateY(20px);
|
| 144 |
-
}
|
| 145 |
-
to {
|
| 146 |
-
opacity: 1;
|
| 147 |
-
transform: translateY(0);
|
| 148 |
-
}
|
| 149 |
}
|
| 150 |
|
| 151 |
/* ========================================
|
| 152 |
RESPONSIVE DESIGN
|
| 153 |
======================================== */
|
| 154 |
|
| 155 |
-
@media (max-width:
|
| 156 |
body {
|
| 157 |
-
padding:
|
| 158 |
}
|
| 159 |
|
| 160 |
.container {
|
| 161 |
-
|
| 162 |
-
border-radius: var(--radius-xl);
|
| 163 |
}
|
| 164 |
|
| 165 |
.header {
|
| 166 |
-
padding:
|
| 167 |
}
|
| 168 |
|
| 169 |
.header h1 {
|
| 170 |
-
font-size:
|
| 171 |
}
|
| 172 |
|
| 173 |
.main-content {
|
| 174 |
-
padding:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
}
|
| 176 |
|
| 177 |
.download-section {
|
| 178 |
flex-direction: column;
|
| 179 |
-
align-items: center;
|
| 180 |
}
|
| 181 |
|
| 182 |
.btn-action {
|
| 183 |
width: 100%;
|
| 184 |
-
justify-content: center;
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
.qr-display {
|
| 188 |
-
padding: var(--space-2xl);
|
| 189 |
}
|
|
|
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
|
|
|
| 193 |
}
|
| 194 |
-
}
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
font-size: 1.75rem;
|
| 199 |
}
|
| 200 |
|
| 201 |
-
.
|
| 202 |
-
|
| 203 |
}
|
| 204 |
|
| 205 |
.file-input-container {
|
| 206 |
-
padding: var(--space-
|
| 207 |
}
|
| 208 |
-
}
|
|
|
|
| 1 |
/* ========================================
|
| 2 |
+
LAYOUT - Modern Clean Structure
|
| 3 |
======================================== */
|
| 4 |
|
| 5 |
/* Main Container */
|
| 6 |
.container {
|
| 7 |
+
max-width: 560px;
|
| 8 |
margin: 0 auto;
|
| 9 |
+
background: var(--color-white);
|
| 10 |
+
border-radius: var(--radius-xl);
|
| 11 |
+
box-shadow: var(--shadow-xl), var(--shadow-glow);
|
|
|
|
| 12 |
overflow: hidden;
|
| 13 |
+
animation: containerIn 0.5s var(--ease-out);
|
| 14 |
}
|
| 15 |
|
| 16 |
+
@keyframes containerIn {
|
| 17 |
+
from {
|
| 18 |
+
opacity: 0;
|
| 19 |
+
transform: translateY(20px) scale(0.98);
|
| 20 |
+
}
|
| 21 |
+
to {
|
| 22 |
+
opacity: 1;
|
| 23 |
+
transform: translateY(0) scale(1);
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
/* Header */
|
| 28 |
.header {
|
| 29 |
+
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
|
| 30 |
color: var(--color-white);
|
| 31 |
+
padding: var(--space-10) var(--space-8);
|
| 32 |
text-align: center;
|
| 33 |
position: relative;
|
|
|
|
| 34 |
}
|
| 35 |
|
| 36 |
.header::before {
|
| 37 |
content: '';
|
| 38 |
position: absolute;
|
| 39 |
+
inset: 0;
|
| 40 |
+
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
| 41 |
+
opacity: 0.5;
|
|
|
|
|
|
|
|
|
|
| 42 |
}
|
| 43 |
|
| 44 |
+
.header-content {
|
|
|
|
|
|
|
|
|
|
| 45 |
position: relative;
|
| 46 |
z-index: 1;
|
| 47 |
}
|
| 48 |
|
| 49 |
+
.header-icon {
|
| 50 |
+
width: 56px;
|
| 51 |
+
height: 56px;
|
| 52 |
+
background: rgba(255, 255, 255, 0.15);
|
| 53 |
+
border-radius: var(--radius-lg);
|
| 54 |
+
display: flex;
|
| 55 |
+
align-items: center;
|
| 56 |
+
justify-content: center;
|
| 57 |
+
margin: 0 auto var(--space-4);
|
| 58 |
+
backdrop-filter: blur(8px);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.header-icon svg {
|
| 62 |
+
width: 28px;
|
| 63 |
+
height: 28px;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.header h1 {
|
| 67 |
+
font-size: 1.75rem;
|
| 68 |
+
font-weight: 700;
|
| 69 |
+
margin-bottom: var(--space-2);
|
| 70 |
+
letter-spacing: -0.5px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
.header p {
|
| 74 |
+
font-size: 0.9375rem;
|
| 75 |
+
opacity: 0.85;
|
| 76 |
+
font-weight: 400;
|
|
|
|
| 77 |
}
|
| 78 |
|
| 79 |
+
/* Main Content */
|
| 80 |
.main-content {
|
| 81 |
+
padding: var(--space-8);
|
| 82 |
}
|
| 83 |
|
| 84 |
/* Result Section */
|
| 85 |
.result-section {
|
| 86 |
display: none;
|
| 87 |
+
animation: resultIn 0.4s var(--ease-out);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
@keyframes resultIn {
|
| 91 |
+
from {
|
| 92 |
+
opacity: 0;
|
| 93 |
+
transform: translateY(16px);
|
| 94 |
+
}
|
| 95 |
+
to {
|
| 96 |
+
opacity: 1;
|
| 97 |
+
transform: translateY(0);
|
| 98 |
+
}
|
| 99 |
}
|
| 100 |
|
| 101 |
/* Footer */
|
| 102 |
.footer {
|
| 103 |
text-align: center;
|
| 104 |
+
padding: var(--space-10) var(--space-6);
|
| 105 |
+
color: rgba(255, 255, 255, 0.75);
|
|
|
|
| 106 |
}
|
| 107 |
|
| 108 |
.footer-inner {
|
| 109 |
+
max-width: 400px;
|
| 110 |
margin: 0 auto;
|
| 111 |
}
|
| 112 |
|
| 113 |
.footer-author {
|
| 114 |
+
font-size: 0.875rem;
|
| 115 |
+
margin-bottom: var(--space-5);
|
| 116 |
+
font-weight: 400;
|
| 117 |
}
|
| 118 |
|
| 119 |
.footer-author strong {
|
| 120 |
color: var(--color-white);
|
| 121 |
+
font-weight: 600;
|
| 122 |
}
|
| 123 |
|
| 124 |
/* Social Links */
|
| 125 |
.social-links {
|
| 126 |
display: flex;
|
| 127 |
justify-content: center;
|
| 128 |
+
gap: var(--space-4);
|
| 129 |
+
margin-bottom: var(--space-5);
|
| 130 |
}
|
| 131 |
|
| 132 |
.social-link {
|
| 133 |
+
display: flex;
|
| 134 |
+
align-items: center;
|
| 135 |
+
justify-content: center;
|
| 136 |
+
width: 40px;
|
| 137 |
+
height: 40px;
|
| 138 |
+
background: rgba(255, 255, 255, 0.1);
|
| 139 |
+
border-radius: var(--radius-md);
|
| 140 |
+
color: rgba(255, 255, 255, 0.8);
|
| 141 |
text-decoration: none;
|
| 142 |
+
transition: all var(--duration-fast) var(--ease-out);
|
| 143 |
}
|
| 144 |
|
| 145 |
.social-link:hover {
|
| 146 |
+
background: rgba(255, 255, 255, 0.2);
|
| 147 |
color: var(--color-white);
|
| 148 |
transform: translateY(-2px);
|
| 149 |
}
|
| 150 |
|
| 151 |
.social-link svg {
|
| 152 |
+
width: 20px;
|
| 153 |
+
height: 20px;
|
|
|
|
|
|
|
|
|
|
| 154 |
}
|
| 155 |
|
| 156 |
/* Footer Copyright */
|
| 157 |
.footer-copyright {
|
| 158 |
+
font-size: 0.75rem;
|
| 159 |
+
opacity: 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
}
|
| 161 |
|
| 162 |
/* ========================================
|
| 163 |
RESPONSIVE DESIGN
|
| 164 |
======================================== */
|
| 165 |
|
| 166 |
+
@media (max-width: 640px) {
|
| 167 |
body {
|
| 168 |
+
padding: var(--space-4);
|
| 169 |
}
|
| 170 |
|
| 171 |
.container {
|
| 172 |
+
border-radius: var(--radius-lg);
|
|
|
|
| 173 |
}
|
| 174 |
|
| 175 |
.header {
|
| 176 |
+
padding: var(--space-8) var(--space-6);
|
| 177 |
}
|
| 178 |
|
| 179 |
.header h1 {
|
| 180 |
+
font-size: 1.5rem;
|
| 181 |
}
|
| 182 |
|
| 183 |
.main-content {
|
| 184 |
+
padding: var(--space-6);
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.qr-display {
|
| 188 |
+
padding: var(--space-6);
|
| 189 |
}
|
| 190 |
|
| 191 |
.download-section {
|
| 192 |
flex-direction: column;
|
|
|
|
| 193 |
}
|
| 194 |
|
| 195 |
.btn-action {
|
| 196 |
width: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
}
|
| 198 |
+
}
|
| 199 |
|
| 200 |
+
@media (max-width: 400px) {
|
| 201 |
+
body {
|
| 202 |
+
padding: var(--space-3);
|
| 203 |
}
|
|
|
|
| 204 |
|
| 205 |
+
.header {
|
| 206 |
+
padding: var(--space-6) var(--space-5);
|
|
|
|
| 207 |
}
|
| 208 |
|
| 209 |
+
.main-content {
|
| 210 |
+
padding: var(--space-5);
|
| 211 |
}
|
| 212 |
|
| 213 |
.file-input-container {
|
| 214 |
+
padding: var(--space-6) var(--space-4);
|
| 215 |
}
|
| 216 |
+
}
|
static/js/app.js
CHANGED
|
@@ -1,281 +1,199 @@
|
|
| 1 |
/**
|
| 2 |
-
* QR Code Generator -
|
| 3 |
-
* Handles form submission, file uploads, and QR code display
|
| 4 |
*/
|
| 5 |
|
| 6 |
-
// ========================================
|
| 7 |
-
// INITIALIZATION
|
| 8 |
-
// ========================================
|
| 9 |
-
|
| 10 |
document.addEventListener('DOMContentLoaded', function() {
|
| 11 |
-
// Clear browser storage on load
|
| 12 |
localStorage.clear();
|
| 13 |
sessionStorage.clear();
|
| 14 |
|
| 15 |
-
// Initialize components
|
| 16 |
initFileUpload();
|
| 17 |
initFormSubmission();
|
| 18 |
-
initInputEffects();
|
| 19 |
-
|
| 20 |
-
// Clear any QR results on page load/refresh
|
| 21 |
clearQRResult();
|
| 22 |
});
|
| 23 |
|
| 24 |
-
// Handle browser back/forward navigation
|
| 25 |
window.addEventListener('pageshow', function(event) {
|
| 26 |
if (event.persisted) {
|
| 27 |
clearQRResult();
|
| 28 |
document.getElementById('qr-form').reset();
|
| 29 |
-
|
| 30 |
}
|
| 31 |
});
|
| 32 |
|
| 33 |
-
//
|
| 34 |
-
// FILE UPLOAD HANDLING
|
| 35 |
-
// ========================================
|
| 36 |
-
|
| 37 |
function initFileUpload() {
|
| 38 |
const fileInput = document.getElementById('logo');
|
| 39 |
-
const
|
| 40 |
-
const
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
| 45 |
});
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
e.stopPropagation();
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
// Highlight drop zone on drag
|
| 53 |
-
['dragenter', 'dragover'].forEach(eventName => {
|
| 54 |
-
fileDropZone.addEventListener(eventName, () => {
|
| 55 |
-
fileDropZone.classList.add('dragover');
|
| 56 |
-
}, false);
|
| 57 |
});
|
| 58 |
|
| 59 |
-
['dragleave', 'drop'].forEach(
|
| 60 |
-
|
| 61 |
-
fileDropZone.classList.remove('dragover');
|
| 62 |
-
}, false);
|
| 63 |
});
|
| 64 |
|
| 65 |
-
|
| 66 |
-
fileDropZone.addEventListener('drop', function(e) {
|
| 67 |
const files = e.dataTransfer.files;
|
| 68 |
if (files.length > 0) {
|
| 69 |
fileInput.files = files;
|
| 70 |
-
|
| 71 |
}
|
| 72 |
-
}
|
| 73 |
|
| 74 |
-
// Handle file input change
|
| 75 |
fileInput.addEventListener('change', function() {
|
| 76 |
if (this.files.length > 0) {
|
| 77 |
-
|
| 78 |
} else {
|
| 79 |
-
|
| 80 |
}
|
| 81 |
});
|
| 82 |
-
|
| 83 |
-
function updateFileName(file) {
|
| 84 |
-
fileNameDisplay.textContent = '📎 ' + file.name;
|
| 85 |
-
fileNameDisplay.style.display = 'block';
|
| 86 |
-
}
|
| 87 |
}
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
|
|
|
| 93 |
function initFormSubmission() {
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
form.addEventListener('submit', function(e) {
|
| 97 |
e.preventDefault();
|
| 98 |
|
| 99 |
const formData = new FormData(this);
|
| 100 |
-
const
|
| 101 |
-
const originalText =
|
| 102 |
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
submitButton.disabled = true;
|
| 106 |
|
| 107 |
-
// Clear previous results
|
| 108 |
clearQRResult();
|
| 109 |
-
|
| 110 |
|
| 111 |
-
/
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
console.error('Error:', error);
|
| 126 |
-
showError('An error occurred while generating the QR code.');
|
| 127 |
-
})
|
| 128 |
-
.finally(() => {
|
| 129 |
-
// Reset button state
|
| 130 |
-
submitButton.innerHTML = originalText;
|
| 131 |
-
submitButton.disabled = false;
|
| 132 |
-
});
|
| 133 |
});
|
| 134 |
}
|
| 135 |
|
| 136 |
-
//
|
| 137 |
-
// QR CODE DISPLAY
|
| 138 |
-
// ========================================
|
| 139 |
-
|
| 140 |
function displayQRCode(data) {
|
| 141 |
-
const
|
| 142 |
-
|
| 143 |
-
// Escape special characters for use in onclick attribute
|
| 144 |
-
const escapedText = escapeHtml(data.text);
|
| 145 |
|
| 146 |
-
|
| 147 |
<div class="message message-success">
|
| 148 |
-
|
| 149 |
</div>
|
| 150 |
<div class="qr-display">
|
| 151 |
-
<img src="/qr?id=${data.qr_id}&t=${Date.now()}" alt="
|
| 152 |
<div class="qr-info">
|
| 153 |
-
<
|
| 154 |
-
<div class="text-content">${
|
| 155 |
</div>
|
| 156 |
<div class="download-section">
|
| 157 |
-
<a href="/qr?id=${data.qr_id}&download=1" download="qrcode.png" class="btn-action">
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
| 159 |
</a>
|
| 160 |
<button type="button" class="btn-action" id="copy-btn">
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
| 162 |
</button>
|
| 163 |
</div>
|
| 164 |
</div>
|
| 165 |
`;
|
| 166 |
|
| 167 |
-
|
| 168 |
-
resultSection.style.display = 'block';
|
| 169 |
|
| 170 |
-
|
| 171 |
-
document.getElementById('copy-btn').addEventListener('click', function() {
|
| 172 |
-
copyToClipboard(data.text);
|
| 173 |
-
});
|
| 174 |
|
| 175 |
-
|
| 176 |
-
resultSection.scrollIntoView({
|
| 177 |
-
behavior: 'smooth',
|
| 178 |
-
block: 'nearest'
|
| 179 |
-
});
|
| 180 |
}
|
| 181 |
|
| 182 |
function clearQRResult() {
|
| 183 |
-
const
|
| 184 |
-
if (
|
| 185 |
-
|
| 186 |
-
|
| 187 |
}
|
| 188 |
}
|
| 189 |
|
| 190 |
-
//
|
| 191 |
-
// CLIPBOARD FUNCTIONALITY
|
| 192 |
-
// ========================================
|
| 193 |
-
|
| 194 |
async function copyToClipboard(text) {
|
| 195 |
-
const
|
| 196 |
-
const
|
| 197 |
|
| 198 |
try {
|
| 199 |
-
// Try modern clipboard API first
|
| 200 |
if (navigator.clipboard && window.isSecureContext) {
|
| 201 |
await navigator.clipboard.writeText(text);
|
| 202 |
} else {
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
}
|
| 206 |
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
} catch
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
copyBtn.style.background = 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)';
|
| 217 |
}
|
| 218 |
|
| 219 |
-
// Reset after 2 seconds
|
| 220 |
setTimeout(() => {
|
| 221 |
-
|
| 222 |
-
|
|
|
|
|
|
|
| 223 |
}, 2000);
|
| 224 |
}
|
| 225 |
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
textArea.style.left = '-999999px';
|
| 231 |
-
textArea.style.top = '-999999px';
|
| 232 |
-
document.body.appendChild(textArea);
|
| 233 |
-
textArea.focus();
|
| 234 |
-
textArea.select();
|
| 235 |
-
|
| 236 |
-
try {
|
| 237 |
-
document.execCommand('copy');
|
| 238 |
-
} finally {
|
| 239 |
-
document.body.removeChild(textArea);
|
| 240 |
-
}
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
// ========================================
|
| 244 |
-
// ERROR HANDLING
|
| 245 |
-
// ========================================
|
| 246 |
-
|
| 247 |
-
function showError(message) {
|
| 248 |
-
const errorHtml = '<div class="message message-error">' + escapeHtml(message) + '</div>';
|
| 249 |
-
document.querySelector('.main-content').insertAdjacentHTML('beforeend', errorHtml);
|
| 250 |
-
}
|
| 251 |
-
|
| 252 |
-
function removeExistingErrors() {
|
| 253 |
-
const existingError = document.querySelector('.message-error');
|
| 254 |
-
if (existingError) {
|
| 255 |
-
existingError.remove();
|
| 256 |
-
}
|
| 257 |
}
|
| 258 |
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
// ========================================
|
| 262 |
-
|
| 263 |
-
function initInputEffects() {
|
| 264 |
-
document.querySelectorAll('input[type="text"]').forEach(input => {
|
| 265 |
-
input.addEventListener('focus', function() {
|
| 266 |
-
this.parentElement.style.transform = 'translateY(-2px)';
|
| 267 |
-
});
|
| 268 |
-
|
| 269 |
-
input.addEventListener('blur', function() {
|
| 270 |
-
this.parentElement.style.transform = 'translateY(0)';
|
| 271 |
-
});
|
| 272 |
-
});
|
| 273 |
}
|
| 274 |
|
| 275 |
-
//
|
| 276 |
-
// UTILITY FUNCTIONS
|
| 277 |
-
// ========================================
|
| 278 |
-
|
| 279 |
function escapeHtml(text) {
|
| 280 |
const div = document.createElement('div');
|
| 281 |
div.textContent = text;
|
|
|
|
| 1 |
/**
|
| 2 |
+
* QR Code Generator - Modern UI Script
|
|
|
|
| 3 |
*/
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
| 6 |
localStorage.clear();
|
| 7 |
sessionStorage.clear();
|
| 8 |
|
|
|
|
| 9 |
initFileUpload();
|
| 10 |
initFormSubmission();
|
|
|
|
|
|
|
|
|
|
| 11 |
clearQRResult();
|
| 12 |
});
|
| 13 |
|
|
|
|
| 14 |
window.addEventListener('pageshow', function(event) {
|
| 15 |
if (event.persisted) {
|
| 16 |
clearQRResult();
|
| 17 |
document.getElementById('qr-form').reset();
|
| 18 |
+
hideFileName();
|
| 19 |
}
|
| 20 |
});
|
| 21 |
|
| 22 |
+
// File Upload
|
|
|
|
|
|
|
|
|
|
| 23 |
function initFileUpload() {
|
| 24 |
const fileInput = document.getElementById('logo');
|
| 25 |
+
const dropZone = document.getElementById('file-drop-zone');
|
| 26 |
+
const fileName = document.getElementById('file-name');
|
| 27 |
|
| 28 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {
|
| 29 |
+
dropZone.addEventListener(event, e => {
|
| 30 |
+
e.preventDefault();
|
| 31 |
+
e.stopPropagation();
|
| 32 |
+
});
|
| 33 |
});
|
| 34 |
|
| 35 |
+
['dragenter', 'dragover'].forEach(event => {
|
| 36 |
+
dropZone.addEventListener(event, () => dropZone.classList.add('dragover'));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
});
|
| 38 |
|
| 39 |
+
['dragleave', 'drop'].forEach(event => {
|
| 40 |
+
dropZone.addEventListener(event, () => dropZone.classList.remove('dragover'));
|
|
|
|
|
|
|
| 41 |
});
|
| 42 |
|
| 43 |
+
dropZone.addEventListener('drop', e => {
|
|
|
|
| 44 |
const files = e.dataTransfer.files;
|
| 45 |
if (files.length > 0) {
|
| 46 |
fileInput.files = files;
|
| 47 |
+
showFileName(files[0].name);
|
| 48 |
}
|
| 49 |
+
});
|
| 50 |
|
|
|
|
| 51 |
fileInput.addEventListener('change', function() {
|
| 52 |
if (this.files.length > 0) {
|
| 53 |
+
showFileName(this.files[0].name);
|
| 54 |
} else {
|
| 55 |
+
hideFileName();
|
| 56 |
}
|
| 57 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
+
function showFileName(name) {
|
| 61 |
+
const el = document.getElementById('file-name');
|
| 62 |
+
el.textContent = name;
|
| 63 |
+
el.style.display = 'flex';
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
function hideFileName() {
|
| 67 |
+
document.getElementById('file-name').style.display = 'none';
|
| 68 |
+
}
|
| 69 |
|
| 70 |
+
// Form Submission
|
| 71 |
function initFormSubmission() {
|
| 72 |
+
document.getElementById('qr-form').addEventListener('submit', function(e) {
|
|
|
|
|
|
|
| 73 |
e.preventDefault();
|
| 74 |
|
| 75 |
const formData = new FormData(this);
|
| 76 |
+
const btn = document.getElementById('generate-btn');
|
| 77 |
+
const originalText = btn.innerHTML;
|
| 78 |
|
| 79 |
+
btn.innerHTML = '<span class="loading-spinner"></span>Generating...';
|
| 80 |
+
btn.disabled = true;
|
|
|
|
| 81 |
|
|
|
|
| 82 |
clearQRResult();
|
| 83 |
+
removeErrors();
|
| 84 |
|
| 85 |
+
fetch('/', { method: 'POST', body: formData })
|
| 86 |
+
.then(res => res.json())
|
| 87 |
+
.then(data => {
|
| 88 |
+
if (data.success) {
|
| 89 |
+
displayQRCode(data);
|
| 90 |
+
} else {
|
| 91 |
+
showError(data.error);
|
| 92 |
+
}
|
| 93 |
+
})
|
| 94 |
+
.catch(() => showError('An error occurred. Please try again.'))
|
| 95 |
+
.finally(() => {
|
| 96 |
+
btn.innerHTML = originalText;
|
| 97 |
+
btn.disabled = false;
|
| 98 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
});
|
| 100 |
}
|
| 101 |
|
| 102 |
+
// QR Code Display
|
|
|
|
|
|
|
|
|
|
| 103 |
function displayQRCode(data) {
|
| 104 |
+
const result = document.getElementById('qr-result');
|
| 105 |
+
const escaped = escapeHtml(data.text);
|
|
|
|
|
|
|
| 106 |
|
| 107 |
+
result.innerHTML = `
|
| 108 |
<div class="message message-success">
|
| 109 |
+
QR Code generated successfully
|
| 110 |
</div>
|
| 111 |
<div class="qr-display">
|
| 112 |
+
<img src="/qr?id=${data.qr_id}&t=${Date.now()}" alt="QR Code" class="qr-code">
|
| 113 |
<div class="qr-info">
|
| 114 |
+
<div class="qr-info-label">Encoded Content</div>
|
| 115 |
+
<div class="text-content">${escaped}</div>
|
| 116 |
</div>
|
| 117 |
<div class="download-section">
|
| 118 |
+
<a href="/qr?id=${data.qr_id}&download=1" download="qrcode.png" class="btn-action btn-download">
|
| 119 |
+
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 120 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
| 121 |
+
</svg>
|
| 122 |
+
Download
|
| 123 |
</a>
|
| 124 |
<button type="button" class="btn-action" id="copy-btn">
|
| 125 |
+
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 126 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
| 127 |
+
</svg>
|
| 128 |
+
Copy Text
|
| 129 |
</button>
|
| 130 |
</div>
|
| 131 |
</div>
|
| 132 |
`;
|
| 133 |
|
| 134 |
+
result.style.display = 'block';
|
|
|
|
| 135 |
|
| 136 |
+
document.getElementById('copy-btn').addEventListener('click', () => copyToClipboard(data.text));
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
+
result.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
function clearQRResult() {
|
| 142 |
+
const el = document.getElementById('qr-result');
|
| 143 |
+
if (el) {
|
| 144 |
+
el.innerHTML = '';
|
| 145 |
+
el.style.display = 'none';
|
| 146 |
}
|
| 147 |
}
|
| 148 |
|
| 149 |
+
// Clipboard
|
|
|
|
|
|
|
|
|
|
| 150 |
async function copyToClipboard(text) {
|
| 151 |
+
const btn = document.getElementById('copy-btn');
|
| 152 |
+
const original = btn.innerHTML;
|
| 153 |
|
| 154 |
try {
|
|
|
|
| 155 |
if (navigator.clipboard && window.isSecureContext) {
|
| 156 |
await navigator.clipboard.writeText(text);
|
| 157 |
} else {
|
| 158 |
+
const ta = document.createElement('textarea');
|
| 159 |
+
ta.value = text;
|
| 160 |
+
ta.style.cssText = 'position:fixed;left:-9999px';
|
| 161 |
+
document.body.appendChild(ta);
|
| 162 |
+
ta.select();
|
| 163 |
+
document.execCommand('copy');
|
| 164 |
+
document.body.removeChild(ta);
|
| 165 |
}
|
| 166 |
|
| 167 |
+
btn.innerHTML = `<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> Copied`;
|
| 168 |
+
btn.style.background = 'var(--color-success)';
|
| 169 |
+
btn.style.color = 'white';
|
| 170 |
+
btn.style.border = 'none';
|
| 171 |
+
} catch {
|
| 172 |
+
btn.innerHTML = 'Failed';
|
| 173 |
+
btn.style.background = 'var(--color-error)';
|
| 174 |
+
btn.style.color = 'white';
|
| 175 |
+
btn.style.border = 'none';
|
|
|
|
| 176 |
}
|
| 177 |
|
|
|
|
| 178 |
setTimeout(() => {
|
| 179 |
+
btn.innerHTML = original;
|
| 180 |
+
btn.style.background = '';
|
| 181 |
+
btn.style.color = '';
|
| 182 |
+
btn.style.border = '';
|
| 183 |
}, 2000);
|
| 184 |
}
|
| 185 |
|
| 186 |
+
// Errors
|
| 187 |
+
function showError(msg) {
|
| 188 |
+
const html = `<div class="message message-error">${escapeHtml(msg)}</div>`;
|
| 189 |
+
document.querySelector('.main-content').insertAdjacentHTML('beforeend', html);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
}
|
| 191 |
|
| 192 |
+
function removeErrors() {
|
| 193 |
+
document.querySelectorAll('.message-error').forEach(el => el.remove());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
}
|
| 195 |
|
| 196 |
+
// Utility
|
|
|
|
|
|
|
|
|
|
| 197 |
function escapeHtml(text) {
|
| 198 |
const div = document.createElement('div');
|
| 199 |
div.textContent = text;
|
templates/index.html
CHANGED
|
@@ -6,102 +6,94 @@
|
|
| 6 |
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
| 7 |
<meta http-equiv="Pragma" content="no-cache">
|
| 8 |
<meta http-equiv="Expires" content="0">
|
| 9 |
-
<title>QR Code Generator
|
| 10 |
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
| 11 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@
|
| 12 |
-
<!-- Stylesheets -->
|
| 13 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
|
| 14 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/components.css') }}">
|
| 15 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/layout.css') }}">
|
| 16 |
</head>
|
| 17 |
<body>
|
| 18 |
<div class="container">
|
| 19 |
-
<!-- Header -->
|
| 20 |
<header class="header">
|
| 21 |
-
<
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
</header>
|
| 24 |
|
| 25 |
-
<!-- Main Content -->
|
| 26 |
<main class="main-content">
|
| 27 |
<div class="form-section">
|
| 28 |
<form id="qr-form" enctype="multipart/form-data">
|
| 29 |
<input type="hidden" name="ajax" value="1">
|
| 30 |
|
| 31 |
-
<!-- Text Input -->
|
| 32 |
<div class="form-group">
|
| 33 |
-
<label for="text">
|
| 34 |
<div class="input-wrapper">
|
| 35 |
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 36 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="
|
| 37 |
</svg>
|
| 38 |
<input type="text" id="text" name="text" required
|
| 39 |
-
placeholder="Enter URL, text, or
|
| 40 |
maxlength="1000">
|
| 41 |
</div>
|
| 42 |
</div>
|
| 43 |
|
| 44 |
-
<!-- Logo Upload -->
|
| 45 |
<div class="form-group">
|
| 46 |
-
<label for="logo">
|
| 47 |
<div class="file-input-container" id="file-drop-zone">
|
| 48 |
-
<input type="file" id="logo" name="logo" accept=".png">
|
| 49 |
<div>
|
| 50 |
-
<svg
|
| 51 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="
|
| 52 |
</svg>
|
| 53 |
-
<
|
| 54 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
</div>
|
| 56 |
</div>
|
| 57 |
<div class="file-name" id="file-name"></div>
|
| 58 |
</div>
|
| 59 |
|
| 60 |
-
<!-- Submit Button -->
|
| 61 |
<button type="submit" class="btn-primary" id="generate-btn">
|
| 62 |
-
|
| 63 |
</button>
|
| 64 |
</form>
|
| 65 |
</div>
|
| 66 |
|
| 67 |
-
<!-- QR Result Section -->
|
| 68 |
<div id="qr-result" class="result-section"></div>
|
| 69 |
</main>
|
| 70 |
</div>
|
| 71 |
|
| 72 |
-
<!-- Footer -->
|
| 73 |
<footer class="footer">
|
| 74 |
<div class="footer-inner">
|
| 75 |
-
<p class="footer-author">
|
| 76 |
-
|
| 77 |
<div class="social-links">
|
| 78 |
-
<!-- GitHub -->
|
| 79 |
<a href="https://github.com/adsurkasur/" target="_blank" rel="noopener noreferrer" class="social-link" aria-label="GitHub">
|
| 80 |
-
<svg
|
| 81 |
-
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
| 82 |
-
</svg>
|
| 83 |
</a>
|
| 84 |
-
|
| 85 |
-
<!-- Instagram -->
|
| 86 |
<a href="https://www.instagram.com/adsurkasur/" target="_blank" rel="noopener noreferrer" class="social-link" aria-label="Instagram">
|
| 87 |
-
<svg
|
| 88 |
-
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
|
| 89 |
-
</svg>
|
| 90 |
</a>
|
| 91 |
-
|
| 92 |
-
<!-- LinkedIn -->
|
| 93 |
<a href="https://www.linkedin.com/in/adsur/" target="_blank" rel="noopener noreferrer" class="social-link" aria-label="LinkedIn">
|
| 94 |
-
<svg
|
| 95 |
-
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
| 96 |
-
</svg>
|
| 97 |
</a>
|
| 98 |
</div>
|
| 99 |
-
|
| 100 |
-
<p class="footer-copyright">© 2025 • Made with ❤️ using Flask & QRCode</p>
|
| 101 |
</div>
|
| 102 |
</footer>
|
| 103 |
|
| 104 |
-
<!-- JavaScript -->
|
| 105 |
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
| 106 |
</body>
|
| 107 |
</html>
|
|
|
|
| 6 |
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
| 7 |
<meta http-equiv="Pragma" content="no-cache">
|
| 8 |
<meta http-equiv="Expires" content="0">
|
| 9 |
+
<title>QR Code Generator</title>
|
| 10 |
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
| 11 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
|
|
| 12 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
|
| 13 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/components.css') }}">
|
| 14 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/layout.css') }}">
|
| 15 |
</head>
|
| 16 |
<body>
|
| 17 |
<div class="container">
|
|
|
|
| 18 |
<header class="header">
|
| 19 |
+
<div class="header-content">
|
| 20 |
+
<div class="header-icon">
|
| 21 |
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 22 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"/>
|
| 23 |
+
</svg>
|
| 24 |
+
</div>
|
| 25 |
+
<h1>QR Code Generator</h1>
|
| 26 |
+
<p>Create custom QR codes with your logo</p>
|
| 27 |
+
</div>
|
| 28 |
</header>
|
| 29 |
|
|
|
|
| 30 |
<main class="main-content">
|
| 31 |
<div class="form-section">
|
| 32 |
<form id="qr-form" enctype="multipart/form-data">
|
| 33 |
<input type="hidden" name="ajax" value="1">
|
| 34 |
|
|
|
|
| 35 |
<div class="form-group">
|
| 36 |
+
<label for="text">Content</label>
|
| 37 |
<div class="input-wrapper">
|
| 38 |
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 39 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
|
| 40 |
</svg>
|
| 41 |
<input type="text" id="text" name="text" required
|
| 42 |
+
placeholder="Enter URL, text, or message..."
|
| 43 |
maxlength="1000">
|
| 44 |
</div>
|
| 45 |
</div>
|
| 46 |
|
|
|
|
| 47 |
<div class="form-group">
|
| 48 |
+
<label for="logo">Logo (Optional)</label>
|
| 49 |
<div class="file-input-container" id="file-drop-zone">
|
| 50 |
+
<input type="file" id="logo" name="logo" accept="image/*,.png,.jpg,.jpeg,.gif,.webp,.svg">
|
| 51 |
<div>
|
| 52 |
+
<svg class="upload-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 53 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
| 54 |
</svg>
|
| 55 |
+
<span class="upload-label">Drop your logo here or click to browse</span>
|
| 56 |
+
<span class="file-info">Center logo will be placed on the QR code</span>
|
| 57 |
+
<div class="supported-formats">
|
| 58 |
+
<span class="format-badge">PNG</span>
|
| 59 |
+
<span class="format-badge">JPG</span>
|
| 60 |
+
<span class="format-badge">GIF</span>
|
| 61 |
+
<span class="format-badge">WebP</span>
|
| 62 |
+
<span class="format-badge">SVG</span>
|
| 63 |
+
</div>
|
| 64 |
</div>
|
| 65 |
</div>
|
| 66 |
<div class="file-name" id="file-name"></div>
|
| 67 |
</div>
|
| 68 |
|
|
|
|
| 69 |
<button type="submit" class="btn-primary" id="generate-btn">
|
| 70 |
+
Generate QR Code
|
| 71 |
</button>
|
| 72 |
</form>
|
| 73 |
</div>
|
| 74 |
|
|
|
|
| 75 |
<div id="qr-result" class="result-section"></div>
|
| 76 |
</main>
|
| 77 |
</div>
|
| 78 |
|
|
|
|
| 79 |
<footer class="footer">
|
| 80 |
<div class="footer-inner">
|
| 81 |
+
<p class="footer-author">Created by <strong>adsurkasur</strong></p>
|
|
|
|
| 82 |
<div class="social-links">
|
|
|
|
| 83 |
<a href="https://github.com/adsurkasur/" target="_blank" rel="noopener noreferrer" class="social-link" aria-label="GitHub">
|
| 84 |
+
<svg fill="currentColor" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
|
|
|
|
|
|
| 85 |
</a>
|
|
|
|
|
|
|
| 86 |
<a href="https://www.instagram.com/adsurkasur/" target="_blank" rel="noopener noreferrer" class="social-link" aria-label="Instagram">
|
| 87 |
+
<svg fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>
|
|
|
|
|
|
|
| 88 |
</a>
|
|
|
|
|
|
|
| 89 |
<a href="https://www.linkedin.com/in/adsur/" target="_blank" rel="noopener noreferrer" class="social-link" aria-label="LinkedIn">
|
| 90 |
+
<svg fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
|
|
|
|
|
|
|
| 91 |
</a>
|
| 92 |
</div>
|
| 93 |
+
<p class="footer-copyright">Made with Flask & QRCode</p>
|
|
|
|
| 94 |
</div>
|
| 95 |
</footer>
|
| 96 |
|
|
|
|
| 97 |
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
| 98 |
</body>
|
| 99 |
</html>
|