<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>