Spaces:
Running
Running
Download index.html from Ghada-99-Ragab/Finallll: direct link, hf CLI and curl.
- Browser
- Download file 42.9 kB
-
https://huggingface.co/spaces/Ghada-99-Ragab/Finallll/resolve/main/index.html
- Command line
-
hf download hf://spaces/Ghada-99-Ragab/Finallll/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Ghada-99-Ragab/Finallll/resolve/main/index.html
42.9 kB
| <html lang="ar" dir="rtl"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>التحقق من هلوسة القرآن والحديث وتصحيحها</title> | |
| <meta name="description" content="تحقّق من آيات القرآن والأحاديث النبوية بالدليل"> | |
| <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link rel="preload" href="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js" as="script" crossorigin> | |
| <link rel="preload" href="index/quran.idx.gz" as="fetch" crossorigin> | |
| <link rel="prefetch" href="index/hadith.idx.gz" as="fetch" crossorigin> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&family=Amiri:wght@400;700&display=swap'); | |
| :root { --green:#0F4C3A; --green-2:#17694F; --gold:#B8912F; --gold-2:#E2C06E; --cream:#FBF6EA; --paper:#FFFFFF; | |
| --ink:#1F2933; --muted:#55626D; --line:#E6DCC3; --ok:#1B7A4B; --ok-bg:#EAF6EF; --ok-line:#BFE3CD; | |
| --bad:#B3261E; --bad-bg:#FDECEA; --bad-line:#F4B8B3; --warn:#7A5B0C; --warn-bg:#FFF6DA; --warn-line:#EBD28A; } | |
| .gradio-container, body.icv-page { background:var(--cream) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='88' height='88' viewBox='0 0 88 88'%3E%3Cg fill='none' stroke='%230F4C3A' stroke-opacity='0.07' stroke-width='1'%3E%3Crect x='22' y='22' width='44' height='44'/%3E%3Crect x='22' y='22' width='44' height='44' transform='rotate(45 44 44)'/%3E%3C/g%3E%3C/svg%3E") ; font-family:'Cairo','Segoe UI',Tahoma,sans-serif; color:var(--ink); } | |
| .gradio-container { max-width:1060px ; --body-text-color:#1F2933; --block-background-fill:#FFFFFF; --block-border-color:#E6DCC3; | |
| --input-background-fill:#FFFFFF; --block-label-text-color:#55626D; --button-primary-background-fill:#0F4C3A; | |
| --button-primary-background-fill-hover:#17694F; --button-primary-text-color:#FFFFFF; --button-secondary-background-fill:#FFFFFF; | |
| --button-secondary-text-color:#0F4C3A; --button-secondary-border-color:#B8912F; --color-accent:#B8912F; } | |
| .icv { direction:rtl; text-align:right; color:var(--ink); line-height:1.9; } | |
| .icv .hero { background:linear-gradient(135deg,#0F4C3A,#17694F); border-radius:20px; padding:30px 22px 26px; margin:10px 0 18px; text-align:center; | |
| box-shadow:0 6px 22px rgba(15,76,58,.18); border-bottom:4px solid var(--gold); } | |
| .icv .hero .mark { width:54px; height:54px; display:block; margin:0 auto 4px; } | |
| .icv .hero h1 { font-size:2.3rem; margin:.1rem 0; color:#FFFFFF; font-weight:700; } | |
| .icv .hero .tagline { color:var(--gold-2); font-size:1.2rem; font-weight:600; margin:.1rem 0 .6rem; } | |
| .icv .hero .sub { max-width:720px; margin:0 auto; color:#E9F2EE; font-size:1rem; } | |
| .icv .flow { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:8px; margin-top:16px; } | |
| .icv .flow span { background:rgba(255,255,255,.12); border:1px solid rgba(226,192,110,.55); color:#FFF; border-radius:999px; padding:3px 16px; font-size:.9rem; } | |
| .icv .flow i { color:var(--gold-2); font-style:normal; font-size:1.2rem; } | |
| .icv .disclaimer { font-size:.85rem; color:var(--muted); text-align:center; padding:14px 8px; } | |
| .icv .en-free { direction:rtl; } | |
| .input-area textarea { direction:rtl; text-align:right; font-family:'Amiri','Cairo',serif ; font-size:1.25rem ; line-height:2.1 ; background:#FFFFFF ; color:#1F2933 ; } | |
| .llm-row label, .gradio-container label span { font-family:'Cairo',sans-serif; } | |
| .results { direction:rtl; } | |
| .icv .summary { display:grid; grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); gap:10px; margin:8px 0 14px; } | |
| .icv .tile { background:var(--paper); border:1px solid var(--line); border-radius:14px; padding:12px 8px; text-align:center; } | |
| .icv .tile b { display:block; font-size:1.8rem; color:var(--green); line-height:1.3; } .icv .tile span { font-size:.92rem; color:var(--muted); } | |
| .icv .tile.verified { background:var(--ok-bg); border-color:var(--ok-line); } .icv .tile.verified b { color:var(--ok); } | |
| .icv .tile.mismatch { background:var(--bad-bg); border-color:var(--bad-line); } .icv .tile.mismatch b { color:var(--bad); } | |
| .icv .tile.review { background:var(--warn-bg); border-color:var(--warn-line); } .icv .tile.review b { color:var(--warn); } | |
| .icv .tile.zero { background:var(--paper); border-color:var(--line); opacity:.6; } .icv .tile.zero b { color:var(--muted); } | |
| .icv .highlight, .icv .generated, .icv .final { background:var(--paper); border:1px solid var(--line); border-radius:14px; padding:14px 18px; margin-bottom:14px; } | |
| .icv label { display:block; color:var(--muted); font-size:.82rem; font-weight:600; margin-bottom:4px; } | |
| .icv .highlight p, .icv .generated p, .icv .final-text { font-family:'Amiri','Cairo',serif; font-size:1.2rem; line-height:2.2; margin:0; white-space:pre-wrap; } | |
| .icv mark { color:var(--ink); border-radius:6px; padding:1px 5px; } | |
| .icv mark.verified { background:#D5EEDF; } .icv mark.mismatch { background:#F8CFCB; } .icv mark.review { background:#F7E3A6; } | |
| .icv .hl-legend { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; font-size:.78rem; } | |
| .icv .qcard { background:var(--paper); border:1px solid var(--line); border-inline-start:6px solid var(--line); border-radius:16px; padding:16px 18px; margin:12px 0; } | |
| .icv .qcard.verified { border-inline-start-color:var(--ok); } .icv .qcard.mismatch { border-inline-start-color:var(--bad); } .icv .qcard.review { border-inline-start-color:var(--gold); } | |
| .icv .qhead { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:8px; } | |
| .icv .idx { background:var(--green); color:#FFF; border-radius:50%; width:28px; height:28px; display:inline-flex; align-items:center; justify-content:center; font-weight:700; font-size:.9rem; } | |
| .icv .badge { padding:2px 14px; border-radius:999px; font-size:.84rem; background:#F3EEDD; border:1px solid var(--line); color:var(--ink); } | |
| .icv .badge.st.verified { background:var(--ok-bg); border-color:var(--ok-line); color:var(--ok); font-weight:600; } | |
| .icv .badge.st.mismatch { background:var(--bad-bg); border-color:var(--bad-line); color:var(--bad); font-weight:600; } | |
| .icv .badge.st.review { background:var(--warn-bg); border-color:var(--warn-line); color:var(--warn); font-weight:600; } | |
| .icv .badge.scan { background:#EEF3FB; border-color:#C9D8EE; color:#2F4F7F; } | |
| .icv .conf { margin-inline-start:auto; color:var(--muted); font-size:.88rem; } .icv .conf b { color:var(--green); } | |
| .icv .field { margin:8px 0; } .icv .field p { margin:0; } | |
| .icv .quote { font-family:'Amiri','Cairo',serif; font-size:1.25rem; line-height:2.2; color:var(--ink); } .icv .quote.small { font-size:1.05rem; color:#3a4651; } | |
| .icv .diff { font-family:'Amiri','Cairo',serif; font-size:1.2rem; line-height:2.2; } | |
| .icv .w-extra { background:var(--bad-bg); color:var(--bad); border-radius:4px; padding:0 4px; text-decoration:line-through; } | |
| .icv .w-missing { background:var(--ok-bg); color:var(--ok); border-radius:4px; padding:0 4px; font-weight:700; } | |
| .icv .legend { color:var(--muted); font-size:.82rem; margin:4px 0 0; } .icv .legend span { text-decoration:none; font-size:.8rem; } | |
| .icv .reason { color:var(--muted); font-size:.92rem; margin:6px 0 0; } | |
| .icv .note { color:var(--warn); background:var(--warn-bg); border:1px solid var(--warn-line); border-radius:10px; padding:5px 12px; font-size:.88rem; margin:8px 0 0; } | |
| .icv .action { border-radius:12px; padding:10px 14px; margin-top:10px; } | |
| .icv .action.ok { background:var(--ok-bg); border:1px solid var(--ok-line); } .icv .action.ok b { color:var(--ok); } | |
| .icv .action.warn { background:var(--warn-bg); border:1px solid var(--warn-line); } .icv .action.warn b { color:var(--warn); } | |
| .icv .action.bad { background:var(--bad-bg); border:1px solid var(--bad-line); } .icv .action.bad b { color:var(--bad); } | |
| .icv .evidence { margin-top:12px; border-top:1px dashed var(--line); padding-top:8px; } | |
| .icv .evidence summary { cursor:pointer; color:var(--green); font-weight:700; } | |
| .icv .ev-row { margin:10px 0; } .icv .ev-row b { color:var(--gold); font-size:.88rem; } .icv .ev-row p { margin:2px 0; } | |
| .icv .indicators { display:grid; gap:6px; margin-top:6px; } | |
| .icv .ind { display:grid; grid-template-columns:150px 1fr 48px; gap:10px; align-items:center; font-size:.88rem; } | |
| .icv .bar { background:#EFE8D3; border-radius:999px; height:9px; overflow:hidden; direction:rtl; } .icv .bar span { display:block; height:100%; background:linear-gradient(270deg,var(--green),var(--gold)); } | |
| .icv .ind-val { text-align:left; direction:ltr; color:var(--ink); } .icv .ind-val.wide { grid-column:2 / span 2; text-align:right; direction:rtl; } | |
| .icv .final { border-color:var(--gold); background:#FFFDF6; } | |
| .icv .final-head { display:flex; justify-content:space-between; align-items:center; } .icv .final-head b { color:var(--green); font-size:1.05rem; } | |
| .icv .copy { background:var(--green); color:#FFF; border:0; border-radius:10px; padding:5px 16px; font-family:inherit; cursor:pointer; } | |
| .icv .notice { background:var(--paper); border:1px solid var(--line); border-radius:14px; padding:16px; } | |
| .icv .notice.warn { background:var(--warn-bg); border-color:var(--warn-line); color:var(--warn); } .icv .notice.bad { background:var(--bad-bg); border-color:var(--bad-line); color:var(--bad); } | |
| @media (max-width:640px){ .icv .hero h1{font-size:1.7rem;} .icv .ind{grid-template-columns:104px 1fr 40px;} } | |
| :root { --glass:rgba(255,255,255,.62); --glass-strong:rgba(255,255,255,.82); --glass-line:rgba(255,255,255,.75); | |
| --shadow-1:0 1px 2px rgba(15,76,58,.06), 0 8px 24px rgba(15,76,58,.08); --shadow-2:0 2px 4px rgba(15,76,58,.08), 0 18px 44px rgba(15,76,58,.16); } | |
| .gradio-container, body.icv-page { background: | |
| radial-gradient(900px 520px at 88% -8%, rgba(226,192,110,.34), transparent 60%), | |
| radial-gradient(760px 520px at 6% 4%, rgba(23,105,79,.20), transparent 62%), | |
| linear-gradient(180deg,#FBF6EA 0%,#F3EBD3 100%) ; background-attachment:fixed ; } | |
| /* RTL safety: long words, URLs and mixed Latin/digit runs wrap inside their box instead of spilling out */ | |
| .icv, .icv * { box-sizing:border-box; min-width:0; } | |
| .icv p, .icv span, .icv b, .icv label, .icv summary, .icv button, .icv .badge, .icv .tile { overflow-wrap:anywhere; } | |
| .icv .quote, .icv .diff, .icv .final-text, .icv .highlight p, .icv .generated p { unicode-bidi:plaintext; text-align:start; } | |
| .icv .hero { position:relative; overflow:hidden; border-bottom:0; border:1px solid rgba(226,192,110,.45); | |
| background:linear-gradient(120deg,#0B3B2D,#17694F 45%,#0F4C3A 70%,#1d7a5c); background-size:240% 240%; animation:icv-flow 16s ease-in-out infinite; | |
| box-shadow:var(--shadow-2); } | |
| .icv .hero::before { content:""; position:absolute; inset:-40% -10% auto auto; width:60%; aspect-ratio:1; border-radius:50%; | |
| background:radial-gradient(circle, rgba(226,192,110,.38), transparent 65%); pointer-events:none; } | |
| .icv .hero::after { content:""; position:absolute; inset:auto auto 0 0; width:100%; height:4px; background:linear-gradient(90deg,transparent,var(--gold-2),transparent); } | |
| .icv .hero h1 { font-size:clamp(1.45rem,4.2vw,2.3rem); line-height:1.5; text-wrap:balance; position:relative; } | |
| .icv .hero .tagline, .icv .hero .sub, .icv .flow { position:relative; } | |
| .icv .flow span { backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); } | |
| @keyframes icv-flow { 0%,100%{background-position:0% 50%} 50%{background-position:100% 50%} } | |
| .icv .tile, .icv .highlight, .icv .generated, .icv .final, .icv .qcard, .icv .notice, .icv .export { | |
| background:var(--glass); border:1px solid var(--glass-line); box-shadow:var(--shadow-1); | |
| backdrop-filter:blur(14px) saturate(150%); -webkit-backdrop-filter:blur(14px) saturate(150%); transition:transform .25s ease, box-shadow .25s ease; } | |
| .icv .qcard { border-inline-start:6px solid var(--line); } | |
| .icv .qcard:hover, .icv .tile:hover { transform:translateY(-2px); box-shadow:var(--shadow-2); } | |
| .icv .tile.verified { background:linear-gradient(160deg,rgba(234,246,239,.92),rgba(255,255,255,.6)); } | |
| .icv .tile.mismatch { background:linear-gradient(160deg,rgba(253,236,234,.92),rgba(255,255,255,.6)); } | |
| .icv .tile.review { background:linear-gradient(160deg,rgba(255,246,218,.95),rgba(255,255,255,.6)); } | |
| .icv .final { background:linear-gradient(160deg,rgba(255,253,246,.92),rgba(250,240,208,.55)); border-color:rgba(184,145,47,.55); } | |
| .icv .final-head { flex-wrap:wrap; gap:10px; } | |
| .icv .qhead .badge { max-width:100%; white-space:normal; } | |
| .icv .ind { grid-template-columns:minmax(96px,150px) minmax(0,1fr) 48px; } | |
| .icv .bar span { background:linear-gradient(270deg,var(--green),var(--gold-2)); transition:width .6s ease; } | |
| .icv .copy, .icv .dl { background:linear-gradient(135deg,var(--green),var(--green-2)); color:#FFF; border:0; border-radius:12px; padding:7px 18px; | |
| font-family:inherit; font-weight:600; cursor:pointer; box-shadow:0 4px 12px rgba(15,76,58,.25); transition:transform .15s ease, box-shadow .15s ease, filter .15s ease; } | |
| .icv .copy:hover, .icv .dl:hover { transform:translateY(-1px); filter:brightness(1.08); box-shadow:0 8px 18px rgba(15,76,58,.3); } | |
| .icv .copy:active, .icv .dl:active { transform:translateY(0); } | |
| .icv .copy:focus-visible, .icv .dl:focus-visible { outline:3px solid rgba(184,145,47,.55); outline-offset:2px; } | |
| .icv .copy.done { background:linear-gradient(135deg,#1B7A4B,#2a9d66); } | |
| .icv .export { border-radius:16px; padding:12px 18px; margin:12px 0; } | |
| .icv .export summary { cursor:pointer; color:var(--green); font-weight:700; } | |
| .icv .dls { display:flex; flex-wrap:wrap; gap:10px; margin-top:10px; } | |
| /* toast */ | |
| #icv-toast { position:fixed; inset-inline:0; bottom:28px; margin-inline:auto; width:max-content; max-width:calc(100vw - 32px); z-index:99999; | |
| direction:rtl; text-align:center; font:600 1rem 'Cairo','Segoe UI',Tahoma,sans-serif; color:#FFF; padding:12px 24px; border-radius:999px; | |
| background:linear-gradient(135deg,rgba(15,76,58,.94),rgba(23,105,79,.94)); border:1px solid rgba(226,192,110,.6); | |
| box-shadow:0 14px 40px rgba(15,76,58,.35); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px); | |
| opacity:0; transform:translateY(18px) scale(.97); pointer-events:none; transition:opacity .28s ease, transform .28s cubic-bezier(.2,.9,.3,1.2); } | |
| #icv-toast.show { opacity:1; transform:none; } #icv-toast.bad { background:linear-gradient(135deg,rgba(179,38,30,.95),rgba(214,69,58,.95)); } | |
| /* skeleton loaders */ | |
| .skel { position:relative; overflow:hidden; background:rgba(15,76,58,.08); border-radius:10px; } | |
| .skel::after { content:""; position:absolute; inset:0; transform:translateX(100%); animation:icv-shimmer 1.4s infinite; | |
| background:linear-gradient(90deg,transparent,rgba(255,255,255,.75),transparent); } | |
| @keyframes icv-shimmer { 100% { transform:translateX(-100%); } } | |
| .skel-card { background:var(--glass); border:1px solid var(--glass-line); border-radius:16px; padding:16px 18px; margin:12px 0; box-shadow:var(--shadow-1); } | |
| .skel-line { height:14px; margin:10px 0; } .skel-line.w60 { width:60%; } .skel-line.w85 { width:85%; } .skel-line.w40 { width:40%; } | |
| .skel-tiles { display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:10px; margin:8px 0 14px; } .skel-tile { height:78px; border-radius:14px; } | |
| .icv-spinner { width:18px; height:18px; border-radius:50%; border:3px solid rgba(15,76,58,.18); border-top-color:var(--gold); display:inline-block; | |
| vertical-align:middle; margin-inline-end:10px; animation:icv-spin .8s linear infinite; } | |
| @keyframes icv-spin { to { transform:rotate(360deg); } } | |
| @media (prefers-reduced-motion: reduce) { .icv .hero, .skel::after, .icv-spinner { animation:none; } .icv .qcard, .icv .tile, #icv-toast { transition:none; } } | |
| @media (max-width:640px){ .icv .ind{grid-template-columns:minmax(84px,104px) minmax(0,1fr) 40px;} .icv .final-head{flex-direction:column; align-items:stretch;} .icv .copy{width:100%;} } | |
| body.icv-page{margin:0;min-height:100vh;} .page{max-width:1060px;margin:0 auto;padding:10px 16px 28px;box-sizing:border-box;} | |
| .glass{background:var(--glass);border:1px solid var(--glass-line);border-radius:20px;box-shadow:var(--shadow-1); | |
| backdrop-filter:blur(16px) saturate(150%);-webkit-backdrop-filter:blur(16px) saturate(150%);} | |
| .tabs{display:flex;gap:6px;margin:6px 0 14px;direction:rtl;padding:6px;border-radius:18px;} | |
| .tab{flex:1;border:0;background:transparent;color:var(--green);border-radius:13px;padding:11px 14px;font:700 1rem 'Cairo',sans-serif;cursor:pointer;transition:background .2s,color .2s,box-shadow .2s;} | |
| .tab:hover{background:rgba(15,76,58,.07);} | |
| .tab.active{background:linear-gradient(135deg,var(--green),var(--green-2));color:#FFF;box-shadow:0 6px 16px rgba(15,76,58,.28);} | |
| .tab:focus-visible,.btns button:focus-visible{outline:3px solid rgba(184,145,47,.55);outline-offset:2px;} | |
| section[hidden]{display:none;} | |
| .form{padding:18px 20px 14px;margin-bottom:14px;} | |
| .form label{display:block;color:var(--muted);font-size:.88rem;font-weight:600;margin:10px 0 5px;direction:rtl;text-align:right;} | |
| .form textarea,.form select{width:100%;box-sizing:border-box;background:rgba(255,255,255,.88);color:var(--ink);border:1px solid var(--line);border-radius:14px;padding:12px 14px;font:1rem 'Cairo',sans-serif;direction:rtl;text-align:right;outline:none;transition:border-color .2s,box-shadow .2s;} | |
| .form textarea{min-height:200px;resize:vertical;font-family:'Amiri','Cairo',serif;font-size:1.25rem;line-height:2.1;} | |
| .form textarea.short{min-height:96px;} | |
| .form textarea:focus,.form select:focus{border-color:var(--gold);box-shadow:0 0 0 4px rgba(184,145,47,.18);} | |
| .engine{display:grid;grid-template-columns:minmax(0,1fr);gap:4px;margin-bottom:6px;} | |
| .engine select{direction:ltr;text-align:left;font-family:'Segoe UI',Tahoma,Arial,sans-serif;appearance:none;-webkit-appearance:none;cursor:pointer; | |
| background:rgba(255,255,255,.88) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'%3E%3Cpath d='M1 1l6 6 6-6' fill='none' stroke='%230F4C3A' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 14px center;padding-right:38px;} | |
| .engine label{direction:ltr;text-align:left;font-family:'Segoe UI',Tahoma,Arial,sans-serif;} | |
| .hint{font-size:.84rem;color:var(--muted);direction:rtl;margin:2px 0 0;} | |
| .btns{display:flex;gap:12px;margin:14px 0 6px;flex-wrap:wrap;} | |
| .btns button{flex:1 1 200px;border:0;border-radius:14px;padding:13px 18px;font:700 1.02rem 'Cairo',sans-serif;cursor:pointer;transition:transform .15s,box-shadow .15s,filter .15s;} | |
| .btn-main{background:linear-gradient(135deg,var(--green),var(--green-2));color:#FFF;box-shadow:0 8px 20px rgba(15,76,58,.28);} | |
| .btn-main:hover{transform:translateY(-1px);filter:brightness(1.07);} | |
| .btn-alt{background:rgba(255,255,255,.85);color:var(--green);border:1.5px solid var(--gold);} .btn-alt:hover{background:#FFFBEF;transform:translateY(-1px);} | |
| .btns button:disabled{opacity:.5;cursor:not-allowed;transform:none;} | |
| #status{direction:rtl;text-align:center;color:var(--muted);font-size:.93rem;margin:8px 0;min-height:1.4em;} | |
| .boot{padding:14px 18px;margin:0 0 14px;direction:rtl;} | |
| .boot h2{margin:0 0 8px;font-size:.98rem;color:var(--green);} | |
| .boot .step{display:grid;grid-template-columns:22px minmax(0,1fr) 54px;gap:10px;align-items:center;margin:8px 0;font-size:.92rem;color:var(--muted);} | |
| .boot .dot{width:18px;height:18px;border-radius:50%;border:2px solid rgba(15,76,58,.22);display:inline-block;} | |
| .boot .step.active .dot{border-color:rgba(15,76,58,.18);border-top-color:var(--gold);animation:icv-spin .8s linear infinite;} | |
| .boot .step.done .dot{background:var(--ok);border-color:var(--ok);position:relative;} .boot .step.done .dot::after{content:"";position:absolute;inset:3px 5px 5px 5px;border:solid #FFF;border-width:0 2px 2px 0;transform:rotate(40deg);} | |
| .boot .step.done{color:var(--ok);} .boot .step.active{color:var(--ink);font-weight:600;} | |
| .boot .meter{height:8px;border-radius:999px;background:rgba(15,76,58,.1);overflow:hidden;} .boot .meter i{display:block;height:100%;width:0;background:linear-gradient(270deg,var(--green),var(--gold-2));transition:width .3s ease;} | |
| .boot .step.pending .meter{position:relative;} .boot .pct{font-size:.8rem;direction:ltr;text-align:left;} | |
| .boot.ready{padding:8px 16px;} .boot.ready h2{margin:0;} .boot.ready .step{display:none;} | |
| .banner{margin:0 0 12px;} | |
| </style> | |
| </head> | |
| <body class="icv-page"> | |
| <div class="page"> | |
| <div class="icv"><div class="hero"> | |
| <svg class="mark" viewBox="0 0 64 64" fill="none" stroke="#E2C06E" stroke-width="1.8" aria-hidden="true"><rect x="14" y="14" width="36" height="36"/><rect x="14" y="14" width="36" height="36" transform="rotate(45 32 32)"/><circle cx="32" cy="32" r="7" fill="#E2C06E" stroke="none"/></svg> | |
| <h1>التحقق من هلوسة القرآن والحديث وتصحيحها</h1> | |
| <p class="tagline">تحقّق من آيات القرآن والأحاديث النبوية بالدليل</p> | |
| <p class="sub">يكتشف النظام الاقتباسات داخل أي نص، ويسترجع نصوصها من المصادر، ويقارنها كلمةً بكلمة، ثم يعرض الدليل. | |
| وإذا لم تكفِ الأدلة فلن يختلق تصحيحًا، بل يحيل الحالة إلى المراجعة البشرية.</p> | |
| <div class="flow"><span>كشف</span><i>‹</i><span>استرجاع</span><i>‹</i><span>محاذاة</span><i>‹</i><span>دليل</span><i>‹</i><span>قرار</span></div> | |
| </div></div> | |
| <div class="boot glass" id="boot" aria-live="polite"> | |
| <h2 id="boot-title">جارٍ تجهيز النظام في متصفحك…</h2> | |
| <div class="step pending" data-step="python"><i class="dot"></i><span>بيئة بايثون</span><span class="pct"></span><div class="meter" style="grid-column:2 / span 2"><i></i></div></div> | |
| <div class="step pending" data-step="code"><i class="dot"></i><span>الكود وفهرس القرآن</span><span class="pct"></span><div class="meter" style="grid-column:2 / span 2"><i></i></div></div> | |
| <div class="step pending" data-step="hadith"><i class="dot"></i><span>كتب الحديث (تُحمَّل بالتوازي)</span><span class="pct"></span><div class="meter" style="grid-column:2 / span 2"><i></i></div></div> | |
| </div> | |
| <div class="tabs glass" role="tablist"><button class="tab active" role="tab" aria-selected="true" data-tab="direct">تحقّق مباشر</button><button class="tab" role="tab" aria-selected="false" data-tab="ask">اسأل ثم تحقّق</button></div> | |
| <div class="form glass engine-card" style="padding-bottom:12px"> | |
| <div class="engine"><label for="engine">Detection engine</label> | |
| <select id="engine"><option value="standard">Standard · Rules + Corpus Scan</option><option value="camelbert">CAMeLBERT-MSA · Fine-tuned (Hugging Face)</option></select></div> | |
| <p class="hint" id="engine-hint"></p> | |
| </div> | |
| <section id="tab-direct" class="form glass"> | |
| <label for="text">النص المراد التحقق منه</label> | |
| <textarea id="text" placeholder="الصق هنا النص الذي ولّده نموذج لغوي. يكتشف النظام الآيات والأحاديث الواردة فيه، بعلامات تنصيص أو بدونها…"></textarea> | |
| <div class="btns"><button id="verify" class="btn-main">تحقّق من النص</button><button id="example" class="btn-alt">جرّب مثالًا</button></div> | |
| </section> | |
| <section id="tab-ask" class="form glass" hidden> | |
| <div class="icv"><div class="notice">اكتب سؤالًا، وسيجيب عنه النظام مباشرةً، ثم يفحص كل آية وحديث في الإجابة ويعرض الأخطاء والتصحيحات.</div></div> | |
| <label for="prompt">سؤالك</label> | |
| <textarea id="prompt" class="short" maxlength="1500" placeholder="اكتب سؤالك، مثل: اشرح لي فضل الصبر في القرآن والسنة مع ذكر الأدلة."></textarea> | |
| <div class="btns"><button id="ask" class="btn-main">اسأل ثم تحقّق</button></div> | |
| </section> | |
| <div id="status" role="status"></div> | |
| <div id="results" class="results"></div> | |
| <div class="icv"><div class="disclaimer">أداة مساعدة للتدقيق النصي وليست فتوى ولا بديلًا عن المراجعة المتخصصة. | |
| النتائج مبنية على مراجع القرآن الكريم والكتب الستة المضمّنة فقط.</div></div> | |
| </div> | |
| <script> | |
| (function () { | |
| if (window.icvCopy) return; | |
| function toast(message, bad) { | |
| var t = document.getElementById('icv-toast'); | |
| if (!t) { t = document.createElement('div'); t.id = 'icv-toast'; t.setAttribute('role', 'status'); t.setAttribute('aria-live', 'polite'); document.body.appendChild(t); } | |
| t.textContent = message; t.className = 'show' + (bad ? ' bad' : ''); | |
| clearTimeout(window.__icvToast); window.__icvToast = setTimeout(function () { t.className = bad ? 'bad' : ''; }, 2400); | |
| } | |
| function legacyCopy(text) { | |
| var area = document.createElement('textarea'); area.value = text; area.setAttribute('readonly', ''); | |
| area.style.cssText = 'position:fixed;top:0;left:0;opacity:0;pointer-events:none'; document.body.appendChild(area); | |
| area.select(); area.setSelectionRange(0, text.length); var ok = false; | |
| try { ok = document.execCommand('copy'); } catch (e) { ok = false; } | |
| document.body.removeChild(area); return ok; | |
| } | |
| window.icvToast = toast; | |
| window.icvCopy = function (button) { | |
| var text = button.getAttribute('data-text') || ''; | |
| function finish(ok) { | |
| toast(ok ? 'تم نسخ النص بنجاح' : 'تعذّر النسخ، حدّد النص وانسخه يدويًا', !ok); | |
| if (ok) { button.classList.add('done'); setTimeout(function () { button.classList.remove('done'); }, 1600); } | |
| } | |
| if (navigator.clipboard && window.isSecureContext) { | |
| navigator.clipboard.writeText(text).then(function () { finish(true); }, function () { finish(legacyCopy(text)); }); | |
| } else { finish(legacyCopy(text)); } | |
| }; | |
| window.icvDownload = function (button) { | |
| var blob = new Blob([button.getAttribute('data-text') || ''], { type: 'text/tab-separated-values;charset=utf-8' }); | |
| var link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = button.getAttribute('data-name') || 'results.tsv'; | |
| document.body.appendChild(link); link.click(); document.body.removeChild(link); setTimeout(function () { URL.revokeObjectURL(link.href); }, 1000); | |
| }; | |
| })(); | |
| </script> | |
| <script type="text/plain" id="worker-src"> | |
| // Python (Pyodide) and the whole verification pipeline run here, off the main thread. | |
| let py = null, booted = null, hadithReady = null; | |
| const post = (message) => self.postMessage(message); | |
| const step = (name, state, extra) => post(Object.assign({ type: "step", name, state }, extra || {})); | |
| async function openCache(buildId) { | |
| try { | |
| const names = await caches.keys(); | |
| await Promise.all(names.filter((n) => n.startsWith("icv-") && n !== "icv-" + buildId).map((n) => caches.delete(n))); // purge old builds | |
| return await caches.open("icv-" + buildId); | |
| } catch (e) { return null; } | |
| } | |
| async function fetchBytes(url, cache, onProgress) { | |
| if (cache) { try { const hit = await cache.match(url); if (hit) return { bytes: new Uint8Array(await hit.arrayBuffer()), cached: true }; } catch (e) { /* fall through */ } } | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error("تعذّر تحميل " + url + " (" + response.status + ")"); | |
| if (cache) { try { await cache.put(url, response.clone()); } catch (e) { /* the cache is an optimisation only */ } } | |
| const total = Number(response.headers.get("Content-Length")) || 0; | |
| if (!response.body || !onProgress) return { bytes: new Uint8Array(await response.arrayBuffer()), cached: false }; | |
| const reader = response.body.getReader(), chunks = []; let received = 0; | |
| for (;;) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); received += value.length; onProgress(received, total); } | |
| const bytes = new Uint8Array(received); let offset = 0; | |
| for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; } | |
| return { bytes, cached: false }; | |
| } | |
| // Accept both layouts: files inside folders (index/, demo/) or uploaded flat next to index.html. | |
| async function fetchAny(file, base, cache, onProgress) { | |
| let lastError = null; | |
| for (const path of [file, file.split("/").pop()]) { | |
| try { return await fetchBytes(new URL(path, base).href, cache, onProgress); } catch (error) { lastError = error; } | |
| } | |
| throw lastError; | |
| } | |
| // A server that sets Content-Encoding: gzip on .gz files makes the browser inflate them; Python expects gzip, so re-pack. | |
| async function ensureGzip(file, bytes) { | |
| if (!file.endsWith(".gz") || (bytes[0] === 0x1f && bytes[1] === 0x8b)) return bytes; | |
| const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip")); | |
| return new Uint8Array(await new Response(stream).arrayBuffer()); | |
| } | |
| async function boot(message) { | |
| const cache = await openCache(message.buildId); | |
| step("python", "active"); | |
| importScripts("https://cdn.jsdelivr.net/pyodide/v" + message.pyodideVersion + "/full/pyodide.js"); | |
| const hadithFile = message.lazy[0]; | |
| // the large Hadith index starts downloading now, in parallel with the Python runtime | |
| const hadithDownload = fetchAny(hadithFile, message.base, cache, (got, total) => step("hadith", "active", { pct: total ? Math.round((got / total) * 100) : null, mb: (got / 1048576).toFixed(1) })); | |
| hadithDownload.catch(() => {}); | |
| py = await loadPyodide(); | |
| step("python", "done"); | |
| step("code", "active", { pct: 0 }); | |
| for (const dir of ["/app", "/app/index", "/app/demo", "/app/data"]) py.FS.mkdir(dir); | |
| let cached = true, n = 0; | |
| for (const file of message.files) { | |
| const result = await fetchAny(file, message.base, cache); | |
| cached = cached && result.cached; | |
| py.FS.writeFile("/app/" + file, await ensureGzip(file, result.bytes)); | |
| step("code", "active", { pct: Math.round((++n / message.files.length) * 100) }); | |
| } | |
| const examplesText = new TextDecoder().decode(py.FS.readFile("/app/demo/examples.json")); | |
| py.runPython("import sys; sys.path.insert(0, '/app')"); | |
| py.runPython("from app import get_pipeline, verify_text, verify_generated_answer, verify_with_entities; get_pipeline()"); | |
| step("code", "done", { cached }); | |
| post({ type: "ready", examples: JSON.parse(examplesText) }); | |
| hadithReady = (async () => { | |
| const result = await hadithDownload; | |
| step("hadith", "active", { pct: 100, prepare: true }); | |
| py.FS.writeFile("/app/" + hadithFile, await ensureGzip(hadithFile, result.bytes)); | |
| py.runPython("get_pipeline().retriever.warm()"); | |
| step("hadith", "done", { cached: result.cached }); | |
| post({ type: "hadith" }); | |
| })(); | |
| await hadithReady; | |
| } | |
| self.onmessage = async (event) => { | |
| const message = event.data; | |
| if (message.type === "boot") { | |
| booted = boot(message); | |
| try { await booted; } catch (error) { post({ type: "error", text: String((error && error.message) || error) }); } | |
| } else if (message.type === "verify") { | |
| try { | |
| await booted; | |
| let html; | |
| if (message.entities) html = py.globals.get("verify_with_entities")(message.text, message.entities, "camelbert", !!message.generated); | |
| else html = py.globals.get(message.generated ? "verify_generated_answer" : "verify_text")(message.text, message.engine || "standard"); | |
| post({ type: "result", id: message.id, html }); | |
| } catch (error) { post({ type: "result", id: message.id, html: null }); } | |
| } | |
| }; | |
| </script> | |
| <script> | |
| const DEFAULT_CONFIG = {"askEndpoint": "/api/ask", "hf": {"model": "", "endpoint": "https://router.huggingface.co/hf-inference/models/{model}"}}; | |
| const BUILD_ID = "fc19c57cae1d"; | |
| const SKELETON = "<div class=\"icv\" aria-busy=\"true\" aria-label=\"جارٍ التحقق\"><div class=\"skel-tiles\"><div class=\"skel skel-tile\"></div><div class=\"skel skel-tile\"></div><div class=\"skel skel-tile\"></div><div class=\"skel skel-tile\"></div></div><div class=\"skel-card\"><div class=\"skel skel-line w40\"></div><div class=\"skel skel-line w85\"></div><div class=\"skel skel-line\"></div><div class=\"skel skel-line w60\"></div></div><div class=\"skel-card\"><div class=\"skel skel-line w40\"></div><div class=\"skel skel-line w85\"></div><div class=\"skel skel-line\"></div><div class=\"skel skel-line w60\"></div></div></div>"; | |
| const MAX_PROMPT = 1500; | |
| const $ = (id) => document.getElementById(id); | |
| let cfg = DEFAULT_CONFIG, examples = [], exampleIndex = 0, nextId = 0, worker = null, ready = false; | |
| const pending = new Map(); | |
| const store = { | |
| get(key) { try { return localStorage.getItem(key); } catch (e) { return null; } }, | |
| set(key, value) { try { localStorage.setItem(key, value); } catch (e) { /* private mode / quota: caching is optional */ } }, | |
| }; | |
| function purgeOldLocalCache() { | |
| try { for (const key of Object.keys(localStorage)) if (key.startsWith("icv:examples:") && key !== "icv:examples:" + BUILD_ID) localStorage.removeItem(key); } catch (e) { /* ignore */ } | |
| } | |
| const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); | |
| function setStatus(text, busy = true) { $("status").innerHTML = (busy && text ? '<span class="icv-spinner"></span>' : "") + esc(text); } | |
| function setBusy(flag) { for (const id of ["verify", "example", "ask"]) $(id).disabled = flag; } | |
| function notice(text, kind) { return '<div class="icv banner"><div class="notice ' + kind + '">' + esc(text) + "</div></div>"; } | |
| function show(html, banner) { $("results").innerHTML = (banner || "") + html; } | |
| function showSkeleton() { $("results").innerHTML = SKELETON; } | |
| function verifyRemote(payload) { | |
| return new Promise((resolve) => { const id = ++nextId; pending.set(id, resolve); worker.postMessage(Object.assign({ type: "verify", id }, payload)); }); | |
| } | |
| // ---- detection engine: standard, or a fine-tuned CAMeLBERT-MSA hosted on Hugging Face (simulated when not configured) --------- | |
| async function hostedEntities(text) { | |
| const model = (cfg.hf && cfg.hf.model || "").trim(); | |
| if (!model) return { entities: null, reason: "not_configured" }; | |
| try { | |
| const response = await fetch(cfg.hf.endpoint.replace("{model}", encodeURIComponent(model)), { | |
| method: "POST", headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ inputs: text, parameters: { aggregation_strategy: "simple" } }), | |
| }); | |
| if (!response.ok) return { entities: null, reason: "unavailable" }; | |
| const data = await response.json(); | |
| return Array.isArray(data) ? { entities: JSON.stringify(data) } : { entities: null, reason: "unavailable" }; | |
| } catch (e) { return { entities: null, reason: "unavailable" }; } | |
| } | |
| async function verifyWithEngine(text, generated) { | |
| const engine = $("engine").value; | |
| if (engine !== "camelbert") return { html: await verifyRemote({ text, generated, engine: "standard" }), banner: "" }; | |
| const hosted = await hostedEntities(text); | |
| if (hosted.entities !== null) return { html: await verifyRemote({ text, generated, entities: hosted.entities }), banner: "" }; | |
| const banner = notice(hosted.reason === "unavailable" | |
| ? "تعذّر الوصول إلى النموذج المستضاف الآن؛ استُخدمت المحاكاة بالكاشف المدمج." | |
| : "لم يُضبط نموذج مستضاف بعد؛ يعمل هذا الخيار كمحاكاة بالكاشف المدمج.", "warn"); | |
| return { html: await verifyRemote({ text, generated, engine: "camelbert" }), banner }; | |
| } | |
| function updateEngineHint() { | |
| const hosted = (cfg.hf && cfg.hf.model || "").trim(); | |
| $("engine-hint").textContent = $("engine").value === "camelbert" | |
| ? (hosted ? "يُستدعى نموذج مضبوط مستضاف على Hugging Face لتحديد حدود الاقتباسات." : "لم يُضبط نموذج مستضاف؛ سيعمل هذا الخيار كمحاكاة ويُعلَّم ذلك في النتائج.") | |
| : "كشف سريع بالقواعد ومطابقة النص مع المراجع، يعمل كله داخل المتصفح."; | |
| } | |
| async function runDirect() { | |
| if (!$("text").value.trim()) return show(notice("الرجاء إدخال نص للتحقق منه.", "warn")); | |
| setBusy(true); showSkeleton(); setStatus(ready ? "جارٍ التحقق…" : "جارٍ تجهيز النظام ثم التحقق…"); | |
| const { html, banner } = await verifyWithEngine($("text").value, false); | |
| show(html !== null ? html : notice("حدث خطأ غير متوقع أثناء التحقق.", "bad"), banner); | |
| setStatus("", false); setBusy(false); | |
| } | |
| // ---- ask then verify: same-origin proxy that holds the key; saved sample answer when it is not reachable ---------------- | |
| async function askServer(prompt) { | |
| const response = await fetch(cfg.askEndpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt }) }); | |
| if (!response.ok) { | |
| const error = new Error("http_" + response.status); error.status = response.status; | |
| try { error.code = (await response.json()).error; } catch (e) { error.code = ""; } | |
| throw error; | |
| } | |
| const data = await response.json(); | |
| if (!data.answer) throw new Error("empty"); | |
| return String(data.answer); | |
| } | |
| async function runAsk() { | |
| const prompt = $("prompt").value.trim(); | |
| if (!prompt) return show(notice("اكتب سؤالًا أولًا.", "warn")); | |
| if (prompt.length > MAX_PROMPT) return show(notice("السؤال طويل جدًا (الحد الأقصى " + MAX_PROMPT + " حرف).", "warn")); | |
| setBusy(true); showSkeleton(); setStatus("جارٍ إعداد الإجابة…"); | |
| let answer = null, banner = ""; | |
| try { answer = await askServer(prompt); } | |
| catch (error) { | |
| const stop = (message) => { show(notice(message, "warn")); setStatus("", false); setBusy(false); }; | |
| if (error.status === 402) return stop("رصيد حساب OpenAI المرتبط بالخدمة غير كافٍ؛ على صاحب الحساب مراجعة الفوترة ثم المحاولة مجددًا."); | |
| if (error.status === 429) return stop("الطلبات كثيرة الآن؛ انتظر قليلًا ثم أعد المحاولة."); | |
| if (error.status === 413) return stop("السؤال طويل جدًا."); | |
| if (error.code === "upstream_auth") return stop("مفتاح الخدمة على الخادم غير صالح؛ يلزم تحديثه من صاحب الحساب."); | |
| if (examples.length) { | |
| answer = examples[exampleIndex++ % examples.length].text; | |
| banner = notice("خدمة الإجابة المباشرة غير متاحة الآن؛ عُرضت إجابة تجريبية محفوظة لتوضيح خطوات التحقق.", "warn"); | |
| } | |
| } | |
| if (answer === null) { show(notice("تعذّر الحصول على إجابة الآن؛ حاول لاحقًا.", "warn")); setStatus("", false); return setBusy(false); } | |
| setStatus("جارٍ التحقق من الإجابة…"); | |
| const result = await verifyWithEngine(answer, true); | |
| show(result.html !== null ? result.html : notice("تعذّر التحقق من الإجابة.", "bad"), banner + result.banner); | |
| setStatus("", false); setBusy(false); | |
| } | |
| // ---- boot panel ------------------------------------------------------------------------------------------------------ | |
| function applyStep(m) { | |
| const row = document.querySelector('.boot .step[data-step="' + m.name + '"]'); | |
| if (!row) return; | |
| row.className = "step " + m.state; | |
| const pct = m.state === "done" ? 100 : (m.pct == null ? 0 : m.pct); | |
| row.querySelector(".meter i").style.width = pct + "%"; | |
| row.querySelector(".pct").textContent = m.state === "done" ? (m.cached ? "محفوظ" : "تم") : (m.prepare ? "…" : (m.pct == null ? (m.mb ? m.mb + " MB" : "") : pct + "%")); | |
| if (document.querySelectorAll(".boot .step.done").length === 3) { $("boot").classList.add("ready"); $("boot-title").textContent = "النظام جاهز · يعمل كله داخل متصفحك ولا يُرسل نصك إلى أي خادم"; } | |
| } | |
| function start() { | |
| try { worker = new Worker(URL.createObjectURL(new Blob([$("worker-src").textContent], { type: "text/javascript" }))); } | |
| catch (error) { return setStatus("تعذّر تشغيل النظام: " + error.message, false); } | |
| worker.onerror = (event) => setStatus("تعذّر تشغيل النظام: " + (event.message || ""), false); | |
| worker.onmessage = (event) => { | |
| const m = event.data; | |
| if (m.type === "step") applyStep(m); | |
| else if (m.type === "error") setStatus("تعذّر تشغيل النظام: " + m.text + ". جرّب متصفح كمبيوتر حديثًا ثم أعد تحميل الصفحة.", false); | |
| else if (m.type === "result") { const resolve = pending.get(m.id); pending.delete(m.id); resolve(m.html); } | |
| else if (m.type === "ready") { examples = m.examples; ready = true; store.set("icv:examples:" + BUILD_ID, JSON.stringify(m.examples)); } | |
| }; | |
| worker.postMessage({ type: "boot", base: location.href, buildId: BUILD_ID, pyodideVersion: "0.26.4", files: ["app.py", "verifier.py", "retrieval.py", "index_builder.py", "normalization.py", "alignment.py", "similarity.py", "detector.py", "scanner.py", "idgham.py", "ui.py", "llm_client.py", "benchmark_format.py", "camelbert_adapter.py", "index/quran.idx.gz", "demo/examples.json"], lazy: ["index/hadith.idx.gz"] }); | |
| } | |
| async function loadConfig() { | |
| try { | |
| const response = await fetch("config.json", { cache: "no-store" }); | |
| if (response.ok) { cfg = Object.assign({}, DEFAULT_CONFIG, await response.json()); store.set("icv:config", JSON.stringify(cfg)); return; } | |
| } catch (e) { /* offline or missing: fall back to the last known configuration */ } | |
| try { const saved = store.get("icv:config"); if (saved) cfg = Object.assign({}, DEFAULT_CONFIG, JSON.parse(saved)); } catch (e) { /* ignore */ } | |
| } | |
| document.querySelectorAll(".tab").forEach((tab) => tab.addEventListener("click", () => { | |
| document.querySelectorAll(".tab").forEach((t) => { const on = t === tab; t.classList.toggle("active", on); t.setAttribute("aria-selected", on); }); | |
| for (const name of ["direct", "ask"]) $("tab-" + name).hidden = tab.dataset.tab !== name; | |
| })); | |
| $("engine").addEventListener("change", updateEngineHint); | |
| $("verify").addEventListener("click", runDirect); | |
| $("ask").addEventListener("click", runAsk); | |
| $("example").addEventListener("click", () => { if (!examples.length) return; $("text").value = examples[exampleIndex++ % examples.length].text; runDirect(); }); | |
| purgeOldLocalCache(); | |
| try { const saved = store.get("icv:examples:" + BUILD_ID); if (saved) examples = JSON.parse(saved); } catch (e) { examples = []; } | |
| loadConfig().then(updateEngineHint); | |
| start(); | |
| </script> | |
| </body> | |
| </html> | |