أداء عمل باسوردات
htmlv1MITنُشر ٤ أكتوبر ٢٠٢٦· ♥ 0 · ⬇ 1 · 💬 0
- الهدف: توفير أداة عربية آمنة وسهلة لتوليد كلمات سر قوية بدون الاعتماد على أي موقع خارجي. 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="مثال: @#$"'`" 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)
سجّل الدخول للتعليق.