- Remove duplicate badges on md, parser, placeholder pages - Custom logout modal (blur overlay, confirm/cancel buttons) - Replace native confirm() with styled modal - Cache bust shared.js?v=3
264 lines
14 KiB
HTML
264 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Placeholder — WA Dev Tools</title>
|
||
<script src="/vendor/tailwind.js"></script>
|
||
<script>window.WA_TOOL_ID = 'placeholder';</script>
|
||
<script src="/shared.js?v=3"></script>
|
||
<link href="/vendor/fonts.css" rel="stylesheet">
|
||
<link href="/shared.css" rel="stylesheet">
|
||
<style>
|
||
select, select option { background: var(--select-bg); color: var(--select-color); }
|
||
|
||
@keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
||
.fade-up { animation: fadeUp 0.4s ease-out forwards; }
|
||
.fade-up-delay { animation: fadeUp 0.4s ease-out 0.1s forwards; opacity: 0; }
|
||
|
||
.preview-frame {
|
||
background: repeating-conic-gradient(rgba(128,128,128,.08) 0% 25%, transparent 0% 50%) 50%/16px 16px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 160px;
|
||
overflow: hidden;
|
||
}
|
||
.preview-frame img { max-width: 100%; max-height: 400px; display: block; border-radius: 8px; }
|
||
|
||
.color-swatch {
|
||
width: 36px; height: 36px; border-radius: 8px; border: 2px solid var(--surface-600);
|
||
cursor: pointer; transition: transform .15s;
|
||
}
|
||
.color-swatch:hover { transform: scale(1.1); }
|
||
input[type="color"] { position: absolute; opacity: 0; width: 0; height: 0; }
|
||
</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-2xl mx-auto">
|
||
|
||
<!-- Header -->
|
||
<div class="page-header" style="text-align:center;">
|
||
<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>
|
||
PNG · JPG · WebP
|
||
</div>
|
||
<h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight dark:text-white text-gray-900">
|
||
Placeholder <span class="text-accent">генератор</span>
|
||
</h1>
|
||
<p class="description mt-2 text-sm dark:text-gray-500 text-gray-400 font-mono">Изображения-заглушки любого размера, цвета и формата</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Controls 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">
|
||
|
||
<!-- Size -->
|
||
<div class="grid grid-cols-2 gap-3 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium dark:text-gray-500 text-gray-400 mb-1.5 font-mono uppercase tracking-wider">Ширина</label>
|
||
<input type="number" id="width" value="600" min="1" max="4000" class="w-full px-3 py-2.5 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:border-accent/50 transition-colors dark:text-white text-gray-800">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium dark:text-gray-500 text-gray-400 mb-1.5 font-mono uppercase tracking-wider">Высота</label>
|
||
<input type="number" id="height" value="400" min="1" max="4000" class="w-full px-3 py-2.5 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:border-accent/50 transition-colors dark:text-white text-gray-800">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Colors -->
|
||
<div class="grid grid-cols-2 gap-3 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium dark:text-gray-500 text-gray-400 mb-1.5 font-mono uppercase tracking-wider">Фон</label>
|
||
<div class="flex items-center gap-2">
|
||
<div class="relative">
|
||
<div id="bgSwatch" class="color-swatch" style="background:#3B82F6"></div>
|
||
<input type="color" id="bgPicker" value="#3B82F6">
|
||
</div>
|
||
<input type="text" id="bgColor" value="3B82F6" maxlength="20" class="flex-1 px-3 py-2.5 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:border-accent/50 transition-colors dark:text-white text-gray-800">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium dark:text-gray-500 text-gray-400 mb-1.5 font-mono uppercase tracking-wider">Текст</label>
|
||
<div class="flex items-center gap-2">
|
||
<div class="relative">
|
||
<div id="fgSwatch" class="color-swatch" style="background:#FFFFFF"></div>
|
||
<input type="color" id="fgPicker" value="#FFFFFF">
|
||
</div>
|
||
<input type="text" id="fgColor" value="FFFFFF" maxlength="20" class="flex-1 px-3 py-2.5 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:border-accent/50 transition-colors dark:text-white text-gray-800">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Text & Font size -->
|
||
<div class="grid grid-cols-2 gap-3 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium dark:text-gray-500 text-gray-400 mb-1.5 font-mono uppercase tracking-wider">Надпись</label>
|
||
<input type="text" id="text" placeholder="600x400" class="w-full px-3 py-2.5 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:border-accent/50 transition-colors dark:text-white text-gray-800">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium dark:text-gray-500 text-gray-400 mb-1.5 font-mono uppercase tracking-wider">Формат</label>
|
||
<select id="format" class="w-full px-3 py-2.5 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-lg text-sm focus:outline-none focus:border-accent/50 transition-colors">
|
||
<option value="png">PNG</option>
|
||
<option value="jpg">JPG</option>
|
||
<option value="webp">WebP</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Generate URL -->
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium dark:text-gray-500 text-gray-400 mb-1.5 font-mono uppercase tracking-wider">URL</label>
|
||
<div class="flex gap-2">
|
||
<input type="text" id="urlField" readonly class="flex-1 px-3 py-2.5 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-lg text-xs font-mono focus:outline-none dark:text-gray-300 text-gray-600 truncate">
|
||
<button id="copyUrlBtn" class="px-4 py-2.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 whitespace-nowrap">
|
||
Копировать
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Generate button -->
|
||
<button id="generateBtn"
|
||
class="w-full py-3 rounded-xl font-semibold text-sm tracking-wide transition-all duration-300
|
||
bg-accent text-white border border-accent cursor-pointer
|
||
hover:bg-accent-bright hover:shadow-lg hover:shadow-accent/20
|
||
active:scale-[0.98]">
|
||
Сгенерировать
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Preview -->
|
||
<div id="previewCard" class="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">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<span class="text-xs font-mono dark:text-gray-500 text-gray-400 uppercase tracking-wider">Превью</span>
|
||
<a id="downloadLink" href="#" download 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 no-underline">
|
||
Скачать
|
||
</a>
|
||
</div>
|
||
<div class="preview-frame p-3">
|
||
<img id="previewImg" src="/placeholder-img/600x400/3B82F6/FFFFFF" alt="placeholder">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API Reference -->
|
||
<div class="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">
|
||
<h3 class="text-sm font-semibold dark:text-white text-gray-800 mb-3">API</h3>
|
||
<div class="space-y-2 text-xs font-mono dark:text-gray-400 text-gray-500 leading-relaxed">
|
||
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">/placeholder-img/:WxH/:bg/:fg</code> — картинка без текста</p>
|
||
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">/placeholder-img/:WxH/:bg/:fg?text=Hello</code> — с текстом</p>
|
||
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">?format=png|jpg|webp</code> — формат выходного файла</p>
|
||
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">?fontsize=30</code> — размер шрифта (авто по умолчанию)</p>
|
||
<p class="dark:text-gray-500 text-gray-400 pt-1">Цвета: HEX (<code class="dark:bg-surface-700 bg-gray-100 px-1.5 py-0.5 rounded">3B82F6</code>) или имя (<code class="dark:bg-surface-700 bg-gray-100 px-1.5 py-0.5 rounded">red</code>, <code class="dark:bg-surface-700 bg-gray-100 px-1.5 py-0.5 rounded">yellow</code>)</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div><!-- /main-content -->
|
||
|
||
<script>
|
||
// Elements
|
||
const widthEl = document.getElementById('width');
|
||
const heightEl = document.getElementById('height');
|
||
const bgColorEl = document.getElementById('bgColor');
|
||
const fgColorEl = document.getElementById('fgColor');
|
||
const bgPickerEl = document.getElementById('bgPicker');
|
||
const fgPickerEl = document.getElementById('fgPicker');
|
||
const bgSwatchEl = document.getElementById('bgSwatch');
|
||
const fgSwatchEl = document.getElementById('fgSwatch');
|
||
const textEl = document.getElementById('text');
|
||
const formatEl = document.getElementById('format');
|
||
const urlField = document.getElementById('urlField');
|
||
const previewImg = document.getElementById('previewImg');
|
||
const downloadLink = document.getElementById('downloadLink');
|
||
|
||
// Color picker sync
|
||
bgSwatchEl.addEventListener('click', () => bgPickerEl.click());
|
||
fgSwatchEl.addEventListener('click', () => fgPickerEl.click());
|
||
|
||
bgPickerEl.addEventListener('input', e => {
|
||
const hex = e.target.value.replace('#', '').toUpperCase();
|
||
bgColorEl.value = hex;
|
||
bgSwatchEl.style.background = e.target.value;
|
||
updateUrl();
|
||
});
|
||
fgPickerEl.addEventListener('input', e => {
|
||
const hex = e.target.value.replace('#', '').toUpperCase();
|
||
fgColorEl.value = hex;
|
||
fgSwatchEl.style.background = e.target.value;
|
||
updateUrl();
|
||
});
|
||
bgColorEl.addEventListener('input', () => {
|
||
const v = bgColorEl.value.trim();
|
||
if (/^[0-9A-Fa-f]{6}$/.test(v)) {
|
||
bgPickerEl.value = '#' + v;
|
||
bgSwatchEl.style.background = '#' + v;
|
||
} else {
|
||
bgSwatchEl.style.background = v;
|
||
}
|
||
updateUrl();
|
||
});
|
||
fgColorEl.addEventListener('input', () => {
|
||
const v = fgColorEl.value.trim();
|
||
if (/^[0-9A-Fa-f]{6}$/.test(v)) {
|
||
fgPickerEl.value = '#' + v;
|
||
fgSwatchEl.style.background = '#' + v;
|
||
} else {
|
||
fgSwatchEl.style.background = v;
|
||
}
|
||
updateUrl();
|
||
});
|
||
|
||
[widthEl, heightEl, textEl, formatEl].forEach(el => el.addEventListener('input', updateUrl));
|
||
|
||
function buildPath() {
|
||
const w = widthEl.value || 600;
|
||
const h = heightEl.value || 400;
|
||
const bg = bgColorEl.value.trim() || '3B82F6';
|
||
const fg = fgColorEl.value.trim() || 'FFFFFF';
|
||
let url = `/placeholder-img/${w}x${h}/${bg}/${fg}`;
|
||
const params = [];
|
||
const txt = textEl.value.trim();
|
||
if (txt) params.push('text=' + encodeURIComponent(txt));
|
||
const fmt = formatEl.value;
|
||
if (fmt !== 'png') params.push('format=' + fmt);
|
||
if (params.length) url += '?' + params.join('&');
|
||
return url;
|
||
}
|
||
|
||
function updateUrl() {
|
||
const p = buildPath();
|
||
urlField.value = location.origin + p;
|
||
}
|
||
|
||
function generate() {
|
||
const p = buildPath();
|
||
previewImg.src = p;
|
||
downloadLink.href = p;
|
||
updateUrl();
|
||
}
|
||
|
||
document.getElementById('generateBtn').addEventListener('click', generate);
|
||
|
||
// Copy URL
|
||
document.getElementById('copyUrlBtn').addEventListener('click', () => {
|
||
navigator.clipboard.writeText(urlField.value).then(() => {
|
||
const btn = document.getElementById('copyUrlBtn');
|
||
const orig = btn.textContent;
|
||
btn.textContent = 'Скопировано!';
|
||
btn.classList.add('text-accent');
|
||
setTimeout(() => { btn.textContent = orig; btn.classList.remove('text-accent'); }, 1500);
|
||
});
|
||
});
|
||
|
||
// Init
|
||
updateUrl();
|
||
</script>
|
||
</body>
|
||
</html>
|