wa-dev-tools/public/md.html
treamz 69ec8f4ede UI: light theme default, remove free mentions, fix scroll animation
- Default theme: light (was dark)
- Removed all бесплатно/free/навсегда mentions across all pages
- Landing: title, hero badge, hero text, CTA button, advantages, footer CTA
- Dashboard: hero text, about section, stats
- Register: subtitle
- Fixed scroll-hint animation (was bouncing infinitely, now static)
- Removed class=dark from all 18 HTML files (theme managed by JS)
2026-03-22 00:10:12 +03:00

257 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Markdown Viewer — WA Dev Tools</title>
<script src="/vendor/tailwind.js"></script>
<script>window.WA_TOOL_ID = 'md';</script>
<script src="/shared.js"></script>
<link href="/vendor/fonts.css" rel="stylesheet">
<link href="/shared.css" rel="stylesheet">
<script src="/vendor/marked.min.js"></script>
<script src="/vendor/highlight.min.js"></script>
<link id="hljsTheme" href="/vendor/hljs-github-dark.css" rel="stylesheet">
<style>
/* Dropzone */
.md-dropzone { border: 2px dashed var(--surface-600); transition: all .3s; }
.md-dropzone.active { border-color: #0054e6; background: rgba(0,84,230,.05); }
/* Markdown rendered output */
.md-output {
color: var(--text-primary);
line-height: 1.75;
font-size: 15px;
}
.md-output h1 { font-size: 2em; font-weight: 800; margin: 1.2em 0 .6em; padding-bottom: .3em; border-bottom: 1px solid var(--surface-600); }
.md-output h2 { font-size: 1.5em; font-weight: 700; margin: 1em 0 .5em; padding-bottom: .25em; border-bottom: 1px solid var(--surface-600); }
.md-output h3 { font-size: 1.25em; font-weight: 600; margin: .9em 0 .4em; }
.md-output h4 { font-size: 1.1em; font-weight: 600; margin: .8em 0 .3em; }
.md-output p { margin: .6em 0; }
.md-output ul, .md-output ol { margin: .6em 0; padding-left: 1.8em; }
.md-output li { margin: .25em 0; }
.md-output ul li { list-style: disc; }
.md-output ol li { list-style: decimal; }
.md-output a { color: #0054e6; text-decoration: underline; text-underline-offset: 2px; }
.md-output blockquote { border-left: 3px solid #0054e6; padding: .5em 1em; margin: .8em 0; color: var(--text-secondary); background: rgba(0,84,230,.03); border-radius: 0 8px 8px 0; }
.md-output code { font-family: 'JetBrains Mono', monospace; font-size: .88em; padding: .15em .4em; border-radius: 4px; background: var(--surface-700); }
.md-output pre { margin: .8em 0; border-radius: 10px; overflow-x: auto; }
.md-output pre code { display: block; padding: 1em 1.2em; background: var(--surface-700); font-size: .85em; line-height: 1.6; border-radius: 10px; }
.md-output table { border-collapse: collapse; width: 100%; margin: .8em 0; font-size: .92em; }
.md-output th, .md-output td { border: 1px solid var(--surface-600); padding: .5em .8em; text-align: left; }
.md-output th { background: var(--surface-700); font-weight: 600; }
.md-output hr { border: none; border-top: 1px solid var(--surface-600); margin: 1.5em 0; }
.md-output img { max-width: 100%; border-radius: 8px; margin: .8em 0; }
/* Tabs */
.tab-btn { transition: all .2s; }
.tab-btn.active { color: #0054e6; border-color: #0054e6; }
</style>
</head>
<body class="noise dark:text-gray-200 text-gray-700 font-sans antialiased">
<div class="main-content">
<div class="tool-container relative z-10 max-w-4xl mx-auto">
<!-- Header -->
<div class="text-center mb-8 fade-up">
<div class="inline-flex items-center gap-2 mb-3 px-3 py-1 rounded-full border dark:border-surface-600 border-gray-300 dark:bg-surface-800/60 bg-white/60 text-xs font-mono dark:text-gray-400 text-gray-500 tracking-wide">
<span class="w-1.5 h-1.5 rounded-full bg-accent animate-pulse"></span>
MD Viewer
</div>
<div class="page-header">
<h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight dark:text-white text-gray-900">
Просмотр <span class="text-accent">Markdown</span>
</h1>
<p class="description mt-2 text-sm dark:text-gray-500 text-gray-400 font-mono">drag-drop файл или вставьте текст</p>
</div>
</div>
<!-- Input Card -->
<div class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-5 sm:p-7 backdrop-blur-sm fade-up-delay shadow-sm dark:shadow-none">
<!-- Tabs -->
<div class="flex gap-2 mb-5 border-b dark:border-surface-600 border-gray-200 pb-3">
<button class="tab-btn active text-sm font-medium px-3 py-1.5 border-b-2 border-transparent dark:text-gray-400 text-gray-500" data-tab="file">
Файл
</button>
<button class="tab-btn text-sm font-medium px-3 py-1.5 border-b-2 border-transparent dark:text-gray-400 text-gray-500" data-tab="text">
Текст
</button>
</div>
<!-- File tab -->
<div id="tabFile" class="tab-content">
<div id="dropzone" class="md-dropzone rounded-xl p-8 sm:p-10 text-center cursor-pointer group">
<svg class="mx-auto w-10 h-10 dark:text-gray-500 text-gray-400 group-hover:text-accent transition-colors mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
<p class="text-sm dark:text-gray-400 text-gray-500">
Перетащите <span class="text-accent font-medium">.md</span> файл сюда или <span class="text-accent font-medium cursor-pointer">выберите</span>
</p>
<p id="fileName" class="mt-2 text-xs font-mono dark:text-gray-600 text-gray-400 hidden"></p>
<input type="file" accept=".md,.markdown,.txt" id="fileInput" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" style="position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer">
</div>
</div>
<!-- Text tab -->
<div id="tabText" class="tab-content hidden">
<textarea id="mdInput" class="w-full h-48 px-4 py-3 rounded-xl text-sm font-mono dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 dark:text-gray-200 text-gray-700 focus:outline-none focus:border-accent/50 resize-y transition-colors" placeholder="Вставьте Markdown текст..."></textarea>
<button id="renderBtn" class="mt-3 px-5 py-2.5 rounded-xl text-sm font-semibold bg-accent text-white hover:bg-accent-bright transition-all active:scale-[0.98]">
Рендер
</button>
</div>
</div>
<!-- Output -->
<div id="outputCard" class="hidden mt-5 fade-up">
<div class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-5 sm:p-7 backdrop-blur-sm shadow-sm dark:shadow-none">
<!-- Toolbar -->
<div class="flex items-center justify-between mb-4 pb-3 border-b dark:border-surface-600 border-gray-200">
<span class="text-xs font-mono dark:text-gray-500 text-gray-400 uppercase tracking-wider">Результат</span>
<div class="flex gap-2">
<button id="downloadHtmlBtn" class="px-3 py-1.5 rounded-lg text-xs font-medium bg-accent/10 text-accent hover:bg-accent/20 transition-colors border border-accent/20">
Скачать HTML
</button>
<button id="copyHtmlBtn" class="px-3 py-1.5 rounded-lg text-xs font-medium dark:bg-surface-700 bg-gray-100 dark:text-gray-300 text-gray-600 hover:text-accent transition-colors border dark:border-surface-600 border-gray-200">
Скопировать HTML
</button>
<button id="clearBtn" class="px-3 py-1.5 rounded-lg text-xs font-medium dark:bg-surface-700 bg-gray-100 dark:text-gray-300 text-gray-600 hover:text-danger transition-colors border dark:border-surface-600 border-gray-200">
Очистить
</button>
</div>
</div>
<!-- Rendered markdown -->
<div id="mdOutput" class="md-output"></div>
</div>
</div>
</div>
</div><!-- /main-content -->
<script>
function applyHljsTheme() {
const isDark = document.documentElement.classList.contains('dark');
document.getElementById('hljsTheme').href = isDark ? '/vendor/hljs-github-dark.css' : '/vendor/hljs-github.css';
}
applyHljsTheme();
// Listen for theme changes from shared.js
document.addEventListener('themeChanged', applyHljsTheme);
// Also observe class changes on html element for theme toggle
const observer = new MutationObserver(applyHljsTheme);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
// Tabs
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const tab = btn.dataset.tab;
document.getElementById('tabFile').classList.toggle('hidden', tab !== 'file');
document.getElementById('tabText').classList.toggle('hidden', tab !== 'text');
});
});
// Marked config
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return hljs.highlightAuto(code).value;
},
breaks: true,
gfm: true,
});
function renderMarkdown(md) {
const output = document.getElementById('mdOutput');
const card = document.getElementById('outputCard');
output.innerHTML = marked.parse(md);
card.classList.remove('hidden');
// Re-highlight any code blocks
output.querySelectorAll('pre code').forEach(block => {
hljs.highlightElement(block);
});
}
// File drop
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
// Make dropzone relative for absolute file input
dropzone.style.position = 'relative';
dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('active'); });
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('active'));
dropzone.addEventListener('drop', e => {
e.preventDefault();
dropzone.classList.remove('active');
const file = e.dataTransfer.files[0];
if (file) readFile(file);
});
fileInput.addEventListener('change', e => {
if (e.target.files[0]) readFile(e.target.files[0]);
});
function readFile(file) {
const nameEl = document.getElementById('fileName');
nameEl.textContent = file.name;
nameEl.classList.remove('hidden');
const reader = new FileReader();
reader.onload = e => renderMarkdown(e.target.result);
reader.readAsText(file);
}
// Text render
document.getElementById('renderBtn').addEventListener('click', () => {
const text = document.getElementById('mdInput').value.trim();
if (text) renderMarkdown(text);
});
// Also render on Ctrl+Enter in textarea
document.getElementById('mdInput').addEventListener('keydown', e => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
document.getElementById('renderBtn').click();
}
});
// Copy HTML
document.getElementById('copyHtmlBtn').addEventListener('click', () => {
const html = document.getElementById('mdOutput').innerHTML;
navigator.clipboard.writeText(html).then(() => {
const btn = document.getElementById('copyHtmlBtn');
const orig = btn.textContent;
btn.textContent = 'Скопировано!';
btn.classList.add('text-accent');
setTimeout(() => { btn.textContent = orig; btn.classList.remove('text-accent'); }, 1500);
});
});
// Download HTML
document.getElementById('downloadHtmlBtn').addEventListener('click', () => {
const body = document.getElementById('mdOutput').innerHTML;
const fullHtml = '<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>Markdown</title>\n<style>\nbody{font-family:sans-serif;max-width:800px;margin:40px auto;padding:0 20px;line-height:1.7;color:#333}\npre{background:#f5f5f5;padding:1em;border-radius:6px;overflow-x:auto}\ncode{font-family:monospace;font-size:.9em;background:#f0f0f0;padding:2px 5px;border-radius:3px}\npre code{background:none;padding:0}\ntable{border-collapse:collapse;width:100%}\nth,td{border:1px solid #ddd;padding:8px;text-align:left}\nth{background:#f5f5f5}\nblockquote{border-left:3px solid #0054e6;padding:.5em 1em;margin:1em 0;color:#666;background:#f9f9f9}\nimg{max-width:100%}\n</style>\n</head>\n<body>\n' + body + '\n</body>\n</html>';
const blob = new Blob([fullHtml], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'markdown.html';
a.click();
URL.revokeObjectURL(url);
});
// Clear
document.getElementById('clearBtn').addEventListener('click', () => {
document.getElementById('mdOutput').innerHTML = '';
document.getElementById('outputCard').classList.add('hidden');
document.getElementById('mdInput').value = '';
document.getElementById('fileName').classList.add('hidden');
fileInput.value = '';
});
</script>
</body>
</html>