|
|
|
@@ -0,0 +1,604 @@
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title>MarkItDown · carloselugo.com</title>
|
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;800&display=swap" rel="stylesheet">
|
|
|
|
|
<style>
|
|
|
|
|
/* ── Design tokens — match carloselugo.com ── */
|
|
|
|
|
:root {
|
|
|
|
|
--bg: #0a0a0a;
|
|
|
|
|
--bg-card: #111111;
|
|
|
|
|
--bg-hover: #161616;
|
|
|
|
|
--border: #1e1e1e;
|
|
|
|
|
--accent: #c8ff00;
|
|
|
|
|
--accent-2: #ff6b35;
|
|
|
|
|
--text: #e8e8e8;
|
|
|
|
|
--muted: #555;
|
|
|
|
|
--muted-2: #888;
|
|
|
|
|
--font-mono: 'Space Mono', monospace;
|
|
|
|
|
--font-sans: 'Syne', sans-serif;
|
|
|
|
|
--radius: 4px;
|
|
|
|
|
--max-w: 1100px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
|
html { scroll-behavior: smooth; }
|
|
|
|
|
body {
|
|
|
|
|
background: var(--bg);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-family: var(--font-sans);
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Noise overlay */
|
|
|
|
|
body::before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
z-index: 9999;
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.container { max-width: var(--max-w); margin: 0 auto; padding: 0 24px; }
|
|
|
|
|
|
|
|
|
|
/* ── Header ── */
|
|
|
|
|
.site-header {
|
|
|
|
|
position: sticky; top: 0; z-index: 100;
|
|
|
|
|
padding: 16px 0;
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
background: rgba(10,10,10,0.85);
|
|
|
|
|
backdrop-filter: blur(12px);
|
|
|
|
|
}
|
|
|
|
|
.header-inner { display: flex; align-items: center; justify-content: space-between; }
|
|
|
|
|
.logo {
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
font-family: var(--font-sans); font-weight: 800; font-size: 1.1rem;
|
|
|
|
|
letter-spacing: -0.02em; color: var(--text);
|
|
|
|
|
}
|
|
|
|
|
.logo-bracket { color: var(--accent); }
|
|
|
|
|
.logo span.logo-name { color: var(--text); }
|
|
|
|
|
.logo:hover .logo-name { color: var(--accent); }
|
|
|
|
|
.nav { display: flex; gap: 24px; align-items: center; }
|
|
|
|
|
.nav a {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.75rem;
|
|
|
|
|
color: var(--muted); text-decoration: none; letter-spacing: 0.03em;
|
|
|
|
|
transition: color 0.2s;
|
|
|
|
|
}
|
|
|
|
|
.nav a:hover { color: var(--accent); }
|
|
|
|
|
.nav-tag {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.65rem;
|
|
|
|
|
padding: 2px 8px; border: 1px solid var(--border);
|
|
|
|
|
border-radius: var(--radius); color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Main layout ── */
|
|
|
|
|
main { padding: 64px 0 80px; }
|
|
|
|
|
|
|
|
|
|
.page-head { margin-bottom: 48px; }
|
|
|
|
|
.page-label {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.75rem;
|
|
|
|
|
color: var(--accent); letter-spacing: 0.1em; margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
.page-title {
|
|
|
|
|
font-size: clamp(2rem, 5vw, 3.5rem);
|
|
|
|
|
font-weight: 800; letter-spacing: -0.03em; line-height: 1.05;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
.page-sub { font-size: 0.9rem; color: var(--muted); max-width: 480px; }
|
|
|
|
|
|
|
|
|
|
.accent-line {
|
|
|
|
|
display: inline-block; width: 40px; height: 3px;
|
|
|
|
|
background: var(--accent); margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Two-column layout ── */
|
|
|
|
|
.work-area {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
gap: 1px;
|
|
|
|
|
background: var(--border);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
min-height: 520px;
|
|
|
|
|
}
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.work-area { grid-template-columns: 1fr; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
background: var(--bg-card);
|
|
|
|
|
display: flex; flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
.panel-header {
|
|
|
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.panel-label {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.7rem;
|
|
|
|
|
color: var(--muted); letter-spacing: 0.1em;
|
|
|
|
|
}
|
|
|
|
|
.panel-meta {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.65rem; color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
.panel-body { flex: 1; padding: 24px; display: flex; flex-direction: column; }
|
|
|
|
|
|
|
|
|
|
/* ── Drop zone ── */
|
|
|
|
|
#drop-zone {
|
|
|
|
|
flex: 1;
|
|
|
|
|
border: 2px dashed var(--border);
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
display: flex; flex-direction: column;
|
|
|
|
|
align-items: center; justify-content: center;
|
|
|
|
|
text-align: center; padding: 40px 24px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: border-color 0.2s, background 0.2s;
|
|
|
|
|
position: relative;
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
}
|
|
|
|
|
#drop-zone:hover, #drop-zone.drag-over {
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
background: rgba(200,255,0,0.02);
|
|
|
|
|
}
|
|
|
|
|
#drop-zone.drag-over { border-style: solid; }
|
|
|
|
|
|
|
|
|
|
.drop-icon {
|
|
|
|
|
font-size: 2.5rem; margin-bottom: 16px;
|
|
|
|
|
filter: grayscale(1); opacity: 0.5;
|
|
|
|
|
transition: filter 0.2s, opacity 0.2s;
|
|
|
|
|
}
|
|
|
|
|
#drop-zone:hover .drop-icon,
|
|
|
|
|
#drop-zone.drag-over .drop-icon { filter: none; opacity: 1; }
|
|
|
|
|
|
|
|
|
|
.drop-title {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.75rem;
|
|
|
|
|
color: var(--muted-2); letter-spacing: 0.05em; margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
.drop-hint { font-size: 0.78rem; color: var(--muted); margin-bottom: 20px; }
|
|
|
|
|
.drop-formats {
|
|
|
|
|
display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;
|
|
|
|
|
max-width: 320px;
|
|
|
|
|
}
|
|
|
|
|
.fmt-tag {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.6rem;
|
|
|
|
|
padding: 2px 6px; border: 1px solid var(--border);
|
|
|
|
|
border-radius: var(--radius); color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#file-input { display: none; }
|
|
|
|
|
|
|
|
|
|
/* File loaded state */
|
|
|
|
|
.file-loaded {
|
|
|
|
|
display: none; flex-direction: column; align-items: center;
|
|
|
|
|
justify-content: center; text-align: center; padding: 40px 24px;
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
}
|
|
|
|
|
.file-loaded.active { display: flex; }
|
|
|
|
|
#drop-zone.has-file { display: none; }
|
|
|
|
|
|
|
|
|
|
.file-icon { font-size: 3rem; margin-bottom: 12px; }
|
|
|
|
|
.file-name {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.75rem;
|
|
|
|
|
color: var(--accent); word-break: break-all; margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
.file-size { font-size: 0.78rem; color: var(--muted); margin-bottom: 24px; }
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.7rem;
|
|
|
|
|
letter-spacing: 0.06em; padding: 10px 20px;
|
|
|
|
|
border-radius: var(--radius); cursor: pointer;
|
|
|
|
|
transition: all 0.15s; border: none;
|
|
|
|
|
}
|
|
|
|
|
.btn-primary {
|
|
|
|
|
background: var(--accent); color: #000;
|
|
|
|
|
}
|
|
|
|
|
.btn-primary:hover { filter: brightness(1.1); }
|
|
|
|
|
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
|
|
|
.btn-ghost {
|
|
|
|
|
background: transparent; color: var(--muted);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.btn-ghost:hover { color: var(--text); border-color: var(--muted); }
|
|
|
|
|
|
|
|
|
|
.convert-actions { display: flex; gap: 8px; margin-top: 0; }
|
|
|
|
|
|
|
|
|
|
/* ── Progress / status ── */
|
|
|
|
|
#status-row {
|
|
|
|
|
display: none; align-items: center; gap: 8px;
|
|
|
|
|
padding: 12px 0; font-family: var(--font-mono); font-size: 0.7rem;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
#status-row.active { display: flex; }
|
|
|
|
|
.spinner {
|
|
|
|
|
width: 14px; height: 14px;
|
|
|
|
|
border: 2px solid var(--border); border-top-color: var(--accent);
|
|
|
|
|
border-radius: 50%; animation: spin 0.7s linear infinite; flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
|
|
|
|
|
|
/* ── Output panel ── */
|
|
|
|
|
.output-empty {
|
|
|
|
|
flex: 1; display: flex; flex-direction: column;
|
|
|
|
|
align-items: center; justify-content: center;
|
|
|
|
|
text-align: center; color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
.output-empty-icon { font-size: 2rem; opacity: 0.3; margin-bottom: 8px; }
|
|
|
|
|
.output-empty-text { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.08em; }
|
|
|
|
|
|
|
|
|
|
#markdown-output {
|
|
|
|
|
display: none;
|
|
|
|
|
flex: 1; font-family: var(--font-mono); font-size: 0.78rem;
|
|
|
|
|
color: var(--text); line-height: 1.7; white-space: pre-wrap;
|
|
|
|
|
overflow-y: auto; max-height: 480px;
|
|
|
|
|
background: #0d0d0d; border: 1px solid var(--border);
|
|
|
|
|
border-radius: var(--radius); padding: 16px;
|
|
|
|
|
resize: none; width: 100%;
|
|
|
|
|
}
|
|
|
|
|
#markdown-output.active { display: block; }
|
|
|
|
|
|
|
|
|
|
.output-actions {
|
|
|
|
|
display: none; gap: 8px; margin-top: 12px; flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
.output-actions.active { display: flex; }
|
|
|
|
|
|
|
|
|
|
/* Output stats */
|
|
|
|
|
.output-stats {
|
|
|
|
|
display: none; gap: 20px; padding: 10px 0 4px;
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.65rem; color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
.output-stats.active { display: flex; }
|
|
|
|
|
.stat span { color: var(--muted-2); }
|
|
|
|
|
|
|
|
|
|
/* Error state */
|
|
|
|
|
.error-box {
|
|
|
|
|
display: none;
|
|
|
|
|
background: rgba(255,107,53,0.06);
|
|
|
|
|
border: 1px solid rgba(255,107,53,0.3);
|
|
|
|
|
border-radius: var(--radius); padding: 12px 16px;
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.72rem; color: var(--accent-2);
|
|
|
|
|
}
|
|
|
|
|
.error-box.active { display: block; }
|
|
|
|
|
|
|
|
|
|
/* ── Supported formats info ── */
|
|
|
|
|
.formats-section { margin-top: 40px; padding-top: 40px; border-top: 1px solid var(--border); }
|
|
|
|
|
.formats-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
|
|
|
gap: 1px; background: var(--border);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.fmt-group { background: var(--bg-card); padding: 20px; }
|
|
|
|
|
.fmt-group:hover { background: var(--bg-hover); }
|
|
|
|
|
.fmt-group-title {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.65rem;
|
|
|
|
|
color: var(--accent); letter-spacing: 0.1em; margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
.fmt-list { font-size: 0.82rem; color: var(--muted); line-height: 1.9; }
|
|
|
|
|
|
|
|
|
|
/* ── Copy feedback ── */
|
|
|
|
|
.copy-feedback {
|
|
|
|
|
font-family: var(--font-mono); font-size: 0.65rem;
|
|
|
|
|
color: var(--accent); opacity: 0; transition: opacity 0.3s;
|
|
|
|
|
align-self: center;
|
|
|
|
|
}
|
|
|
|
|
.copy-feedback.show { opacity: 1; }
|
|
|
|
|
|
|
|
|
|
/* ── Footer ── */
|
|
|
|
|
footer {
|
|
|
|
|
border-top: 1px solid var(--border); padding: 24px 0; margin-top: 0;
|
|
|
|
|
}
|
|
|
|
|
.footer-inner {
|
|
|
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.footer-copy { font-family: var(--font-mono); font-size: 0.72rem; color: var(--muted); }
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
|
|
<header class="site-header">
|
|
|
|
|
<div class="container header-inner">
|
|
|
|
|
<a href="https://carloselugo.com" class="logo">
|
|
|
|
|
<span class="logo-bracket">[</span>
|
|
|
|
|
<span class="logo-name">clugo</span>
|
|
|
|
|
<span class="logo-bracket">]</span>
|
|
|
|
|
</a>
|
|
|
|
|
<nav class="nav">
|
|
|
|
|
<span class="nav-tag">tool</span>
|
|
|
|
|
<a href="https://carloselugo.com">home ↗</a>
|
|
|
|
|
</nav>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<main>
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
|
|
|
|
<div class="page-head">
|
|
|
|
|
<div class="page-label">// document converter</div>
|
|
|
|
|
<div class="accent-line"></div>
|
|
|
|
|
<h1 class="page-title">MarkItDown</h1>
|
|
|
|
|
<p class="page-sub">Drop any document. Get clean Markdown. PDF, DOCX, PPTX, XLSX, HTML, and more.</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="work-area">
|
|
|
|
|
<!-- INPUT PANEL -->
|
|
|
|
|
<div class="panel">
|
|
|
|
|
<div class="panel-header">
|
|
|
|
|
<span class="panel-label">INPUT</span>
|
|
|
|
|
<span class="panel-meta" id="file-meta">no file selected</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="panel-body">
|
|
|
|
|
|
|
|
|
|
<!-- Drop zone -->
|
|
|
|
|
<div id="drop-zone" onclick="document.getElementById('file-input').click()">
|
|
|
|
|
<div class="drop-icon">📄</div>
|
|
|
|
|
<div class="drop-title">DROP FILE HERE</div>
|
|
|
|
|
<div class="drop-hint">or click to browse</div>
|
|
|
|
|
<div class="drop-formats">
|
|
|
|
|
<span class="fmt-tag">pdf</span>
|
|
|
|
|
<span class="fmt-tag">docx</span>
|
|
|
|
|
<span class="fmt-tag">pptx</span>
|
|
|
|
|
<span class="fmt-tag">xlsx</span>
|
|
|
|
|
<span class="fmt-tag">html</span>
|
|
|
|
|
<span class="fmt-tag">csv</span>
|
|
|
|
|
<span class="fmt-tag">epub</span>
|
|
|
|
|
<span class="fmt-tag">json</span>
|
|
|
|
|
<span class="fmt-tag">xml</span>
|
|
|
|
|
<span class="fmt-tag">+ more</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<input type="file" id="file-input" accept="*/*">
|
|
|
|
|
|
|
|
|
|
<!-- File loaded state -->
|
|
|
|
|
<div class="file-loaded" id="file-loaded">
|
|
|
|
|
<div class="file-icon" id="file-icon">📄</div>
|
|
|
|
|
<div class="file-name" id="file-name-display"></div>
|
|
|
|
|
<div class="file-size" id="file-size-display"></div>
|
|
|
|
|
<div class="convert-actions">
|
|
|
|
|
<button class="btn btn-primary" id="convert-btn" onclick="convertFile()">▶ CONVERT</button>
|
|
|
|
|
<button class="btn btn-ghost" onclick="clearFile()">✕ CLEAR</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Status -->
|
|
|
|
|
<div id="status-row">
|
|
|
|
|
<div class="spinner"></div>
|
|
|
|
|
<span id="status-text">Converting...</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Error -->
|
|
|
|
|
<div class="error-box" id="error-box"></div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- OUTPUT PANEL -->
|
|
|
|
|
<div class="panel">
|
|
|
|
|
<div class="panel-header">
|
|
|
|
|
<span class="panel-label">OUTPUT · MARKDOWN</span>
|
|
|
|
|
<span class="panel-meta" id="output-meta"></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="panel-body">
|
|
|
|
|
|
|
|
|
|
<div class="output-empty" id="output-empty">
|
|
|
|
|
<div class="output-empty-icon">✦</div>
|
|
|
|
|
<div class="output-empty-text">MARKDOWN WILL APPEAR HERE</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="output-stats" id="output-stats">
|
|
|
|
|
<div class="stat"><span>chars</span> <strong id="stat-chars">0</strong></div>
|
|
|
|
|
<div class="stat"><span>lines</span> <strong id="stat-lines">0</strong></div>
|
|
|
|
|
<div class="stat"><span>words</span> <strong id="stat-words">0</strong></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<textarea id="markdown-output" readonly spellcheck="false"></textarea>
|
|
|
|
|
|
|
|
|
|
<div class="output-actions" id="output-actions">
|
|
|
|
|
<button class="btn btn-primary" onclick="downloadMarkdown()">↓ DOWNLOAD .md</button>
|
|
|
|
|
<button class="btn btn-ghost" id="copy-btn" onclick="copyMarkdown()">⎘ COPY</button>
|
|
|
|
|
<span class="copy-feedback" id="copy-feedback">copied!</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Supported formats -->
|
|
|
|
|
<div class="formats-section">
|
|
|
|
|
<div class="accent-line"></div>
|
|
|
|
|
<h2 style="font-size:1.1rem;font-weight:800;letter-spacing:-0.02em;margin-bottom:24px;">Supported Formats</h2>
|
|
|
|
|
<div class="formats-grid">
|
|
|
|
|
<div class="fmt-group">
|
|
|
|
|
<div class="fmt-group-title">DOCUMENTS</div>
|
|
|
|
|
<div class="fmt-list">PDF<br>DOCX / DOC<br>PPTX / PPT<br>XLSX / XLS<br>EPUB<br>MSG (Outlook)</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="fmt-group">
|
|
|
|
|
<div class="fmt-group-title">WEB / DATA</div>
|
|
|
|
|
<div class="fmt-list">HTML / HTM<br>CSV<br>JSON<br>XML<br>RSS / Atom<br>Plain text</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="fmt-group">
|
|
|
|
|
<div class="fmt-group-title">IMAGES</div>
|
|
|
|
|
<div class="fmt-list">PNG<br>JPG / JPEG<br>GIF<br>WEBP<br>BMP<br>(OCR via AI)</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="fmt-group">
|
|
|
|
|
<div class="fmt-group-title">AUDIO</div>
|
|
|
|
|
<div class="fmt-list">MP3<br>WAV<br>M4A<br>(Transcription via AI)</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="fmt-group">
|
|
|
|
|
<div class="fmt-group-title">ARCHIVES</div>
|
|
|
|
|
<div class="fmt-list">ZIP<br>(converts each file inside)</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
<div class="container footer-inner">
|
|
|
|
|
<span class="footer-copy">© <script>document.write(new Date().getFullYear())</script> Carlos Lugo</span>
|
|
|
|
|
<span class="footer-copy">powered by markitdown</span>
|
|
|
|
|
</div>
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
let selectedFile = null;
|
|
|
|
|
const ICONS = {
|
|
|
|
|
pdf: '📕', docx: '📘', doc: '📘', pptx: '📙', ppt: '📙',
|
|
|
|
|
xlsx: '📗', xls: '📗', html: '🌐', htm: '🌐', csv: '📊',
|
|
|
|
|
json: '🔧', xml: '📄', epub: '📚', txt: '📝', md: '📝',
|
|
|
|
|
zip: '📦', msg: '✉️',
|
|
|
|
|
png: '🖼️', jpg: '🖼️', jpeg: '🖼️', gif: '🖼️', webp: '🖼️',
|
|
|
|
|
mp3: '🎵', wav: '🎵', m4a: '🎵',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function getIcon(filename) {
|
|
|
|
|
const ext = (filename || '').split('.').pop().toLowerCase();
|
|
|
|
|
return ICONS[ext] || '📄';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatSize(bytes) {
|
|
|
|
|
if (bytes < 1024) return bytes + ' B';
|
|
|
|
|
if (bytes < 1024*1024) return (bytes/1024).toFixed(1) + ' KB';
|
|
|
|
|
return (bytes/(1024*1024)).toFixed(1) + ' MB';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Drag & drop
|
|
|
|
|
const dropZone = document.getElementById('drop-zone');
|
|
|
|
|
dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
|
|
|
|
|
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
|
|
|
|
|
dropZone.addEventListener('drop', e => {
|
|
|
|
|
e.preventDefault(); dropZone.classList.remove('drag-over');
|
|
|
|
|
const f = e.dataTransfer.files[0];
|
|
|
|
|
if (f) setFile(f);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('file-input').addEventListener('change', e => {
|
|
|
|
|
if (e.target.files[0]) setFile(e.target.files[0]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function setFile(f) {
|
|
|
|
|
selectedFile = f;
|
|
|
|
|
const ext = f.name.split('.').pop().toLowerCase();
|
|
|
|
|
document.getElementById('file-icon').textContent = getIcon(f.name);
|
|
|
|
|
document.getElementById('file-name-display').textContent = f.name;
|
|
|
|
|
document.getElementById('file-size-display').textContent = formatSize(f.size) + ' · .' + ext;
|
|
|
|
|
document.getElementById('file-meta').textContent = f.name;
|
|
|
|
|
dropZone.classList.add('has-file');
|
|
|
|
|
document.getElementById('file-loaded').classList.add('active');
|
|
|
|
|
clearOutput();
|
|
|
|
|
hideError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearFile() {
|
|
|
|
|
selectedFile = null;
|
|
|
|
|
document.getElementById('file-input').value = '';
|
|
|
|
|
document.getElementById('file-meta').textContent = 'no file selected';
|
|
|
|
|
dropZone.classList.remove('has-file');
|
|
|
|
|
document.getElementById('file-loaded').classList.remove('active');
|
|
|
|
|
clearOutput();
|
|
|
|
|
hideError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearOutput() {
|
|
|
|
|
document.getElementById('output-empty').style.display = 'flex';
|
|
|
|
|
document.getElementById('markdown-output').classList.remove('active');
|
|
|
|
|
document.getElementById('output-actions').classList.remove('active');
|
|
|
|
|
document.getElementById('output-stats').classList.remove('active');
|
|
|
|
|
document.getElementById('output-meta').textContent = '';
|
|
|
|
|
document.getElementById('markdown-output').value = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function showError(msg) {
|
|
|
|
|
const el = document.getElementById('error-box');
|
|
|
|
|
el.textContent = '✗ ' + msg;
|
|
|
|
|
el.classList.add('active');
|
|
|
|
|
}
|
|
|
|
|
function hideError() {
|
|
|
|
|
document.getElementById('error-box').classList.remove('active');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function convertFile() {
|
|
|
|
|
if (!selectedFile) return;
|
|
|
|
|
|
|
|
|
|
hideError();
|
|
|
|
|
document.getElementById('status-row').classList.add('active');
|
|
|
|
|
document.getElementById('status-text').textContent = 'Converting...';
|
|
|
|
|
document.getElementById('convert-btn').disabled = true;
|
|
|
|
|
clearOutput();
|
|
|
|
|
|
|
|
|
|
const form = new FormData();
|
|
|
|
|
form.append('file', selectedFile);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch('/convert', { method: 'POST', body: form });
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
if (!res.ok || data.error) {
|
|
|
|
|
showError(data.error || 'Unknown error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Show output
|
|
|
|
|
const md = data.markdown || '';
|
|
|
|
|
document.getElementById('markdown-output').value = md;
|
|
|
|
|
document.getElementById('markdown-output').classList.add('active');
|
|
|
|
|
document.getElementById('output-actions').classList.add('active');
|
|
|
|
|
document.getElementById('output-empty').style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// Stats
|
|
|
|
|
const lines = md.split('\n').length;
|
|
|
|
|
const words = md.trim() ? md.trim().split(/\s+/).length : 0;
|
|
|
|
|
document.getElementById('stat-chars').textContent = md.length.toLocaleString();
|
|
|
|
|
document.getElementById('stat-lines').textContent = lines.toLocaleString();
|
|
|
|
|
document.getElementById('stat-words').textContent = words.toLocaleString();
|
|
|
|
|
document.getElementById('output-stats').classList.add('active');
|
|
|
|
|
document.getElementById('output-meta').textContent = `${md.length.toLocaleString()} chars`;
|
|
|
|
|
|
|
|
|
|
} catch(e) {
|
|
|
|
|
showError('Request failed: ' + e.message);
|
|
|
|
|
} finally {
|
|
|
|
|
document.getElementById('status-row').classList.remove('active');
|
|
|
|
|
document.getElementById('convert-btn').disabled = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function downloadMarkdown() {
|
|
|
|
|
const md = document.getElementById('markdown-output').value;
|
|
|
|
|
const base = (selectedFile?.name || 'document').replace(/\.[^.]+$/, '');
|
|
|
|
|
const blob = new Blob([md], { type: 'text/markdown' });
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = URL.createObjectURL(blob);
|
|
|
|
|
a.download = base + '.md';
|
|
|
|
|
a.click();
|
|
|
|
|
URL.revokeObjectURL(a.href);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function copyMarkdown() {
|
|
|
|
|
const md = document.getElementById('markdown-output').value;
|
|
|
|
|
try {
|
|
|
|
|
await navigator.clipboard.writeText(md);
|
|
|
|
|
} catch {
|
|
|
|
|
document.getElementById('markdown-output').select();
|
|
|
|
|
document.execCommand('copy');
|
|
|
|
|
}
|
|
|
|
|
const fb = document.getElementById('copy-feedback');
|
|
|
|
|
fb.classList.add('show');
|
|
|
|
|
setTimeout(() => fb.classList.remove('show'), 2000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allow editing the output textarea
|
|
|
|
|
document.getElementById('markdown-output').addEventListener('focus', function() {
|
|
|
|
|
this.removeAttribute('readonly');
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|