تخطَّ إلى المحتوى
ARAB CODEمجتمع المطورين العرب

أداء عمل باسوردات

htmlv1MITنُشر ٤ أكتوبر ٢٠٢٦· ♥ 0 · ⬇ 1 · 💬 0
  1. الهدف: توفير أداة عربية آمنة وسهلة لتوليد كلمات سر قوية بدون الاعتماد على أي موقع خارجي. 2. ماذا تفعل: تولّد كلمات سر، عبارات مرور، مفاتيح JWT، ومعرّفات UUID v4 بضغطة زر. 3. الفائدة: تحمي حساباتك من التخمين والاختراق بكلمات سر عشوائية يصعب كسرها. 4. القوة: تعتمد على crypto.g
<!DOCTYPE html>
<html lang="ar" dir="rtl" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0b0f19">
<meta name="description" content="مولّد كلمات سر وعبارات مرور ومفاتيح JWT آمنة — توليد محلي بـ Web Crypto API">
<meta name="referrer" content="no-referrer">
<title>حِرز · مولّد كلمات السر الآمنة</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='24' fill='%2306b6d4'/%3E%3Cpath d='M50 22a16 16 0 0 0-16 16v8h-4a6 6 0 0 0-6 6v22a6 6 0 0 0 6 6h40a6 6 0 0 0 6-6V52a6 6 0 0 0-6-6h-4v-8a16 16 0 0 0-16-16zm0 10a6 6 0 0 1 6 6v8H44v-8a6 6 0 0 1 6-6z' fill='%23fff'/%3E%3C/svg%3E">
<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=Tajawal:wght@400;500;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">

<style>
/* ============================================================
   حِرز · Design System
   Single-hue cyan accent · neutral slate scale
   ============================================================ */

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}

:root{
  --r-sm:8px; --r-md:12px; --r-lg:16px; --r-xl:22px; --r-full:999px;
  --f-ar:"Tajawal",system-ui,-apple-system,"Segoe UI",Tahoma,sans-serif;
  --f-mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  --ease:cubic-bezier(.22,1,.36,1);
  --dur:.25s;
}

html[data-theme="dark"]{
  --bg:#0b0f19; --bg-elev:#111827;
  --surface:#151b28; --surface-2:#1c2434; --surface-3:#232c3f;
  --border:#232b3d; --border-strong:#2e3850;
  --text:#e6ebf5; --text-muted:#8896b3; --text-dim:#566178;
  --accent:#06b6d4; --accent-2:#0891b2;
  --accent-soft:rgba(6,182,212,.13); --accent-line:rgba(6,182,212,.32);
  --accent-glow:rgba(6,182,212,.28);
  --ok:#10b981; --ok-soft:rgba(16,185,129,.14);
  --warn:#f59e0b; --warn-soft:rgba(245,158,11,.14);
  --danger:#ef4444; --danger-soft:rgba(239,68,68,.13);
  --shadow-lg:0 24px 60px -18px rgba(0,0,0,.7);
  --shadow-sm:0 4px 14px -4px rgba(0,0,0,.5);
}

html[data-theme="light"]{
  --bg:#f5f8fc; --bg-elev:#ffffff;
  --surface:#ffffff; --surface-2:#f1f5f9; --surface-3:#e6ecf3;
  --border:#e2e8f0; --border-strong:#cbd5e1;
  --text:#0f172a; --text-muted:#475569; --text-dim:#94a3b8;
  --accent:#0891b2; --accent-2:#0e7490;
  --accent-soft:rgba(8,145,178,.09); --accent-line:rgba(8,145,178,.28);
  --accent-glow:rgba(8,145,178,.22);
  --ok:#059669; --ok-soft:rgba(5,150,105,.1);
  --warn:#d97706; --warn-soft:rgba(217,119,6,.1);
  --danger:#dc2626; --danger-soft:rgba(220,38,38,.09);
  --shadow-lg:0 20px 50px -20px rgba(15,23,42,.18);
  --shadow-sm:0 2px 8px -2px rgba(15,23,42,.08);
}

html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}

body{
  min-height:100dvh;font-family:var(--f-ar);color:var(--text);
  background:var(--bg);line-height:1.65;overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease);
}

