<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Symbol Grid</title>
<style>
:root{
--bg:#0b0f14; --panel:#0f1620; --text:#e9eef5; --muted:#94a3b8;
--stroke:rgba(255,255,255,.10); --stroke2:rgba(255,255,255,.06);
--glow:rgba(255,255,255,.12);
--radius:16px;
}
*{box-sizing:border-box}
body{
margin:0; min-height:100vh; background:radial-gradient(1200px 700px at 30% -10%, rgba(255,255,255,.08), transparent 55%),
radial-gradient(900px 500px at 90% 10%, rgba(255,255,255,.06), transparent 60%),
var(--bg);
color:var(--text);
font: 14px/1.35 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
letter-spacing:.2px;
}
.wrap{max-width:1100px; margin:0 auto; padding:28px 18px 40px}
header{
display:flex; gap:14px; align-items:flex-end; justify-content:space-between; flex-wrap:wrap;
margin-bottom:18px;
}
.title{
display:flex; flex-direction:column; gap:6px;
}
h1{margin:0; font-size:22px; font-weight:650}
.sub{color:var(--muted); font-size:12.5px}
.controls{
display:flex; gap:10px; align-items:center; flex-wrap:wrap;
}
.input, .select{
background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03));
border:1px solid var(--stroke2);
color:var(--text);
border-radius:12px;
padding:10px 12px;
outline:none;
box-shadow:0 0 0 0 var(--glow);
transition: box-shadow .15s ease, border-color .15s ease;
}
.input:focus, .select:focus{
border-color:rgba(255,255,255,.18);
box-shadow:0 0 0 6px rgba(255,255,255,.06);
}
.input{width:min(360px, 80vw)}
.select{padding-right:34px}
.panel{
background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
border:1px solid var(--stroke2);
border-radius:var(--radius);
overflow:hidden;
}
/* "Unsichtbares" Grid: keine Kacheln, nur Hover */
.grid{
display:grid;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
gap:0;
}
.cell{
height:56px;
display:flex;
align-items:center;
justify-content:center;
user-select:none;
cursor:pointer;
position:relative;
color:rgba(233,238,245,.92);
transition: background .12s ease, transform .08s ease;
}
.cell:hover{
background:rgba(255,255,255,.06);
}
.cell:active{
transform:scale(.98);
}
.glyph{
font-size:22px;
line-height:1;
filter: drop-shadow(0 10px 18px rgba(0,0,0,.25));
}
.footer{
display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap;
padding:12px 14px;
border-top:1px solid var(--stroke2);
color:var(--muted);
font-size:12.5px;
gap:10px;
}
.toast{
position:fixed;
left:50%;
bottom:18px;
transform:translateX(-50%);
background:rgba(15,22,32,.92);
border:1px solid rgba(255,255,255,.12);
border-radius:999px;
padding:10px 14px;
color:var(--text);
box-shadow:0 18px 40px rgba(0,0,0,.35);
opacity:0;
pointer-events:none;
transition: opacity .18s ease, transform .18s ease;
}
.toast.show{
opacity:1;
transform:translateX(-50%) translateY(-4px);
}
.kbd{
font: 12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
padding:.18rem .42rem;
border:1px solid rgba(255,255,255,.14);
border-bottom-color:rgba(255,255,255,.08);
border-radius:8px;
background:rgba(255,255,255,.04);
color:rgba(233,238,245,.9);
}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="title">
<h1>Symbol Grid</h1>
<div class="sub">Klick auf ein Zeichen → kopiert. Suche filtert live. Tip: <span class="kbd">/</span> fokussiert Suche.</div>
</div>
<div class="controls">
<input id="q" class="input" placeholder="Suche (z.B. 'circle', 'arrow', 'box', 'star', 'math') …" />
<select id="cat" class="select">
<option value="all">Alle</option>
</select>
</div>
</header>
<div class="panel">
<div id="grid" class="grid"></div>
<div class="footer">
<div><span id="count">0</span> Zeichen</div>
<div>Copy: Klick • Multi-Copy: Shift+Klick (fügt an “Sammlung” an)</div>
</div>
</div>
</div>
<div id="toast" class="toast">Kopiert</div>
<script>
// --- Zeichenbibliothek (erweiterbar) ---
const LIB = [
{
name: "Circled Numbers",
tags: ["circle","numbers","counter"],
chars: "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿"
},
{
name: "Parenthesized Numbers",
tags: ["numbers","counter"],
chars: "⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇"
},
{
name: "Circled Letters",
tags: ["circle","letters"],
chars: "ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ"
},
{
name: "Arrows",
tags: ["arrow","direction","ui"],
chars: "←↑→↓↔↕↖↗↘↙⇐⇑⇒⇓⇔⇕⟵⟶⟷⟸⟹⟺⟻⟼⟽⟾➔➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮"
},
{
name: "Geometric",
tags: ["shape","geo","design"],
chars: "■□▢▣▤▥▦▧▨▩▰▱◆◇◈◉○●◌◍◐◑◒◓◔◕◖◗△▲▽▼◁◀▷▶◢◣◤◥◦"
},
{
name: "Stars & Marks",
tags: ["star","mark","badge"],
chars: "★☆✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊"
},
{
name: "Check / X / Warning",
tags: ["check","x","status","ui"],
chars: "✓✔✕✖✗✘☐☑☒⚠⚡☠☢☣"
},
{
name: "Box Drawing",
tags: ["box","lines","grid","ascii"],
chars: "─│┌┐└┘├┤┬┴┼━┃┏┓┗┛┣┫┳┻╋╴╵╶╷╸╹╺╻╼╽╾╿╭╮╰╯"
},
{
name: "Brackets & Quotes",
tags: ["brackets","quotes","typography"],
chars: "“”„‟‹›«»⟦⟧⟨⟩⟪⟫⟮⟯〔〕【】〖〗〈〉《》「」『』"
},
{
name: "Math & Tech",
tags: ["math","logic","tech"],
chars: "±×÷∕∗∑∏√∛∞≈≠≡≤≥∈∉∩∪∧∨¬∅∂∇∫∮∴∵⊂⊃⊆⊇⊕⊗⊙"
},
{
name: "UI Symbols",
tags: ["ui","media","controls"],
chars: "⏎⎋⎇⌘⌥⌃⇧⌫⌦⏏⏻⏼⏽⏾⏸⏹⏺⏵⏭⏮⏯⏩⏪"
},
{
name: "Bullets & Separators",
tags: ["bullet","divider","text"],
chars: "•‣◦∙⋅·⋆⋇※⁕⁖⁘⁙⁚⁛⁜⁝⁞‖¦—–…⋯"
}
];
// Flatten
const all = LIB.flatMap(group =>
[...group.chars].map(ch => ({
ch,
group: group.name,
tags: group.tags
}))
);
const grid = document.getElementById("grid");
const q = document.getElementById("q");
const cat = document.getElementById("cat");
const count = document.getElementById("count");
const toast = document.getElementById("toast");
// Populate categories
for (const g of LIB) {
const opt = document.createElement("option");
opt.value = g.name;
opt.textContent = g.name;
cat.appendChild(opt);
}
let stash = "";
function showToast(text){
toast.textContent = text;
toast.classList.add("show");
clearTimeout(showToast._t);
showToast._t = setTimeout(()=>toast.classList.remove("show"), 900);
}
async function copy(text){
try{
await navigator.clipboard.writeText(text);
showToast(`Kopiert: ${text}`);
}catch(e){
// Fallback
const ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
ta.remove();
showToast(`Kopiert: ${text}`);
}
}
function match(item, query, category){
const qq = query.trim().toLowerCase();
if (category !== "all" && item.group !== category) return false;
if (!qq) return true;
// Suche: Zeichen selbst, Gruppenname, Tags
if (item.ch.includes(qq)) return true;
if (item.group.toLowerCase().includes(qq)) return true;
if (item.tags.some(t => t.includes(qq))) return true;
// Kleine Keyword-Mapping (für intuitive Suche)
const map = {
"circle":"Circled",
"arrow":"Arrows",
"box":"Box",
"star":"Stars",
"math":"Math",
"ui":"UI"
};
for (const k in map){
if (qq.includes(k) && item.group.includes(map[k])) return true;
}
return false;
}
function render(){
const query = q.value;
const category = cat.value;
const filtered = all.filter(it => match(it, query, category));
grid.innerHTML = "";
for (const it of filtered){
const cell = document.createElement("div");
cell.className = "cell";
cell.title = `${it.group}`;
cell.innerHTML = `<span class="glyph">${it.ch}</span>`;
cell.addEventListener("click", (ev)=>{
if (ev.shiftKey){
stash += it.ch;
copy(stash);
} else {
stash = it.ch;
copy(it.ch);
}
});
grid.appendChild(cell);
}
count.textContent = filtered.length;
}
q.addEventListener("input", render);
cat.addEventListener("change", render);
// Shortcut: "/" focuses search
window.addEventListener("keydown", (e)=>{
if (e.key === "/" && document.activeElement !== q){
e.preventDefault();
q.focus();
}
if (e.key === "Escape"){
q.value = "";
cat.value = "all";
render();
}
});
render();
</script>
</body>
</html>