typeAI / ui /index.html
NeerajNotfound's picture
Upload 88 files
e47c691 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Type.ai - Handwriting to Font</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;
}
:root {
--bg-dark: #0f0f1a;
--bg-card: #1a1a2e;
--bg-hover: #252542;
--accent: #7c3aed;
--accent-light: #a78bfa;
--accent-glow: rgba(124, 58, 237, 0.3);
--text-primary: #ffffff;
--text-secondary: #a1a1aa;
--success: #10b981;
--error: #ef4444;
--border: #2d2d4a;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg-dark);
color: var(--text-primary);
min-height: 100vh;
line-height: 1.6;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 2rem;
}
/* Header */
header {
text-align: center;
padding: 3rem 0;
}
header h1 {
font-size: 3rem;
font-weight: 700;
background: linear-gradient(135deg, var(--accent-light) 0%, var(--accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
header p {
color: var(--text-secondary);
font-size: 1.1rem;
}
/* Upload Zone */
.upload-zone {
background: var(--bg-card);
border: 2px dashed var(--border);
border-radius: 16px;
padding: 4rem 2rem;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
margin-bottom: 2rem;
}
.upload-zone:hover, .upload-zone.dragover {
border-color: var(--accent);
background: var(--bg-hover);
box-shadow: 0 0 30px var(--accent-glow);
}
.upload-zone svg {
width: 64px;
height: 64px;
margin-bottom: 1rem;
color: var(--accent-light);
}
.upload-zone h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.upload-zone p {
color: var(--text-secondary);
font-size: 0.95rem;
}
#file-input {
display: none;
}
/* Font Name Input */
.font-name-section {
background: var(--bg-card);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.font-name-section label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.font-name-section input {
width: 100%;
padding: 0.8rem 1rem;
background: var(--bg-dark);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 1rem;
outline: none;
transition: border-color 0.3s;
}
.font-name-section input:focus {
border-color: var(--accent);
}
/* Processing Status */
.status-card {
background: var(--bg-card);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
display: none;
}
.status-card.visible {
display: block;
}
.status-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.status-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.status-icon.processing {
background: var(--accent-glow);
animation: pulse 2s infinite;
}
.status-icon.completed {
background: rgba(16, 185, 129, 0.2);
}
.status-icon.failed {
background: rgba(239, 68, 68, 0.2);
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
}
.progress-bar {
height: 8px;
background: var(--bg-dark);
border-radius: 4px;
overflow: hidden;
margin: 1rem 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent) 0%, var(--accent-light) 100%);
border-radius: 4px;
transition: width 0.5s ease;
}
/* Preview Section */
.preview-section {
background: var(--bg-card);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
display: none;
}
.preview-section.visible {
display: block;
}
.preview-section h3 {
margin-bottom: 1rem;
}
.preview-image {
width: 100%;
max-height: 400px;
object-fit: contain;
border-radius: 8px;
background: var(--bg-dark);
}
.characters-grid {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 1rem;
}
.char-badge {
padding: 0.3rem 0.8rem;
background: var(--bg-hover);
border-radius: 6px;
font-family: monospace;
font-size: 1rem;
}
/* Download Button */
.download-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 2rem;
background: linear-gradient(135deg, var(--accent) 0%, #6d28d9 100%);
color: white;
border: none;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
}
.download-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px var(--accent-glow);
}
.download-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* Test Font Section */
.test-font-section {
background: var(--bg-card);
border-radius: 16px;
padding: 2rem;
margin-top: 2rem;
display: none;
}
.test-font-section.visible {
display: block;
}
.test-font-section h3 {
margin-bottom: 1rem;
}
.font-preview {
padding: 1.5rem;
background: white;
color: black;
border-radius: 8px;
font-size: 1.5rem;
line-height: 1.8;
}
/* Footer */
footer {
text-align: center;
padding: 2rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Type.ai</h1>
<p>Convert your handwriting into a custom font</p>
</header>
<!-- Upload Zone -->
<div class="upload-zone" id="upload-zone">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
</svg>
<h3>Drop your handwriting image here</h3>
<p>or click to browse • JPG, PNG, BMP supported</p>
<input type="file" id="file-input" accept=".jpg,.jpeg,.png,.bmp,.tiff">
</div>
<!-- Font Name -->
<div class="font-name-section">
<label for="font-name">Font Name</label>
<input type="text" id="font-name" value="MyHandwriting" placeholder="Enter font name">
</div>
<!-- Status Card -->
<div class="status-card" id="status-card">
<div class="status-header">
<div class="status-icon processing" id="status-icon"></div>
<div>
<h3 id="status-title">Processing...</h3>
<p id="status-message" style="color: var(--text-secondary)">Analyzing your handwriting</p>
</div>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progress-fill" style="width: 0%"></div>
</div>
</div>
<!-- Preview Section -->
<div class="preview-section" id="preview-section">
<h3>Detected Characters</h3>
<img src="" alt="Segmentation preview" class="preview-image" id="preview-image">
<div class="characters-grid" id="characters-grid"></div>
</div>
<!-- Download Section -->
<div style="text-align: center; margin: 2rem 0;">
<a href="#" class="download-btn" id="download-btn" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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"/>
</svg>
Download Font
</a>
</div>
<!-- Test Font Section -->
<div class="test-font-section" id="test-font-section">
<h3>Preview Your Font</h3>
<div class="font-preview" id="font-preview">
The quick brown fox jumps over the lazy dog.
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789
</div>
</div>
<footer>
<p>Type.ai • AI-Powered Handwriting to Font Converter</p>
</footer>
</div>
<script>
const uploadZone = document.getElementById('upload-zone');
const fileInput = document.getElementById('file-input');
const fontNameInput = document.getElementById('font-name');
const statusCard = document.getElementById('status-card');
const statusIcon = document.getElementById('status-icon');
const statusTitle = document.getElementById('status-title');
const statusMessage = document.getElementById('status-message');
const progressFill = document.getElementById('progress-fill');
const previewSection = document.getElementById('preview-section');
const previewImage = document.getElementById('preview-image');
const charactersGrid = document.getElementById('characters-grid');
const downloadBtn = document.getElementById('download-btn');
const testFontSection = document.getElementById('test-font-section');
const fontPreview = document.getElementById('font-preview');
let currentJobId = null;
let pollInterval = null;
// Drag and drop
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.classList.add('dragover');
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.classList.remove('dragover');
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.classList.remove('dragover');
const file = e.dataTransfer.files[0];
if (file) uploadFile(file);
});
uploadZone.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) uploadFile(file);
});
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('font_name', fontNameInput.value || 'MyHandwriting');
// Show status
statusCard.classList.add('visible');
statusIcon.className = 'status-icon processing';
statusIcon.textContent = '⏳';
statusTitle.textContent = 'Uploading...';
statusMessage.textContent = 'Sending your handwriting to the AI';
progressFill.style.width = '5%';
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
currentJobId = data.job_id;
pollStatus();
} else {
showError(data.detail || 'Upload failed');
}
} catch (error) {
showError('Connection error: ' + error.message);
}
}
function pollStatus() {
if (pollInterval) clearInterval(pollInterval);
pollInterval = setInterval(async () => {
try {
const response = await fetch(`/api/status/${currentJobId}`);
const data = await response.json();
updateStatus(data);
if (data.status === 'completed' || data.status === 'failed') {
clearInterval(pollInterval);
}
} catch (error) {
console.error('Poll error:', error);
}
}, 1000);
}
function updateStatus(data) {
progressFill.style.width = data.progress + '%';
statusMessage.textContent = data.message;
if (data.status === 'processing') {
statusIcon.className = 'status-icon processing';
statusIcon.textContent = '⏳';
statusTitle.textContent = 'Processing...';
} else if (data.status === 'completed') {
statusIcon.className = 'status-icon completed';
statusIcon.textContent = '✓';
statusTitle.textContent = 'Complete!';
// Show preview
if (data.preview_url) {
previewSection.classList.add('visible');
previewImage.src = data.preview_url;
}
// Show characters
if (data.characters && data.characters.length > 0) {
charactersGrid.innerHTML = data.characters
.map(c => `<span class="char-badge">${c}</span>`)
.join('');
}
// Show download button
downloadBtn.style.display = 'inline-flex';
downloadBtn.href = data.font_url;
// Load and preview font
loadFont(data.font_url, fontNameInput.value);
} else if (data.status === 'failed') {
showError(data.message);
}
}
function showError(message) {
statusIcon.className = 'status-icon failed';
statusIcon.textContent = '✗';
statusTitle.textContent = 'Error';
statusMessage.textContent = message;
progressFill.style.width = '0%';
}
async function loadFont(url, fontName) {
try {
const font = new FontFace(fontName, `url(${url})`);
await font.load();
document.fonts.add(font);
fontPreview.style.fontFamily = `"${fontName}", sans-serif`;
testFontSection.classList.add('visible');
} catch (error) {
console.error('Font load error:', error);
}
}
</script>
</body>
</html>