.bg-fx{position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden}
html[data-theme="dark"] .bg-fx{
  background:
    radial-gradient(55rem 38rem at 88% -8%,rgba(6,182,212,.10),transparent 62%),
    radial-gradient(45rem 32rem at 0% 105%,rgba(8,145,178,.08),transparent 60%),
    linear-gradient(180deg,#0b0f19 0%,#0d1320 100%);
}
html[data-theme="light"] .bg-fx{
  background:
    radial-gradient(55rem 38rem at 88% -8%,rgba(8,145,178,.07),transparent 62%),
    radial-gradient(45rem 32rem at 0% 105%,rgba(14,116,144,.05),transparent 60%),
    linear-gradient(180deg,#f5f8fc 0%,#eef3f9 100%);
}
.bg-fx::after{
  content:"";position:absolute;inset:0;
  background-image:
    linear-gradient(var(--border) 1px,transparent 1px),
    linear-gradient(90deg,var(--border) 1px,transparent 1px);
  background-size:48px 48px;opacity:.35;
  mask-image:radial-gradient(ellipse 70% 55% at 50% 40%,#000 25%,transparent 78%);
  -webkit-mask-image:radial-gradient(ellipse 70% 55% at 50% 40%,#000 25%,transparent 78%);
}

.shell{max-width:880px;margin:0 auto;padding:0 20px}

.site-header{padding:clamp(28px,5.5vw,52px) 0 20px}
.header-inner{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
.brand{display:flex;align-items:center;gap:16px;min-width:0}
.brand-mark{
  flex:0 0 auto;width:52px;height:52px;
  display:grid;place-items:center;border-radius:var(--r-lg);color:#fff;
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  box-shadow:0 10px 26px -8px var(--accent-glow),inset 0 1px 0 rgba(255,255,255,.3);
}
.brand-mark svg{width:26px;height:26px}
.brand h1{font-size:clamp(20px,3vw,25px);font-weight:800;letter-spacing:-.02em;line-height:1.2}
.brand h1 em{font-style:normal;color:var(--text-muted);font-weight:500;font-size:.62em;margin-inline-start:6px}
.brand p{margin-top:3px;font-size:13.5px;color:var(--text-muted);line-height:1.5}
.brand code{
  font-family:var(--f-mono);font-size:.85em;color:var(--accent);
  padding:1px 6px;border-radius:6px;background:var(--accent-soft);direction:ltr;display:inline-block;
}
.header-actions{display:flex;gap:8px;flex:0 0 auto}

.ghost-btn{
  width:42px;height:42px;display:grid;place-items:center;
  border-radius:var(--r-md);border:1px solid var(--border);
  background:var(--surface);color:var(--text-muted);cursor:pointer;
  transition:all var(--dur) var(--ease);
}
.ghost-btn svg{width:18px;height:18px;transition:transform var(--dur) var(--ease)}
.ghost-btn:hover{color:var(--accent);border-color:var(--accent-line);background:var(--accent-soft);transform:translateY(-1px)}
.ghost-btn:hover svg{transform:rotate(18deg)}
.ghost-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
html[data-theme="dark"] .ghost-btn .i-sun{display:none}
html[data-theme="light"] .ghost-btn .i-moon{display:none}

.card{
  position:relative;border-radius:var(--r-xl);border:1px solid var(--border);
  background:var(--surface);box-shadow:var(--shadow-lg);
  padding:clamp(20px,3.6vw,28px);margin-bottom:20px;
}
.card::before{
  content:"";position:absolute;inset:0 0 auto;height:1px;border-radius:var(--r-xl) var(--r-xl) 0 0;
  background:linear-gradient(90deg,transparent,var(--border-strong),transparent);
}
.card--slim{padding:20px clamp(16px,3vw,22px)}

.tabs{
  position:relative;display:flex;gap:2px;padding:5px;margin-bottom:24px;
  border-radius:var(--r-md);border:1px solid var(--border);
  background:var(--surface-2);overflow-x:auto;scrollbar-width:none;
}
.tabs::-webkit-scrollbar{display:none}
.tab{
  position:relative;z-index:1;flex:1 0 auto;min-width:96px;
  padding:10px 14px;border:0;border-radius:var(--r-sm);background:transparent;
  color:var(--text-muted);font:inherit;font-size:14px;font-weight:700;cursor:pointer;
  white-space:nowrap;transition:color var(--dur) var(--ease);
}
.tab:hover{color:var(--text)}
.tab:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.tab.is-active{color:#fff}
.tab-pill{
  position:absolute;top:5px;right:0;height:calc(100% - 10px);border-radius:var(--r-sm);
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  box-shadow:0 6px 18px -6px var(--accent-glow),inset 0 1px 0 rgba(255,255,255,.28);
  transition:transform .38s var(--ease),width .38s var(--ease);pointer-events:none;
  will-change:transform,width;
}

.panel{display:grid;gap:20px;animation:rise .35s var(--ease) both}
.panel[hidden]{display:none}
@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

.output{
  position:relative;display:flex;align-items:center;gap:12px;
  padding:16px 16px 16px 12px;border-radius:var(--r-md);
  border:1px solid var(--border-strong);background:var(--surface-2);
  transition:border-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease);
}
.output:hover{border-color:var(--accent-line);box-shadow:0 0 0 4px var(--accent-soft)}
.output--tall{align-items:flex-start;padding-block:20px}
.output-val{
  flex:1 1 auto;min-width:0;font-family:var(--f-mono);
  font-size:clamp(15px,2.5vw,19px);font-weight:600;letter-spacing:.005em;
  color:var(--text);word-break:break-all;overflow-wrap:anywhere;line-height:1.5;
  user-select:all;-webkit-user-select:all;
  transition:filter .25s var(--ease),opacity .25s var(--ease);
}
.output-val--sm{font-size:clamp(12.5px,2vw,14.5px);line-height:1.75}
.output-val.is-blurred{filter:blur(7px);opacity:.55}
.output-tools{display:flex;gap:8px;flex:0 0 auto}

.icon-btn{
  display:grid;place-items:center;width:38px;height:38px;
  border-radius:var(--r-sm);border:1px solid var(--border);
  background:var(--surface);color:var(--text-muted);cursor:pointer;
  transition:all var(--dur) var(--ease);
}
.icon-btn svg{width:17px;height:17px;transition:transform .3s var(--ease)}
.icon-btn:hover{color:var(--accent);border-color:var(--accent-line);background:var(--accent-soft);transform:translateY(-1px)}
.icon-btn:active{transform:translateY(0) scale(.95)}
.icon-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.icon-btn.is-copied{color:var(--ok);border-color:var(--ok);background:var(--ok-soft)}
.icon-btn.is-copied svg{transform:scale(.85)}

.actions{display:flex;gap:10px;flex-wrap:wrap}
.btn-primary{
  flex:1 1 200px;display:flex;align-items:center;justify-content:center;gap:12px;
  padding:15px 22px;border:0;border-radius:var(--r-md);
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  color:#fff;font:inherit;font-size:15px;font-weight:800;cursor:pointer;
  box-shadow:0 12px 28px -12px var(--accent-glow),inset 0 1px 0 rgba(255,255,255,.25);
  transition:transform .15s var(--ease),box-shadow var(--dur) var(--ease),filter var(--dur) var(--ease);
}
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 18px 36px -14px var(--accent-glow);filter:brightness(1.05)}
.btn-primary:active{transform:translateY(0) scale(.985)}
.btn-primary:focus-visible{outline:2px solid var(--text);outline-offset:3px}
.btn-primary svg{width:18px;height:18px;transition:transform .45s var(--ease)}
.btn-primary:hover svg{transform:rotate(90deg)}

.btn-ghost{
  display:inline-flex;align-items:center;gap:8px;padding:9px 16px;
  border-radius:var(--r-sm);border:1px solid var(--border-strong);
  background:transparent;color:var(--text-muted);font:inherit;
  font-size:13.5px;font-weight:700;cursor:pointer;transition:all var(--dur) var(--ease);
}
.btn-ghost:hover{color:var(--accent);border-color:var(--accent-line);background:var(--accent-soft)}
.btn-ghost:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.btn-ghost--sm{padding:6px 12px;font-size:12.5px}
.btn-ghost svg{width:14px;height:14px}

.presets{
  display:flex;flex-wrap:wrap;gap:8px;padding-bottom:16px;
  border-bottom:1px dashed var(--border);margin-bottom:20px;
}
.presets-label{
  width:100%;font-size:12px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--text-dim);margin-bottom:4px;
}
.preset{
  padding:7px 13px;border-radius:var(--r-full);
  border:1px solid var(--border-strong);background:var(--surface-2);
  color:var(--text-muted);font:inherit;font-size:12.5px;font-weight:700;
  cursor:pointer;transition:all var(--dur) var(--ease);
}
.preset:hover{border-color:var(--accent-line);color:var(--accent);background:var(--accent-soft);transform:translateY(-1px)}
.preset.is-active{
  border-color:var(--accent);color:#fff;
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  box-shadow:0 4px 12px -4px var(--accent-glow);
}
.preset:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

.meter{display:grid;gap:12px}
.meter-track{display:grid;grid-template-columns:repeat(5,1fr);gap:5px;height:6px}
.meter-track span{
  border-radius:var(--r-full);background:var(--surface-3);
  transition:background .35s var(--ease),box-shadow .35s var(--ease);
}
.meter[data-level="1"] .meter-track span:nth-child(-n+1){background:var(--danger);box-shadow:0 0 10px -2px var(--danger)}
.meter[data-level="2"] .meter-track span:nth-child(-n+2){background:var(--warn);box-shadow:0 0 10px -2px var(--warn)}
.meter[data-level="3"] .meter-track span:nth-child(-n+3){background:#84cc16;box-shadow:0 0 10px -2px #84cc16}
.meter[data-level="4"] .meter-track span:nth-child(-n+4){background:var(--ok);box-shadow:0 0 10px -2px var(--ok)}
.meter[data-level="5"] .meter-track span:nth-child(-n+5){background:var(--ok);box-shadow:0 0 12px -2px var(--ok)}
.meter-info{
  display:flex;align-items:center;justify-content:space-between;
  flex-wrap:wrap;gap:8px 16px;font-size:12.5px;color:var(--text-muted);
}
.meter-label{
  font-weight:800;font-size:13px;color:var(--text);
  display:flex;align-items:center;gap:8px;
}
.meter-label::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--text-dim)}
.meter[data-level="1"] .meter-label::before{background:var(--danger)}
.meter[data-level="2"] .meter-label::before{background:var(--warn)}
.meter[data-level="3"] .meter-label::before{background:#84cc16}
.meter[data-level="4"] .meter-label::before{background:var(--ok)}
.meter[data-level="5"] .meter-label::before{background:var(--ok);box-shadow:0 0 10px var(--ok)}
.meter-stats{display:flex;gap:16px;font-family:var(--f-mono);font-size:11.5px;direction:ltr}
.meter-stats b{color:var(--text);font-weight:600}

.controls{display:grid;gap:20px}
.row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:16px}
.row > label{font-size:13.5px;font-weight:700;color:var(--text-muted);white-space:nowrap}
.row--field{grid-template-columns:minmax(120px,auto) 1fr}
.pill{
  min-width:56px;padding:5px 12px;border-radius:var(--r-full);
  border:1px solid var(--accent-line);background:var(--accent-soft);
  color:var(--accent);font-family:var(--f-mono);font-size:13.5px;
  font-weight:700;text-align:center;direction:ltr;
}
.input-wrap{position:relative}
.char-count{
  position:absolute;top:50%;transform:translateY(-50%);
  inset-inline-end:12px;font-family:var(--f-mono);
  font-size:11px;color:var(--text-dim);pointer-events:none;
}

input[type="range"]{
  -webkit-appearance:none;appearance:none;width:100%;height:24px;
  background:transparent;cursor:pointer;direction:ltr;
}
input[type="range"]::-webkit-slider-runnable-track{
  height:5px;border-radius:var(--r-full);
  background:linear-gradient(90deg,var(--accent) var(--pct,50%),var(--surface-3) var(--pct,50%));
}
input[type="range"]::-webkit-slider-thumb{
  -webkit-appearance:none;width:18px;height:18px;margin-top:-6.5px;
  border-radius:50%;background:var(--surface);border:4px solid var(--accent);
  box-shadow:0 3px 10px -2px var(--accent-glow);transition:transform .15s var(--ease);
}
input[type="range"]:hover::-webkit-slider-thumb{transform:scale(1.12)}
input[type="range"]:focus-visible::-webkit-slider-thumb{outline:2px solid var(--accent);outline-offset:3px}
input[type="range"]::-moz-range-track{height:5px;border-radius:var(--r-full);background:var(--surface-3)}
input[type="range"]::-moz-range-progress{height:5px;border-radius:var(--r-full);background:var(--accent)}
input[type="range"]::-moz-range-thumb{
  width:14px;height:14px;border-radius:50%;
  background:var(--surface);border:4px solid var(--accent);
  box-shadow:0 3px 10px -2px var(--accent-glow);
}

select,input[type="text"]{
  width:100%;padding:11px 14px;border-radius:var(--r-sm);
  border:1px solid var(--border-strong);background:var(--surface-2);
  color:var(--text);font:inherit;font-size:13.5px;cursor:pointer;
  transition:border-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease);
}
select:hover,input[type="text"]:hover{border-color:var(--accent-line)}
select:focus-visible,input[type="text"]:focus-visible{
  outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);
}
input[type="text"]{
  cursor:text;direction:ltr;font-family:var(--f-mono);
  letter-spacing:.02em;padding-inline-end:56px;
}
option{background:var(--bg-elev);color:var(--text)}

.grid-checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(215px,1fr));gap:8px}
.check{
  position:relative;display:flex;align-items:center;gap:12px;
  padding:10px 13px;border-radius:var(--r-sm);
  border:1px solid var(--border);background:var(--surface-2);
  cursor:pointer;font-size:13px;color:var(--text-muted);
  transition:all var(--dur) var(--ease);user-select:none;
}
.check:hover{border-color:var(--border-strong);background:var(--surface-3);color:var(--text)}
.check input{position:absolute;opacity:0;pointer-events:none}
.check .box{
  position:relative;flex:0 0 auto;width:18px;height:18px;border-radius:5px;
  border:1.5px solid var(--border-strong);background:var(--surface);
  transition:all var(--dur) var(--ease);
}
.check .box::after{
  content:"";position:absolute;top:2px;left:5px;width:5px;height:9px;
  border-right:2px solid #fff;border-bottom:2px solid #fff;
  transform:rotate(45deg) scale(.3);opacity:0;transition:all .2s var(--ease);
}
.check input:checked + .box{
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  border-color:transparent;box-shadow:0 3px 10px -3px var(--accent-glow);
}
.check input:checked + .box::after{opacity:1;transform:rotate(45deg) scale(1)}
.check input:focus-visible + .box{outline:2px solid var(--accent);outline-offset:2px}
.check input:checked ~ span:last-child{color:var(--text);font-weight:700}
.check code{
  font-family:var(--f-mono);font-size:11px;padding:1px 5px;border-radius:4px;
  background:var(--accent-soft);color:var(--accent);direction:ltr;display:inline-block;
}

.hint{
  display:flex;gap:12px;padding:13px 15px;border-radius:var(--r-md);
  border:1px solid var(--accent-line);background:var(--accent-soft);
  color:var(--text);font-size:13px;line-height:1.7;
}
.hint svg{flex:0 0 auto;width:18px;height:18px;margin-top:2px;color:var(--accent)}
.hint code{
  font-family:var(--f-mono);font-size:.88em;padding:1px 6px;border-radius:5px;
  background:var(--surface);color:var(--accent);direction:ltr;display:inline-block;
}

.uuid-list{
  display:grid;gap:8px;max-height:340px;overflow-y:auto;padding:2px;
  scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent;
}
.uuid-list::-webkit-scrollbar{width:6px}
.uuid-list::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:var(--r-full)}
.uuid-row{
  display:flex;align-items:center;gap:12px;padding:10px 12px;
  border-radius:var(--r-sm);border:1px solid var(--border);background:var(--surface-2);
  transition:all var(--dur) var(--ease);animation:rise .3s var(--ease) both;
}
.uuid-row:hover{border-color:var(--accent-line);background:var(--accent-soft)}
.uuid-row code{
  flex:1 1 auto;min-width:0;font-family:var(--f-mono);font-size:12px;
  color:var(--text-muted);word-break:break-all;direction:ltr;text-align:left;
}
.uuid-row .icon-btn{width:30px;height:30px;border-radius:6px}
.uuid-row .icon-btn svg{width:14px;height:14px}

.card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
.card-head h2{
  font-size:14px;font-weight:800;color:var(--text-muted);
  letter-spacing:.01em;display:flex;align-items:center;gap:8px;
}
.card-head h2 svg{width:15px;height:15px;color:var(--accent)}
.history{list-style:none;display:grid;gap:8px}
.history-empty{padding:20px 12px;text-align:center;color:var(--text-dim);font-size:13px}
.history-item{
  display:flex;align-items:center;gap:12px;padding:10px 12px;
  border-radius:var(--r-sm);border:1px solid transparent;background:var(--surface-2);
  transition:all var(--dur) var(--ease);animation:rise .3s var(--ease) both;
}
.history-item:hover{border-color:var(--border-strong);background:var(--surface-3)}
.history-tag{
  flex:0 0 auto;padding:3px 9px;border-radius:var(--r-full);
  font-size:10.5px;font-weight:800;letter-spacing:.03em;border:1px solid transparent;
}
.history-tag[data-kind="password"]{background:var(--accent-soft);color:var(--accent);border-color:var(--accent-line)}
.history-tag[data-kind="passphrase"]{background:var(--ok-soft);color:var(--ok);border-color:var(--ok)}
.history-tag[data-kind="secret"]{background:var(--warn-soft);color:var(--warn);border-color:var(--warn)}
.history-tag[data-kind="uuid"]{background:var(--surface-3);color:var(--text-muted);border-color:var(--border-strong)}
.history-val{
  flex:1 1 auto;min-width:0;font-family:var(--f-mono);font-size:12px;
  color:var(--text-muted);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;direction:ltr;text-align:left;
}
.history-item .icon-btn{width:28px;height:28px;border-radius:6px}
.history-item .icon-btn svg{width:13px;height:13px}
.foot-note{margin-top:16px;font-size:11.5px;color:var(--text-dim);text-align:center}

.site-footer{padding:24px 0 56px;text-align:center;color:var(--text-dim);font-size:12.5px}
.badges{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;margin-bottom:16px}
.badge{
  display:inline-flex;align-items:center;gap:6px;padding:6px 12px;
  border-radius:var(--r-full);border:1px solid var(--border);
  background:var(--surface);font-size:11.5px;color:var(--text-muted);
}
.badge .dot{width:5px;height:5px;border-radius:50%;background:var(--ok);box-shadow:0 0 6px var(--ok)}
.site-footer code{font-family:var(--f-mono);font-size:.9em;color:var(--accent)}

.toast{
  position:fixed;left:50%;bottom:28px;transform:translate(-50%,140%);
  z-index:100;padding:11px 20px;border-radius:var(--r-md);
  border:1px solid var(--ok);background:var(--surface);color:var(--text);
  font-size:13.5px;font-weight:700;box-shadow:var(--shadow-lg);
  opacity:0;transition:transform .35s var(--ease),opacity .3s var(--ease);
  pointer-events:none;display:flex;align-items:center;gap:8px;
}
.toast::before{
  content:"";width:8px;height:8px;border-radius:50%;
  background:var(--ok);box-shadow:0 0 8px var(--ok);
}
.toast.is-visible{transform:translate(-50%,0);opacity:1}
.toast.is-error{border-color:var(--danger)}
.toast.is-error::before{background:var(--danger);box-shadow:0 0 8px var(--danger)}

.modal{
  border:1px solid var(--border);border-radius:var(--r-lg);
  background:var(--surface);color:var(--text);padding:0;
  max-width:440px;width:calc(100vw - 32px);
  box-shadow:var(--shadow-lg);overflow:hidden;
}
.modal::backdrop{background:rgba(0,0,0,.55);backdrop-filter:blur(4px)}
html[data-theme="light"] .modal::backdrop{background:rgba(15,23,42,.35)}
.modal-head{
  display:flex;align-items:center;justify-content:space-between;
  gap:12px;padding:18px 20px;border-bottom:1px solid var(--border);
}
.modal-head h2{font-size:15.5px;font-weight:800}
.shortcuts{list-style:none;padding:12px 20px 20px;display:grid;gap:6px}
.shortcuts li{
  display:flex;align-items:center;justify-content:space-between;
  gap:16px;padding:10px 4px;border-bottom:1px solid var(--border);
  font-size:13px;color:var(--text-muted);
}
.shortcuts li:last-child{border-bottom:0}
.shortcuts kbd{
  display:inline-block;padding:3px 9px;border-radius:6px;
  border:1px solid var(--border-strong);background:var(--surface-2);
  font-family:var(--f-mono);font-size:11.5px;font-weight:600;
  color:var(--text);box-shadow:0 1px 0 var(--border-strong);
}
.shortcuts kbd + kbd{margin-inline-start:4px}

@media (max-width:600px){
  .shell{padding:0 14px}
  .card{padding:20px 16px;border-radius:var(--r-lg)}
  .tab{min-width:82px;font-size:13px;padding:9px 10px}
  .row{grid-template-columns:auto 1fr auto;gap:12px}
  .row--field{grid-template-columns:1fr;gap:8px}
  .row--field > label{grid-column:1 / -1}
  .brand-mark{width:44px;height:44px;border-radius:var(--r-md)}
  .brand-mark svg{width:22px;height:22px}
  .brand p{font-size:12.5px}
  .grid-checks{grid-template-columns:1fr}
  .meter-stats{gap:12px;font-size:10.5px}
  .actions{flex-direction:column}
  .btn-primary{flex:1 1 auto}
  .btn-ghost{width:100%;justify-content:center}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;transition-duration:.001ms !important}
}
</style>
</head>
<body>

<div class="bg-fx" aria-hidden="true"></div>

<div class="shell">

  <header class="site-header">
    <div class="header-inner">
      <div class="brand">
        <div class="brand-mark" aria-hidden="true">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect x="3" y="11" width="18" height="11" rx="2"/>
            <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
          </svg>
        </div>
        <div>
          <h1>حِرز <em>· مولّد كلمات السر</em></h1>
          <p>توليد محلي 100% بـ <code>crypto.getRandomValues</code> — بدون سيرفر أو تتبّع</p>
        </div>
      </div>
      <div class="header-actions">
        <button class="ghost-btn" id="open-help" title="اختصارات لوحة المفاتيح" aria-label="اختصارات">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3M12 17h.01"/></svg>
        </button>
        <button class="ghost-btn" id="theme-toggle" aria-label="تبديل المظهر" title="تبديل المظهر">
          <svg class="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
          <svg class="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>
        </button>
      </div>
    </div>
  </header>

  <main>

    <section class="card">

      <nav class="tabs" role="tablist" aria-label="أنواع التوليد">
        <button class="tab is-active" role="tab" aria-selected="true" data-tab="password">كلمة سر</button>
        <button class="tab" role="tab" aria-selected="false" data-tab="passphrase">عبارة مرور</button>
        <button class="tab" role="tab" aria-selected="false" data-tab="secret">مفتاح JWT</button>
        <button class="tab" role="tab" aria-selected="false" data-tab="uuid">UUID</button>
        <span class="tab-pill" aria-hidden="true"></span>
      </nav>

      <!-- PASSWORD -->
      <div class="panel is-active" id="panel-password" role="tabpanel" aria-label="كلمة سر">

        <div class="presets" role="group" aria-label="قوالب جاهزة">
          <span class="presets-label">قوالب سريعة</span>
          <button class="preset" data-preset="balanced">متوازن</button>
          <button class="preset" data-preset="strong">قوي</button>
          <button class="preset" data-preset="paranoid">خارق</button>
          <button class="preset" data-preset="nodigits">بدون رموز</button>
          <button class="preset" data-preset="readable">سهل الكتابة</button>
          <button class="preset" data-preset="pin">PIN رقمي</button>
        </div>

        <div class="output">
          <output id="pw-out" class="output-val" dir="ltr" aria-live="polite">…</output>
          <div class="output-tools">
            <button class="icon-btn" id="pw-blur" title="إخفاء/إظهار" aria-label="إخفاء">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><path d="M2 2l20 20"/></svg>
            </button>
            <button class="icon-btn" id="pw-copy" title="نسخ" aria-label="نسخ">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
            </button>
            <button class="icon-btn" id="pw-refresh" title="توليد جديد (مسافة)" aria-label="توليد جديد">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>
            </button>
          </div>
        </div>

        <div class="meter" data-level="0" id="pw-meter">
          <div class="meter-track" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span></div>
          <div class="meter-info">
            <span class="meter-label" id="pw-label">—</span>
            <span class="meter-stats">
              <span>Entropy <b id="pw-entropy">0</b></span>
              <span>Crack <b id="pw-crack">—</b></span>
            </span>
          </div>
        </div>

        <div class="controls">
          <div class="row">
            <label for="pw-length">الطول</label>
            <input type="range" id="pw-length" min="4" max="128" value="20">
            <output id="pw-length-val" class="pill">20</output>
          </div>

          <div class="grid-checks">
            <label class="check"><input type="checkbox" id="opt-lower" checked><span class="box"></span><span>حروف صغيرة <code>a-z</code></span></label>
            <label class="check"><input type="checkbox" id="opt-upper" checked><span class="box"></span><span>حروف كبيرة <code>A-Z</code></span></label>
            <label class="check"><input type="checkbox" id="opt-digits" checked><span class="box"></span><span>أرقام <code>0-9</code></span></label>
            <label class="check"><input type="checkbox" id="opt-symbols" checked><span class="box"></span><span>رموز <code>!@#$</code></span></label>
            <label class="check"><input type="checkbox" id="opt-require" checked><span class="box"></span><span>ضمان كل نوع مختار</span></label>
            <label class="check"><input type="checkbox" id="opt-ambiguous"><span class="box"></span><span>استبعاد المتشابهة <code>lI1O0</code></span></label>
          </div>

          <div class="row row--field">
            <label for="opt-exclude">استبعاد أحرف مخصصة</label>
            <div class="input-wrap">
              <input type="text" id="opt-exclude" placeholder="مثال: @#$&quot;'`" maxlength="40" spellcheck="false" autocomplete="off">
              <span class="char-count" id="exclude-count">0/40</span>
            </div>
          </div>
        </div>

        <div class="actions">
          <button class="btn-primary" id="pw-generate">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1"/></svg>
            توليد كلمة سر
          </button>
          <button class="btn-ghost" id="pw-reset" title="استعادة الإعدادات الافتراضية">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>
            افتراضي
          </button>
        </div>
      </div>

      <!-- PASSPHRASE -->
      <div class="panel" id="panel-passphrase" role="tabpanel" aria-label="عبارة مرور" hidden>

        <div class="output">
          <output id="pp-out" class="output-val output-val--sm" dir="ltr" aria-live="polite">…</output>
          <div class="output-tools">
            <button class="icon-btn" id="pp-blur" title="إخفاء/إظهار" aria-label="إخفاء">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><path d="M2 2l20 20"/></svg>
            </button>
            <button class="icon-btn" id="pp-copy" title="نسخ" aria-label="نسخ">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
            </button>
            <button class="icon-btn" id="pp-refresh" title="توليد جديد" aria-label="توليد جديد">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>
            </button>
          </div>
        </div>

        <div class="meter" data-level="0" id="pp-meter">
          <div class="meter-track" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span></div>
          <div class="meter-info">
            <span class="meter-label" id="pp-label">—</span>
            <span class="meter-stats">
              <span>Entropy <b id="pp-entropy">0</b></span>
              <span>Crack <b id="pp-crack">—</b></span>
            </span>
          </div>
        </div>

        <div class="controls">
          <div class="row">
            <label for="pp-words">عدد الكلمات</label>
            <input type="range" id="pp-words" min="3" max="12" value="6">
            <output id="pp-words-val" class="pill">6</output>
          </div>
          <div class="row row--field">
            <label for="pp-sep">الفاصل</label>
            <select id="pp-sep">
              <option value="-">شرطة ‎-‎</option>
              <option value="_">شرطة سفلية ‎_‎</option>
              <option value=".">نقطة ‎.‎</option>
              <option value=" ">مسافة</option>
              <option value="">بدون فاصل</option>
            </select>
          </div>
          <div class="grid-checks">
            <label class="check"><input type="checkbox" id="pp-number" checked><span class="box"></span><span>إضافة رقم</span></label>
            <label class="check"><input type="checkbox" id="pp-symbol"><span class="box"></span><span>إضافة رمز</span></label>
            <label class="check"><input type="checkbox" id="pp-capitalize"><span class="box"></span><span>بدء كل كلمة بحرف كبير</span></label>
          </div>
        </div>

        <div class="actions">
          <button class="btn-primary" id="pp-generate">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1"/></svg>
            توليد عبارة مرور
          </button>
          <button class="btn-ghost" id="pp-reset">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>
            افتراضي
          </button>
        </div>
      </div>

      <!-- SECRET -->
      <div class="panel" id="panel-secret" role="tabpanel" aria-label="مفتاح JWT" hidden>

        <div class="hint">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
          <div>هذا المفتاح يوضع في <code>JWT_SECRET</code>. أي تغيير يلغي كل التوكنات السابقة فوراً.</div>
        </div>

        <div class="output output--tall">
          <output id="sc-out" class="output-val output-val--sm" dir="ltr" aria-live="polite">…</output>
          <div class="output-tools">
            <button class="icon-btn" id="sc-blur" title="إخفاء/إظهار">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><path d="M2 2l20 20"/></svg>
            </button>
            <button class="icon-btn" id="sc-copy" title="نسخ">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
            </button>
            <button class="icon-btn" id="sc-refresh" title="توليد جديد">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>
            </button>
          </div>
        </div>

        <div class="meter" data-level="0" id="sc-meter">
          <div class="meter-track" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span></div>
          <div class="meter-info">
            <span class="meter-label" id="sc-label">—</span>
            <span class="meter-stats">
              <span>Entropy <b id="sc-entropy">0</b></span>
              <span>Crack <b id="sc-crack">—</b></span>
            </span>
          </div>
        </div>

        <div class="controls">
          <div class="row row--field">
            <label for="sc-bytes">حجم العشوائية</label>
            <select id="sc-bytes">
              <option value="32">32 بايت (256 بت)</option>
              <option value="48">48 بايت (384 بت)</option>
              <option value="64" selected>64 بايت (512 بت) — الأقوى</option>
            </select>
          </div>
          <div class="row row--field">
            <label for="sc-format">صيغة الترميز</label>
            <select id="sc-format">
              <option value="base64">Base64</option>
              <option value="base64url" selected>Base64URL</option>
              <option value="hex">Hex</option>
            </select>
          </div>
        </div>

        <div class="actions">
          <button class="btn-primary" id="sc-generate">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1"/></svg>
            توليد مفتاح
          </button>
          <button class="btn-ghost" id="sc-export" title="تنزيل كملف .env">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
            تنزيل .env
          </button>
        </div>
      </div>

      <!-- UUID -->
      <div class="panel" id="panel-uuid" role="tabpanel" aria-label="UUID" hidden>

        <div class="hint">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
          <div>معرّفات فريدة عشوائية بالكامل (UUID v4) — نفس ناتج <code>crypto.randomUUID()</code>.</div>
        </div>

        <div class="controls">
          <div class="row row--field">
            <label for="uuid-count">عدد المعرّفات</label>
            <select id="uuid-count">
              <option>1</option><option>5</option>
              <option selected>10</option><option>25</option><option>50</option>
            </select>
          </div>
        </div>

        <div class="uuid-list" id="uuid-list"></div>

        <div class="actions">
          <button class="btn-primary" id="uuid-generate">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1"/></svg>
            توليد معرّفات
          </button>
          <button class="btn-ghost" id="uuid-copy-all">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
            نسخ الكل
          </button>
          <button class="btn-ghost" id="uuid-download" title="تنزيل كملف نصي">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
            تنزيل
          </button>
        </div>
      </div>

    </section>

    <section class="card card--slim">
      <div class="card-head">
        <h2>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v4l3 3"/><circle cx="12" cy="12" r="10"/></svg>
          آخر ما تم توليده
        </h2>
        <button class="btn-ghost btn-ghost--sm" id="hist-clear">تفريغ</button>
      </div>
      <ul class="history" id="history">
        <li class="history-empty">لم يتم توليد أي شيء بعد…</li>
      </ul>
      <p class="foot-note">السجل في الذاكرة فقط — يُمحى عند تحديث الصفحة</p>
    </section>

  </main>

  <footer class="site-footer">
    <div class="badges">
      <span class="badge"><span class="dot"></span><code>crypto.getRandomValues</code></span>
      <span class="badge"><span class="dot"></span>بدون شبكة أو تخزين</span>
      <span class="badge"><span class="dot"></span>مقاوم لانحياز التوزيع</span>
    </div>
    <p>كل شيء يعمل محلياً داخل المتصفح — يعمل حتى بدون اتصال بالإنترنت.</p>
  </footer>

</div>

<dialog class="modal" id="help-dialog" aria-labelledby="help-title">
  <div class="modal-head">
    <h2 id="help-title">اختصارات لوحة المفاتيح</h2>
    <button class="icon-btn" id="help-close" aria-label="إغلاق">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
    </button>
  </div>
  <ul class="shortcuts">
    <li><kbd>1</kbd><span>التبويب: كلمة سر</span></li>
    <li><kbd>2</kbd><span>التبويب: عبارة مرور</span></li>
    <li><kbd>3</kbd><span>التبويب: مفتاح JWT</span></li>
    <li><kbd>4</kbd><span>التبويب: UUID</span></li>
    <li><kbd>Space</kbd><span>توليد جديد في التبويب الحالي</span></li>
    <li><kbd>?</kbd><span>عرض هذه النافذة</span></li>
    <li><kbd>Esc</kbd><span>إغلاق</span></li>
  </ul>
</dialog>

<div class="toast" id="toast" role="status" aria-live="polite"></div>

<script>
/* ============================================================
   حِرز · Secure Password Generator
   Pure client-side · Web Crypto API · Zero network calls
   ============================================================ */
(function () {
  "use strict";

  const $  = (sel, root = document) => root.querySelector(sel);
  const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));

  /* ====== 1. WEB CRYPTO ====== */
  const cryptoObj = window.crypto || window.msCrypto;
  if (!cryptoObj || !cryptoObj.getRandomValues) {
    document.body.innerHTML =
      '<div style="padding:40px;font-family:sans-serif;text-align:center">' +
      'المتصفح لا يدعم Web Crypto API — لا يمكن توليد كلمة سر آمنة.</div>';
    return;
  }

  function randomInt(max) {
    if (!Number.isInteger(max) || max <= 0) throw new RangeError("max must be positive");
    if (max === 1) return 0;
    const limit = Math.floor(0x100000000 / max) * max;
    const buf = new Uint32Array(1);
    let x;
    do { cryptoObj.getRandomValues(buf); x = buf[0]; } while (x >= limit);
    return x % max;
  }

  const pick = (arr) => arr[randomInt(arr.length)];

  function shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
      const j = randomInt(i + 1);
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
  }

  function randomBytes(n) {
    const b = new Uint8Array(n);
    cryptoObj.getRandomValues(b);
    return b;
  }

  const toBase64 = (b) => {
    let s = "";
    for (let i = 0; i < b.length; i++) s += String.fromCharCode(b[i]);
    return btoa(s);
  };
  const toBase64Url = (b) =>
    toBase64(b).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
  const toHex = (b) => {
    let s = "";
    for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
    return s;
  };

  function uuidV4() {
    const b = randomBytes(16);
    b[6] = (b[6] & 0x0f) | 0x40;
    b[8] = (b[8] & 0x3f) | 0x80;
    const h = toHex(b);
    return h.slice(0,8)+"-"+h.slice(8,12)+"-"+h.slice(12,16)+"-"+h.slice(16,20)+"-"+h.slice(20);
  }

  /* ====== 2. ENTROPY ====== */
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
  const entropyFromPool = (len, pool) => pool <= 1 ? 0 : len * Math.log2(pool);
  const LEVEL_LABELS = {1:"ضعيفة جداً",2:"ضعيفة",3:"متوسطة",4:"قوية",5:"خارقة"};

  function strengthLevel(bits) {
    if (bits < 28) return 1;
    if (bits < 45) return 2;
    if (bits < 64) return 3;
    if (bits < 90) return 4;
    return 5;
  }

  function crackTime(bits) {
    const G = 1e12;
    const sec = Math.pow(2, bits - 1) / G;
    if (!isFinite(sec)) return "أكثر من تريليون سنة";
    if (sec < 1) return "أقل من ثانية";
    const M=60,H=3600,D=86400,Y=31557600;
    if (sec < M) return "~" + Math.round(sec) + " ث";
    if (sec < H) return "~" + Math.round(sec / M) + " دقيقة";
    if (sec < D) return "~" + Math.round(sec / H) + " ساعة";
    if (sec < Y) return "~" + Math.round(sec / D) + " يوم";
    const y = sec / Y;
    if (y < 1e3) return "~" + Math.round(y) + " سنة";
    if (y < 1e6) return "~" + Math.round(y / 1e3) + " ألف سنة";
    if (y < 1e9) return "~" + Math.round(y / 1e6) + " مليون سنة";
    if (y < 1e12) return "~" + Math.round(y / 1e9) + " مليار سنة";
    return "أكثر من تريليون سنة";
  }

  function renderMeter(prefix, bits) {
    const level = strengthLevel(bits);
    const meter = $("#" + prefix + "-meter");
    meter.dataset.level = String(level);
    $("#" + prefix + "-label").textContent = LEVEL_LABELS[level];
    $("#" + prefix + "-entropy").textContent = bits.toFixed(1) + " bits";
    $("#" + prefix + "-crack").textContent = crackTime(bits);
  }

  /* ====== 3. POOLS & WORDS ====== */
  const POOLS = {
    lower:   "abcdefghijklmnopqrstuvwxyz",
    upper:   "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    digits:  "0123456789",
    symbols: "!@#$%^&*()-_=+[]{};:,.?/",
  };
  const AMBIGUOUS = "lI1O0";
  const strip = (s, chars) => s.split("").filter((c) => !chars.includes(c)).join("");

  const WORDS = ("acorn amber angel anchor apple arrow atlas autumn azure basil " +
    "beach beacon berry birch blaze bloom blue breeze bridge bright brook cabin cactus " +
    "camel candy canyon castle cedar chalk cherry chess chill chime cider cinema citrus " +
    "clay clear cliff clove cloud clover coast cobalt cocoa comet coral cotton crane " +
    "creek crest crimson crystal cycle daisy dance dawn delta desert dew diamond dream " +
    "drift dune dusk eagle earth echo elm ember emerald falcon feather fern field fig " +
    "fire flame flint flower fog forest fossil fox frost garden gem ghost ginger glacier " +
    "glade glass globe gold grain grape grass gravel grove hail harbor harvest hawk " +
    "hazel heart heather hive honey horizon iceberg indigo iris island ivory jade " +
    "jasmine jewel journey jungle juniper kelp kite lake lantern lark laurel lava " +
    "leaf leek lemon lily lime linen lotus lunar mango maple marble marsh meadow " +
    "melon meteor midnight mint mirror mist moon moss moth mountain myth nectar nest " +
    "night noble north nova oak oasis oat ocean olive onyx opal orange orchid " +
    "orient otter owl palm pansy paper pear pearl pebble pepper petal pine pink " +
    "plum pond poppy prairie prism pulse quartz quince quiet quill rain raven reed " +
    "reef ribbon ridge river robin rock rose ruby rust sage sand sapphire scarlet " +
    "sea shadow shell shore silk silver sky slate snow solar song spark spice " +
    "spring spruce star steam steel stone storm stream summer sun swan sweet swift " +
    "thorn thunder thyme tide tiger timber topaz trail tree tulip twilight urn valley " +
    "vapor velvet vine violet vista wave wheat whisper willow wind winter wolf wonder " +
    "wood wool wren zephyr").split(" ");
  const WORD_BITS = Math.log2(WORDS.length);

  /* ====== 4. GENERATORS ====== */
  function generatePassword(opts) {
    const exclude = opts.exclude || "";
    const filter = (s) => {
      let out = s;
      if (opts.ambiguous) out = strip(out, AMBIGUOUS);
      if (exclude) out = strip(out, exclude);
      return out;
    };

    const sets = [];
    if (opts.lower)   { const s = filter(POOLS.lower);   if (s) sets.push(s); }
    if (opts.upper)   { const s = filter(POOLS.upper);   if (s) sets.push(s); }
    if (opts.digits)  { const s = filter(POOLS.digits);  if (s) sets.push(s); }
    if (opts.symbols) { const s = filter(POOLS.symbols); if (s) sets.push(s); }

    if (!sets.length) return { value: "", entropy: 0 };

    const pool = sets.join("");
    const len = opts.length;
    const chars = [];

    if (opts.require && len >= sets.length) {
      for (const s of sets) chars.push(s[randomInt(s.length)]);
      for (let i = chars.length; i < len; i++) chars.push(pool[randomInt(pool.length)]);
      shuffle(chars);
    } else {
      for (let i = 0; i < len; i++) chars.push(pool[randomInt(pool.length)]);
    }

    return { value: chars.join(""), entropy: entropyFromPool(len, pool.length) };
  }

  function generatePassphrase(o) {
    const words = [];
    for (let i = 0; i < o.count; i++) {
      let w = pick(WORDS);
      if (o.capitalize) w = w[0].toUpperCase() + w.slice(1);
      words.push(w);
    }
    let extra = 0, tail = "";
    if (o.number) { tail += String(randomInt(10)); extra += Math.log2(10); }
    if (o.symbol) {
      const syms = "!@#$%&*?";
      tail += syms[randomInt(syms.length)];
      extra += Math.log2(syms.length);
    }
    let value = words.join(o.separator);
    if (tail) value += o.separator + tail;
    return { value, entropy: o.count * WORD_BITS + extra };
  }

  function generateSecret(bytes, format) {
    const raw = randomBytes(bytes);
    let value;
    if (format === "hex") value = toHex(raw);
    else if (format === "base64") value = toBase64(raw);
    else value = toBase64Url(raw);
    return { value, entropy: bytes * 8 };
  }

  /* ====== 5. IO ====== */
  async function copyText(text, btnEl) {
    if (!text) return;
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(text);
      } else {
        const ta = document.createElement("textarea");
        ta.value = text;
        ta.setAttribute("readonly", "");
        ta.style.cssText = "position:fixed;top:-1000px;opacity:0";
        document.body.appendChild(ta);
        ta.select();
        document.execCommand("copy");
        document.body.removeChild(ta);
      }
      toast("تم النسخ بنجاح");
      if (btnEl) {
        btnEl.classList.add("is-copied");
        setTimeout(() => btnEl.classList.remove("is-copied"), 1100);
      }
    } catch (e) {
      toast("تعذّر النسخ — انسخ يدوياً", true);
    }
  }

  function downloadText(content, filename, mime) {
    const blob = new Blob([content], { type: mime || "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  /* ====== 6. TOAST ====== */
  let toastTimer = null;
  function toast(msg, isError) {
    const el = $("#toast");
    el.textContent = msg;
    el.classList.toggle("is-error", !!isError);
    el.classList.add("is-visible");
    clearTimeout(toastTimer);
    toastTimer = setTimeout(() => el.classList.remove("is-visible"), 2000);
  }

  /* ====== 7. HISTORY ====== */
  const MAX_HISTORY = 8;
  const history = [];
  const KIND_LABEL = { password:"كلمة سر", passphrase:"عبارة", secret:"JWT", uuid:"UUID" };

  function addToHistory(kind, value) {
    if (!value) return;
    history.unshift({ kind, value });
    if (history.length > MAX_HISTORY) history.length = MAX_HISTORY;
    renderHistory();
  }

  function renderHistory() {
    const list = $("#history");
    list.innerHTML = "";
    if (!history.length) {
      const li = document.createElement("li");
      li.className = "history-empty";
      li.textContent = "لم يتم توليد أي شيء بعد…";
      list.appendChild(li);
      return;
    }
    const frag = document.createDocumentFragment();
    for (const item of history) {
      const li = document.createElement("li");
      li.className = "history-item";

      const tag = document.createElement("span");
      tag.className = "history-tag";
      tag.dataset.kind = item.kind;
      tag.textContent = KIND_LABEL[item.kind] || item.kind;

      const val = document.createElement("span");
      val.className = "history-val";
      val.textContent = item.value;
      val.title = item.value;

      const btn = document.createElement("button");
      btn.className = "icon-btn";
      btn.type = "button";
      btn.title = "نسخ";
      btn.setAttribute("aria-label", "نسخ");
      btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
      btn.addEventListener("click", () => copyText(item.value, btn));

      li.append(tag, val, btn);
      frag.appendChild(li);
    }
    list.appendChild(frag);
  }

  /* ====== 8. TABS ====== */
  const tabsEl = $(".tabs");
  const pill = $(".tab-pill");
  const tabs = $$(".tab");

  function movePill(tab, instant) {
    const r = tab.getBoundingClientRect();
    const c = tabsEl.getBoundingClientRect();
    if (instant) pill.style.transition = "none";
    pill.style.width = r.width + "px";
    pill.style.transform = "translateX(" + (r.left - c.left) + "px)";
    if (instant) requestAnimationFrame(() => (pill.style.transition = ""));
  }

  function activateTab(name, instant) {
    tabs.forEach((t) => {
      const on = t.dataset.tab === name;
      t.classList.toggle("is-active", on);
      t.setAttribute("aria-selected", on ? "true" : "false");
    });
    $$(".panel").forEach((p) => {
      const on = p.id === "panel-" + name;
      p.hidden = !on;
      p.classList.toggle("is-active", on);
    });
    const a = tabs.find((t) => t.dataset.tab === name);
    if (a) movePill(a, instant);
  }

  tabs.forEach((t) => t.addEventListener("click", () => activateTab(t.dataset.tab)));
  window.addEventListener("resize", () => {
    const a = tabs.find((t) => t.classList.contains("is-active"));
    if (a) movePill(a, true);
  });

  /* ====== 9. PERSISTENCE ====== */
  const STORE_KEY = "heirz.settings.v1";

  function readAllSettings() {
    return {
      theme: document.documentElement.getAttribute("data-theme"),
      password: {
        length: +$("#pw-length").value,
        lower: $("#opt-lower").checked,
        upper: $("#opt-upper").checked,
        digits: $("#opt-digits").checked,
        symbols: $("#opt-symbols").checked,
        ambiguous: $("#opt-ambiguous").checked,
        require: $("#opt-require").checked,
        exclude: $("#opt-exclude").value,
      },
      passphrase: {
        count: +$("#pp-words").value,
        separator: $("#pp-sep").value,
        number: $("#pp-number").checked,
        symbol: $("#pp-symbol").checked,
        capitalize: $("#pp-capitalize").checked,
      },
      secret: {
        bytes: $("#sc-bytes").value,
        format: $("#sc-format").value,
      },
      uuid: { count: $("#uuid-count").value },
    };
  }

  function saveSettings() {
    try { localStorage.setItem(STORE_KEY, JSON.stringify(readAllSettings())); } catch (e) {}
  }

  function loadSettings() {
    let s;
    try { s = JSON.parse(localStorage.getItem(STORE_KEY) || "null"); } catch (e) { s = null; }
    if (!s) return;
    if (s.theme) document.documentElement.setAttribute("data-theme", s.theme);

    if (s.password) {
      const p = s.password;
      if (typeof p.length === "number") $("#pw-length").value = clamp(p.length, 4, 128);
      ["lower","upper","digits","symbols","ambiguous","require"].forEach((k) => {
        if (typeof p[k] === "boolean") $("#opt-" + k).checked = p[k];
      });
      if (typeof p.exclude === "string") $("#opt-exclude").value = p.exclude;
    }
    if (s.passphrase) {
      const p = s.passphrase;
      if (typeof p.count === "number") $("#pp-words").value = clamp(p.count, 3, 12);
      if (typeof p.separator === "string") $("#pp-sep").value = p.separator;
      ["number","symbol","capitalize"].forEach((k) => {
        if (typeof p[k] === "boolean") $("#pp-" + k).checked = p[k];
      });
    }
    if (s.secret) {
      if (s.secret.bytes) $("#sc-bytes").value = s.secret.bytes;
      if (s.secret.format) $("#sc-format").value = s.secret.format;
    }
    if (s.uuid && s.uuid.count) $("#uuid-count").value = s.uuid.count;
  }

  /* ====== 10. PASSWORD ====== */
  const pwLength = $("#pw-length");
  const pwLengthVal = $("#pw-length-val");

  function syncRange(el) {
    const min = +el.min, max = +el.max, val = +el.value;
    el.style.setProperty("--pct", ((val - min) / (max - min) * 100) + "%");
  }

  function readPasswordOpts() {
    return {
      length: +pwLength.value,
      lower: $("#opt-lower").checked,
      upper: $("#opt-upper").checked,
      digits: $("#opt-digits").checked,
      symbols: $("#opt-symbols").checked,
      ambiguous: $("#opt-ambiguous").checked,
      require: $("#opt-require").checked,
      exclude: ($("#opt-exclude").value || "").trim(),
    };
  }

  function doGeneratePassword(push) {
    const { value, entropy } = generatePassword(readPasswordOpts());
    if (!value) {
      $("#pw-out").textContent = "اختر نوع حرف واحد على الأقل";
      renderMeter("pw", 0);
      return;
    }
    $("#pw-out").textContent = value;
    renderMeter("pw", entropy);
    if (push) addToHistory("password", value);
    saveSettings();
  }

  pwLength.addEventListener("input", () => {
    pwLengthVal.textContent = pwLength.value;
    syncRange(pwLength);
    doGeneratePassword(false);
  });

  ["opt-lower","opt-upper","opt-digits","opt-symbols","opt-ambiguous","opt-require"]
    .forEach((id) => $("#" + id).addEventListener("input", () => doGeneratePassword(false)));

  $("#opt-exclude").addEventListener("input", (e) => {
    $("#exclude-count").textContent = e.target.value.length + "/40";
    doGeneratePassword(false);
  });

  $("#pw-generate").addEventListener("click", () => doGeneratePassword(true));
  $("#pw-refresh").addEventListener("click", () => doGeneratePassword(true));
  $("#pw-copy").addEventListener("click", (e) => copyText($("#pw-out").textContent, e.currentTarget));
  $("#pw-blur").addEventListener("click", () => toggleBlur("#pw-out", "#pw-blur"));
  $("#pw-reset").addEventListener("click", () => {
    applyPreset("balanced");
    toast("تم استعادة الافتراضي");
  });

  const PRESETS = {
    balanced: { length:16, lower:1, upper:1, digits:1, symbols:1, ambiguous:0, require:1 },
    strong:   { length:24, lower:1, upper:1, digits:1, symbols:1, ambiguous:0, require:1 },
    paranoid: { length:48, lower:1, upper:1, digits:1, symbols:1, ambiguous:0, require:1 },
    nodigits: { length:20, lower:1, upper:1, digits:1, symbols:0, ambiguous:0, require:1 },
    readable: { length:20, lower:1, upper:1, digits:1, symbols:0, ambiguous:1, require:1 },
    pin:      { length:8,  lower:0, upper:0, digits:1, symbols:0, ambiguous:0, require:0 },
  };

  function applyPreset(name) {
    const p = PRESETS[name];
    if (!p) return;
    pwLength.value = p.length;
    pwLengthVal.textContent = p.length;
    $("#opt-lower").checked     = !!p.lower;
    $("#opt-upper").checked     = !!p.upper;
    $("#opt-digits").checked    = !!p.digits;
    $("#opt-symbols").checked   = !!p.symbols;
    $("#opt-ambiguous").checked = !!p.ambiguous;
    $("#opt-require").checked   = !!p.require;
    $("#opt-exclude").value = "";
    $("#exclude-count").textContent = "0/40";
    syncRange(pwLength);
    doGeneratePassword(false);
    $$(".preset").forEach((b) => b.classList.toggle("is-active", b.dataset.preset === name));
  }

  $$(".preset").forEach((b) => b.addEventListener("click", () => applyPreset(b.dataset.preset)));

  /* ====== 11. PASSPHRASE ====== */
  const ppWords = $("#pp-words");
  const ppWordsVal = $("#pp-words-val");

  function readPassphraseOpts() {
    return {
      count: +ppWords.value,
      separator: $("#pp-sep").value,
      number: $("#pp-number").checked,
      symbol: $("#pp-symbol").checked,
      capitalize: $("#pp-capitalize").checked,
    };
  }

  function doGeneratePassphrase(push) {
    const { value, entropy } = generatePassphrase(readPassphraseOpts());
    $("#pp-out").textContent = value;
    renderMeter("pp", entropy);
    if (push) addToHistory("passphrase", value);
    saveSettings();
  }

  ppWords.addEventListener("input", () => {
    ppWordsVal.textContent = ppWords.value;
    syncRange(ppWords);
    doGeneratePassphrase(false);
  });

  ["pp-sep","pp-number","pp-symbol","pp-capitalize"]
    .forEach((id) => $("#" + id).addEventListener("change", () => doGeneratePassphrase(false)));

  $("#pp-generate").addEventListener("click", () => doGeneratePassphrase(true));
  $("#pp-refresh").addEventListener("click", () => doGeneratePassphrase(true));
  $("#pp-copy").addEventListener("click", (e) => copyText($("#pp-out").textContent, e.currentTarget));
  $("#pp-blur").addEventListener("click", () => toggleBlur("#pp-out", "#pp-blur"));
  $("#pp-reset").addEventListener("click", () => {
    ppWords.value = 6;
    ppWordsVal.textContent = "6";
    $("#pp-sep").value = "-";
    $("#pp-number").checked = true;
    $("#pp-symbol").checked = false;
    $("#pp-capitalize").checked = false;
    syncRange(ppWords);
    doGeneratePassphrase(false);
    toast("تم استعادة الافتراضي");
  });

  /* ====== 12. SECRET ====== */
  function doGenerateSecret(push) {
    const bytes = +$("#sc-bytes").value;
    const format = $("#sc-format").value;
    const { value, entropy } = generateSecret(bytes, format);
    $("#sc-out").textContent = value;
    renderMeter("sc", entropy);
    if (push) addToHistory("secret", value);
    saveSettings();
  }

  $("#sc-bytes").addEventListener("change", () => doGenerateSecret(false));
  $("#sc-format").addEventListener("change", () => doGenerateSecret(false));
  $("#sc-generate").addEventListener("click", () => doGenerateSecret(true));
  $("#sc-refresh").addEventListener("click", () => doGenerateSecret(true));
  $("#sc-copy").addEventListener("click", (e) => copyText($("#sc-out").textContent, e.currentTarget));
  $("#sc-blur").addEventListener("click", () => toggleBlur("#sc-out", "#sc-blur"));

  $("#sc-export").addEventListener("click", () => {
    const secret = $("#sc-out").textContent;
    const content =
      "# Generated by Heirz Password Generator\n" +
      "# " + new Date().toISOString() + "\n" +
      "# Keep this file out of version control.\n\n" +
      "JWT_SECRET=" + secret + "\n";
    downloadText(content, "jwt-secret.env");
    toast("تم تنزيل .env");
  });

  /* ====== 13. UUID ====== */
  const uuidList = $("#uuid-list");
  let currentUuids = [];

  function doGenerateUuids(push) {
    const count = +$("#uuid-count").value;
    currentUuids = [];
    uuidList.innerHTML = "";
    const frag = document.createDocumentFragment();

    for (let i = 0; i < count; i++) {
      const id = uuidV4();
      currentUuids.push(id);

      const row = document.createElement("div");
      row.className = "uuid-row";
      row.style.animationDelay = Math.min(i * 20, 300) + "ms";

      const code = document.createElement("code");
      code.textContent = id;

      const btn = document.createElement("button");
      btn.className = "icon-btn";
      btn.type = "button";
      btn.title = "نسخ";
      btn.setAttribute("aria-label", "نسخ");
      btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
      btn.addEventListener("click", () => copyText(id, btn));

      row.append(code, btn);
      frag.appendChild(row);
    }
    uuidList.appendChild(frag);

    if (push && currentUuids[0]) {
      addToHistory("uuid", currentUuids[0] + (count > 1 ? "  (+" + (count - 1) + ")" : ""));
    }
    saveSettings();
  }

  $("#uuid-generate").addEventListener("click", () => doGenerateUuids(true));
  $("#uuid-count").addEventListener("change", () => doGenerateUuids(false));
  $("#uuid-copy-all").addEventListener("click", (e) => copyText(currentUuids.join("\n"), e.currentTarget));
  $("#uuid-download").addEventListener("click", () => {
    if (!currentUuids.length) return;
    downloadText(currentUuids.join("\n") + "\n", "uuids.txt");
    toast("تم التنزيل");
  });

  /* ====== 14. BLUR ====== */
  function toggleBlur(outputSel, btnSel) {
    const out = $(outputSel);
    const btn = $(btnSel);
    const nowBlurred = out.classList.toggle("is-blurred");
    btn.setAttribute("aria-label", nowBlurred ? "إظهار" : "إخفاء");
    btn.title = nowBlurred ? "إظهار" : "إخفاء";
  }

  /* ====== 15. CLEAR HISTORY ====== */
  $("#hist-clear").addEventListener("click", () => {
    history.length = 0;
    renderHistory();
    toast("تم تفريغ السجل");
  });

  /* ====== 16. THEME ====== */
  function setTheme(theme) {
    document.documentElement.setAttribute("data-theme", theme);
    const meta = document.querySelector('meta[name="theme-color"]');
    if (meta) meta.setAttribute("content", theme === "light" ? "#f5f8fc" : "#0b0f19");
    saveSettings();
  }

  $("#theme-toggle").addEventListener("click", () => {
    const cur = document.documentElement.getAttribute("data-theme");
    setTheme(cur === "dark" ? "light" : "dark");
  });

  /* ====== 17. HELP ====== */
  const helpDialog = $("#help-dialog");
  $("#open-help").addEventListener("click", () => helpDialog.showModal());
  $("#help-close").addEventListener("click", () => helpDialog.close());
  helpDialog.addEventListener("click", (e) => {
    if (e.target === helpDialog) helpDialog.close();
  });

  /* ====== 18. SHORTCUTS ====== */
  document.addEventListener("keydown", (e) => {
    const tag = (e.target.tagName || "").toLowerCase();
    const typing = tag === "input" || tag === "select" || tag === "textarea";
    const inDialog = !!document.activeElement.closest("dialog");

    if (e.key === "?" && !typing && !inDialog) {
      e.preventDefault();
      helpDialog.showModal();
      return;
    }
    if (e.code === "Space" && !typing && !inDialog) {
      e.preventDefault();
      const active = tabs.find((t) => t.classList.contains("is-active"));
      if (!active) return;
      const map = { password:"#pw-generate", passphrase:"#pp-generate", secret:"#sc-generate", uuid:"#uuid-generate" };
      const btn = $(map[active.dataset.tab]);
      if (btn) btn.click();
    }
    if (!typing && !inDialog && ["1","2","3","4"].includes(e.key)) {
      const names = ["password", "passphrase", "secret", "uuid"];
      const idx = +e.key - 1;
      if (names[idx]) activateTab(names[idx]);
    }
  });

  /* ====== 19. INIT ====== */
  function init() {
    let saved;
    try { saved = localStorage.getItem(STORE_KEY); } catch (e) {}
    if (!saved) {
      const preferLight = window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches;
      document.documentElement.setAttribute("data-theme", preferLight ? "light" : "dark");
    }
    loadSettings();

    syncRange(pwLength);
    syncRange(ppWords);
    $("#exclude-count").textContent = ($("#opt-exclude").value || "").length + "/40";

    doGeneratePassword(false);
    doGeneratePassphrase(false);
    doGenerateSecret(false);
    doGenerateUuids(false);

    activateTab("password", true);
    requestAnimationFrame(() => {
      const a = tabs.find((t) => t.classList.contains("is-active"));
      if (a) movePill(a, true);
    });

    if (document.fonts && document.fonts.ready) {
      document.fonts.ready.then(() => {
        const a = tabs.find((t) => t.classList.contains("is-active"));
        if (a) movePill(a, true);
      });
    }
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();
</script>
</body>
</html>

التعليقات (0)

سجّل الدخول للتعليق.