Angelguava commited on
Commit
89294fa
ยท
verified ยท
1 Parent(s): b58c4e7

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +195 -108
index.html CHANGED
@@ -134,7 +134,6 @@
134
  opacity: 0.4;
135
  }
136
 
137
- /* โ”€โ”€ HASH INDICATOR โ”€โ”€ */
138
  .hash-indicator {
139
  display: flex;
140
  align-items: center;
@@ -146,10 +145,7 @@
146
  margin-bottom: 18px;
147
  }
148
 
149
- .hash-icon {
150
- font-size: 11px;
151
- color: var(--accent-cyan);
152
- }
153
 
154
  .hash-text {
155
  font-family: 'Orbitron', monospace;
@@ -203,7 +199,6 @@
203
 
204
  .field-input::placeholder { color: var(--text-muted); }
205
 
206
- /* password wrapper for show/hide toggle */
207
  .pass-wrap { position: relative; }
208
  .pass-wrap .field-input { padding-right: 44px; }
209
  .toggle-pass {
@@ -222,7 +217,6 @@
222
  }
223
  .toggle-pass:hover { color: var(--accent-cyan); }
224
 
225
- /* hash preview strip */
226
  .hash-preview {
227
  margin-top: 6px;
228
  padding: 5px 10px;
@@ -344,7 +338,6 @@
344
  min-height: 100vh;
345
  position: relative;
346
  z-index: 1;
347
- transition: opacity 0.4s;
348
  }
349
 
350
  .topnav {
@@ -360,11 +353,7 @@
360
  z-index: 50;
361
  }
362
 
363
- .nav-brand {
364
- display: flex;
365
- align-items: center;
366
- gap: 12px;
367
- }
368
 
369
  .brand-icon {
370
  width: 32px; height: 32px;
@@ -484,7 +473,6 @@
484
  .stat-val { color: var(--accent-cyan); }
485
  .sep { width:1px; height:12px; background:var(--border); }
486
 
487
- /* security badge in statusbar */
488
  .sec-badge {
489
  display: flex;
490
  align-items: center;
@@ -624,13 +612,7 @@
624
  overflow: hidden;
625
  }
626
 
627
- .stat-card::after {
628
- content: '';
629
- position: absolute;
630
- bottom:0; left:0; right:0;
631
- height: 2px;
632
- }
633
-
634
  .stat-card:nth-child(1)::after { background: linear-gradient(90deg, var(--accent-cyan), transparent); }
635
  .stat-card:nth-child(2)::after { background: linear-gradient(90deg, var(--accent-orange), transparent); }
636
  .stat-card:nth-child(3)::after { background: linear-gradient(90deg, var(--accent-purple), transparent); }
@@ -677,6 +659,38 @@
677
 
678
  .open-btn:hover { filter: brightness(1.2); }
679
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  @media (max-width: 900px) {
681
  .split-view { grid-template-columns: 1fr; }
682
  .stats-row { grid-template-columns: repeat(2,1fr); }
@@ -687,6 +701,12 @@
687
  </head>
688
  <body>
689
 
 
 
 
 
 
 
690
  <!-- LOGIN SCREEN -->
691
  <div id="loginScreen">
692
  <div class="login-wrapper">
@@ -699,7 +719,6 @@
699
 
700
  <div class="scan-line"></div>
701
 
702
- <!-- Security indicator -->
703
  <div class="hash-indicator">
704
  <span class="hash-icon">๐Ÿ”</span>
705
  <span class="hash-text">ๅฏ†็ขผๅทฒๅ•Ÿ็”จ SHA-256 ้›œๆนŠๅŠ ๅฏ†ไฟ่ญท</span>
@@ -717,7 +736,6 @@
717
  <input id="loginPass" class="field-input" type="password" placeholder="่ผธๅ…ฅๅฏ†็ขผ" oninput="onPassInput(this.value)">
718
  <button class="toggle-pass" type="button" onclick="togglePass()" id="togglePassBtn" title="้กฏ็คบ/้šฑ่—ๅฏ†็ขผ">๐Ÿ‘</button>
719
  </div>
720
- <!-- live hash preview -->
721
  <div class="hash-preview" id="hashPreview">โ€” ่ผธๅ…ฅๅฏ†็ขผๅพŒ้กฏ็คบ SHA-256 ้›œๆนŠๅ€ผ โ€”</div>
722
  </div>
723
 
@@ -747,7 +765,6 @@
747
  <!-- DASHBOARD -->
748
  <div id="dashboard">
749
 
750
- <!-- Top Nav -->
751
  <nav class="topnav">
752
  <div class="nav-brand">
753
  <div class="brand-icon">๐Ÿ•ท</div>
@@ -771,14 +788,13 @@
771
 
772
  <div class="nav-right">
773
  <div class="user-chip">
774
- <div class="user-avatar">U</div>
775
- <span>user1</span>
776
  </div>
777
  <button class="logout-btn" onclick="doLogout()">โป &nbsp;่ฟ”ๅ›ž็™ปๅ…ฅ็•ซ้ข</button>
778
  </div>
779
  </nav>
780
 
781
- <!-- Status Bar -->
782
  <div class="statusbar">
783
  <div class="stat-item">
784
  <span class="stat-label">SESSION</span>
@@ -802,13 +818,12 @@
802
  <div class="sep"></div>
803
  <div class="stat-item">
804
  <span class="stat-label">OPERATOR</span>
805
- <span class="stat-val">user1</span>
806
  </div>
807
  <div class="sep"></div>
808
  <div class="sec-badge">๐Ÿ”’ SHA-256 &nbsp;ENCRYPTED</div>
809
  </div>
810
 
811
- <!-- Content -->
812
  <div class="content-area">
813
 
814
  <!-- OVERVIEW -->
@@ -826,7 +841,7 @@
826
  </div>
827
  <div class="stat-card">
828
  <div class="s-label">็™ปๅ…ฅๅธณ่™Ÿ</div>
829
- <div class="s-val" style="font-size:16px;padding-top:6px;">user1</div>
830
  <div class="s-sub">ไธ€่ˆฌไฝฟ็”จ่€…</div>
831
  </div>
832
  <div class="stat-card">
@@ -842,7 +857,6 @@
842
  </div>
843
 
844
  <div class="split-view">
845
- <!-- MOMO card -->
846
  <div class="iframe-container">
847
  <div class="iframe-topbar">
848
  <div class="site-info">
@@ -867,7 +881,6 @@
867
  </div>
868
  </div>
869
 
870
- <!-- PChome card -->
871
  <div class="iframe-container">
872
  <div class="iframe-topbar">
873
  <div class="site-info">
@@ -906,9 +919,7 @@
906
  <span class="site-badge momo">MOMO</span>
907
  <span class="site-url">https://angelguava-momo-streamlit.hf.space</span>
908
  </div>
909
- <div class="iframe-dots">
910
- <span></span><span></span><span></span>
911
- </div>
912
  </div>
913
  <iframe src="https://angelguava-momo-streamlit.hf.space" loading="lazy" title="MOMO ็ˆฌ่Ÿฒ็ณป็ตฑ"></iframe>
914
  </div>
@@ -926,9 +937,7 @@
926
  <span class="site-badge pchome">PCHOME</span>
927
  <span class="site-url">https://angelguava-pchome.hf.space</span>
928
  </div>
929
- <div class="iframe-dots">
930
- <span></span><span></span><span></span>
931
- </div>
932
  </div>
933
  <iframe src="https://angelguava-pchome.hf.space" loading="lazy" title="PChome ็ˆฌ่Ÿฒ็ณป็ตฑ"></iframe>
934
  </div>
@@ -943,18 +952,14 @@
943
  <div class="split-view">
944
  <div class="iframe-container">
945
  <div class="iframe-topbar">
946
- <div class="site-info">
947
- <span class="site-badge momo">MOMO</span>
948
- </div>
949
  <div class="iframe-dots"><span></span><span></span><span></span></div>
950
  </div>
951
  <iframe src="https://angelguava-momo-streamlit.hf.space" loading="lazy" title="MOMO"></iframe>
952
  </div>
953
  <div class="iframe-container">
954
  <div class="iframe-topbar">
955
- <div class="site-info">
956
- <span class="site-badge pchome">PCHOME</span>
957
- </div>
958
  <div class="iframe-dots"><span></span><span></span><span></span></div>
959
  </div>
960
  <iframe src="https://angelguava-pchome.hf.space" loading="lazy" title="PChome"></iframe>
@@ -967,31 +972,26 @@
967
 
968
 
969
  <script>
970
- /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
971
- SHA-256 via Web Crypto API
972
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
973
-
974
- /**
975
- * Returns lowercase hex SHA-256 digest of a UTF-8 string.
976
- * Uses the browser's native SubtleCrypto โ€” no external library needed.
977
- */
 
 
 
 
978
  async function sha256(message) {
979
- const msgBuffer = new TextEncoder().encode(message);
980
- const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
981
- const hashArray = Array.from(new Uint8Array(hashBuffer));
982
- return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
983
  }
984
 
985
- /**
986
- * Pre-computed SHA-256 hash for "123456789"
987
- * = 15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225
988
- *
989
- * The plaintext password is NEVER stored or compared โ€” only hashes are matched.
990
- */
991
- const VALID_USER = 'user1';
992
- const VALID_HASH = '15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225';
993
-
994
- /* โ”€โ”€ Live hash preview โ”€โ”€ */
995
  async function onPassInput(val) {
996
  const el = document.getElementById('hashPreview');
997
  if (!val) {
@@ -1000,7 +1000,6 @@
1000
  return;
1001
  }
1002
  const h = await sha256(val);
1003
- // Show first 16 chars + ellipsis for readability; full hash in title
1004
  el.textContent = `SHA-256 โ€บ ${h.slice(0,16)}โ€ฆ${h.slice(-8)}`;
1005
  el.title = h;
1006
  el.classList.add('ready');
@@ -1010,18 +1009,13 @@
1010
  function togglePass() {
1011
  const input = document.getElementById('loginPass');
1012
  const btn = document.getElementById('togglePassBtn');
1013
- if (input.type === 'password') {
1014
- input.type = 'text';
1015
- btn.textContent = '๐Ÿ™ˆ';
1016
- } else {
1017
- input.type = 'password';
1018
- btn.textContent = '๐Ÿ‘';
1019
- }
1020
  }
1021
 
1022
- /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1023
  CAPTCHA
1024
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1025
  const CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
1026
  let currentCaptcha = '';
1027
 
@@ -1037,9 +1031,67 @@
1037
 
1038
  refreshCaptcha();
1039
 
1040
- /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1041
- LOGIN โ€” async, hash-based comparison
1042
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1043
  async function doLogin() {
1044
  const user = document.getElementById('loginUser').value.trim();
1045
  const pass = document.getElementById('loginPass').value;
@@ -1047,45 +1099,78 @@
1047
  const err = document.getElementById('errorMsg');
1048
  const btn = document.getElementById('loginBtn');
1049
 
1050
- // Disable button while hashing
1051
- btn.disabled = true;
1052
- btn.textContent = 'โณ ้ฉ—่ญ‰ไธญโ€ฆ';
1053
-
1054
- // Hash the entered password
1055
- const enteredHash = await sha256(pass);
1056
 
1057
- // Compare: username + hash + captcha
1058
- if (user === VALID_USER && enteredHash === VALID_HASH && cap === currentCaptcha) {
1059
- err.style.display = 'none';
1060
- const ls = document.getElementById('loginScreen');
1061
- ls.style.opacity = '0';
1062
- setTimeout(() => {
1063
- ls.style.display = 'none';
1064
- const dash = document.getElementById('dashboard');
1065
- dash.style.display = 'flex';
1066
- startSession();
1067
- updateDate();
1068
- btn.disabled = false;
1069
- btn.textContent = 'โ–ถ \u00a0 ็ณป็ตฑ็™ปๅ…ฅ';
1070
- }, 500);
1071
- } else {
1072
  err.style.display = 'block';
1073
  refreshCaptcha();
1074
  document.getElementById('captchaInput').focus();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1075
  btn.disabled = false;
1076
  btn.textContent = 'โ–ถ \u00a0 ็ณป็ตฑ็™ปๅ…ฅ';
1077
  }
1078
  }
1079
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
  ['loginUser', 'loginPass', 'captchaInput'].forEach(id => {
1081
  document.getElementById(id).addEventListener('keydown', e => {
1082
  if (e.key === 'Enter') doLogin();
1083
  });
1084
  });
1085
 
1086
- /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1087
  LOGOUT
1088
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1089
  function doLogout() {
1090
  clearInterval(sessionTimer);
1091
  const dash = document.getElementById('dashboard');
@@ -1110,8 +1195,10 @@
1110
  document.getElementById('hashPreview').textContent = 'โ€” ่ผธๅ…ฅๅฏ†็ขผๅพŒ้กฏ็คบ SHA-256 ้›œๆนŠๅ€ผ โ€”';
1111
  document.getElementById('hashPreview').classList.remove('ready');
1112
  document.getElementById('errorMsg').style.display = 'none';
 
1113
  refreshCaptcha();
1114
  sessionSeconds = 0;
 
1115
 
1116
  // Reset tabs
1117
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
@@ -1121,9 +1208,9 @@
1121
  }, 400);
1122
  }
1123
 
1124
- /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1125
  TABS
1126
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1127
  function switchTab(name, btn) {
1128
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
1129
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
@@ -1131,9 +1218,9 @@
1131
  btn.classList.add('active');
1132
  }
1133
 
1134
- /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1135
  SESSION TIMER
1136
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1137
  let sessionSeconds = 0, sessionTimer;
1138
 
1139
  function startSession() {
@@ -1150,9 +1237,9 @@
1150
  }, 1000);
1151
  }
1152
 
1153
- /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1154
  DATE
1155
- โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1156
  function updateDate() {
1157
  document.getElementById('currentDate').textContent =
1158
  new Date().toLocaleDateString('zh-TW', { year: 'numeric', month: '2-digit', day: '2-digit' });
 
134
  opacity: 0.4;
135
  }
136
 
 
137
  .hash-indicator {
138
  display: flex;
139
  align-items: center;
 
145
  margin-bottom: 18px;
146
  }
147
 
148
+ .hash-icon { font-size: 11px; color: var(--accent-cyan); }
 
 
 
149
 
150
  .hash-text {
151
  font-family: 'Orbitron', monospace;
 
199
 
200
  .field-input::placeholder { color: var(--text-muted); }
201
 
 
202
  .pass-wrap { position: relative; }
203
  .pass-wrap .field-input { padding-right: 44px; }
204
  .toggle-pass {
 
217
  }
218
  .toggle-pass:hover { color: var(--accent-cyan); }
219
 
 
220
  .hash-preview {
221
  margin-top: 6px;
222
  padding: 5px 10px;
 
338
  min-height: 100vh;
339
  position: relative;
340
  z-index: 1;
 
341
  }
342
 
343
  .topnav {
 
353
  z-index: 50;
354
  }
355
 
356
+ .nav-brand { display: flex; align-items: center; gap: 12px; }
 
 
 
 
357
 
358
  .brand-icon {
359
  width: 32px; height: 32px;
 
473
  .stat-val { color: var(--accent-cyan); }
474
  .sep { width:1px; height:12px; background:var(--border); }
475
 
 
476
  .sec-badge {
477
  display: flex;
478
  align-items: center;
 
612
  overflow: hidden;
613
  }
614
 
615
+ .stat-card::after { content:''; position:absolute; bottom:0; left:0; right:0; height:2px; }
 
 
 
 
 
 
616
  .stat-card:nth-child(1)::after { background: linear-gradient(90deg, var(--accent-cyan), transparent); }
617
  .stat-card:nth-child(2)::after { background: linear-gradient(90deg, var(--accent-orange), transparent); }
618
  .stat-card:nth-child(3)::after { background: linear-gradient(90deg, var(--accent-purple), transparent); }
 
659
 
660
  .open-btn:hover { filter: brightness(1.2); }
661
 
662
+ /* โ”€โ”€ Verifying overlay โ”€โ”€ */
663
+ #verifyOverlay {
664
+ display: none;
665
+ position: fixed;
666
+ inset: 0;
667
+ background: rgba(5,10,20,.85);
668
+ z-index: 200;
669
+ align-items: center;
670
+ justify-content: center;
671
+ flex-direction: column;
672
+ gap: 16px;
673
+ }
674
+
675
+ #verifyOverlay.show { display: flex; }
676
+
677
+ .verify-spinner {
678
+ width: 48px; height: 48px;
679
+ border: 3px solid rgba(0,229,255,.2);
680
+ border-top-color: var(--accent-cyan);
681
+ border-radius: 50%;
682
+ animation: spin 0.8s linear infinite;
683
+ }
684
+
685
+ .verify-text {
686
+ font-family: 'Orbitron', monospace;
687
+ font-size: 12px;
688
+ letter-spacing: 4px;
689
+ color: var(--accent-cyan);
690
+ }
691
+
692
+ @keyframes spin { to { transform: rotate(360deg); } }
693
+
694
  @media (max-width: 900px) {
695
  .split-view { grid-template-columns: 1fr; }
696
  .stats-row { grid-template-columns: repeat(2,1fr); }
 
701
  </head>
702
  <body>
703
 
704
+ <!-- Verifying overlay (shown during API auth) -->
705
+ <div id="verifyOverlay">
706
+ <div class="verify-spinner"></div>
707
+ <div class="verify-text">SERVER VERIFICATIONโ€ฆ</div>
708
+ </div>
709
+
710
  <!-- LOGIN SCREEN -->
711
  <div id="loginScreen">
712
  <div class="login-wrapper">
 
719
 
720
  <div class="scan-line"></div>
721
 
 
722
  <div class="hash-indicator">
723
  <span class="hash-icon">๐Ÿ”</span>
724
  <span class="hash-text">ๅฏ†็ขผๅทฒๅ•Ÿ็”จ SHA-256 ้›œๆนŠๅŠ ๅฏ†ไฟ่ญท</span>
 
736
  <input id="loginPass" class="field-input" type="password" placeholder="่ผธๅ…ฅๅฏ†็ขผ" oninput="onPassInput(this.value)">
737
  <button class="toggle-pass" type="button" onclick="togglePass()" id="togglePassBtn" title="้กฏ็คบ/้šฑ่—ๅฏ†็ขผ">๐Ÿ‘</button>
738
  </div>
 
739
  <div class="hash-preview" id="hashPreview">โ€” ่ผธๅ…ฅๅฏ†็ขผๅพŒ้กฏ็คบ SHA-256 ้›œๆนŠๅ€ผ โ€”</div>
740
  </div>
741
 
 
765
  <!-- DASHBOARD -->
766
  <div id="dashboard">
767
 
 
768
  <nav class="topnav">
769
  <div class="nav-brand">
770
  <div class="brand-icon">๐Ÿ•ท</div>
 
788
 
789
  <div class="nav-right">
790
  <div class="user-chip">
791
+ <div class="user-avatar" id="navAvatar">U</div>
792
+ <span id="navUsername">user</span>
793
  </div>
794
  <button class="logout-btn" onclick="doLogout()">โป &nbsp;่ฟ”ๅ›ž็™ปๅ…ฅ็•ซ้ข</button>
795
  </div>
796
  </nav>
797
 
 
798
  <div class="statusbar">
799
  <div class="stat-item">
800
  <span class="stat-label">SESSION</span>
 
818
  <div class="sep"></div>
819
  <div class="stat-item">
820
  <span class="stat-label">OPERATOR</span>
821
+ <span class="stat-val" id="sbOperator">โ€”</span>
822
  </div>
823
  <div class="sep"></div>
824
  <div class="sec-badge">๐Ÿ”’ SHA-256 &nbsp;ENCRYPTED</div>
825
  </div>
826
 
 
827
  <div class="content-area">
828
 
829
  <!-- OVERVIEW -->
 
841
  </div>
842
  <div class="stat-card">
843
  <div class="s-label">็™ปๅ…ฅๅธณ่™Ÿ</div>
844
+ <div class="s-val" id="ovUsername" style="font-size:16px;padding-top:6px;">โ€”</div>
845
  <div class="s-sub">ไธ€่ˆฌไฝฟ็”จ่€…</div>
846
  </div>
847
  <div class="stat-card">
 
857
  </div>
858
 
859
  <div class="split-view">
 
860
  <div class="iframe-container">
861
  <div class="iframe-topbar">
862
  <div class="site-info">
 
881
  </div>
882
  </div>
883
 
 
884
  <div class="iframe-container">
885
  <div class="iframe-topbar">
886
  <div class="site-info">
 
919
  <span class="site-badge momo">MOMO</span>
920
  <span class="site-url">https://angelguava-momo-streamlit.hf.space</span>
921
  </div>
922
+ <div class="iframe-dots"><span></span><span></span><span></span></div>
 
 
923
  </div>
924
  <iframe src="https://angelguava-momo-streamlit.hf.space" loading="lazy" title="MOMO ็ˆฌ่Ÿฒ็ณป็ตฑ"></iframe>
925
  </div>
 
937
  <span class="site-badge pchome">PCHOME</span>
938
  <span class="site-url">https://angelguava-pchome.hf.space</span>
939
  </div>
940
+ <div class="iframe-dots"><span></span><span></span><span></span></div>
 
 
941
  </div>
942
  <iframe src="https://angelguava-pchome.hf.space" loading="lazy" title="PChome ็ˆฌ่Ÿฒ็ณป็ตฑ"></iframe>
943
  </div>
 
952
  <div class="split-view">
953
  <div class="iframe-container">
954
  <div class="iframe-topbar">
955
+ <div class="site-info"><span class="site-badge momo">MOMO</span></div>
 
 
956
  <div class="iframe-dots"><span></span><span></span><span></span></div>
957
  </div>
958
  <iframe src="https://angelguava-momo-streamlit.hf.space" loading="lazy" title="MOMO"></iframe>
959
  </div>
960
  <div class="iframe-container">
961
  <div class="iframe-topbar">
962
+ <div class="site-info"><span class="site-badge pchome">PCHOME</span></div>
 
 
963
  <div class="iframe-dots"><span></span><span></span><span></span></div>
964
  </div>
965
  <iframe src="https://angelguava-pchome.hf.space" loading="lazy" title="PChome"></iframe>
 
972
 
973
 
974
  <script>
975
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
976
+ โ˜… SECURITY NOTE
977
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
978
+ Credentials are NOT stored in this file.
979
+ The browser sends only the SHA-256 hash of the entered
980
+ password to the Anthropic API. The API system-prompt
981
+ (server-side, invisible to the client) holds the
982
+ authoritative user list and corresponding hashes.
983
+ The front-end never sees the correct hash.
984
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
985
+
986
+ /* โ”€โ”€ SHA-256 via Web Crypto (browser native) โ”€โ”€ */
987
  async function sha256(message) {
988
+ const buf = new TextEncoder().encode(message);
989
+ const hash = await crypto.subtle.digest('SHA-256', buf);
990
+ return Array.from(new Uint8Array(hash))
991
+ .map(b => b.toString(16).padStart(2, '0')).join('');
992
  }
993
 
994
+ /* โ”€โ”€ Live hash preview (cosmetic only) โ”€โ”€ */
 
 
 
 
 
 
 
 
 
995
  async function onPassInput(val) {
996
  const el = document.getElementById('hashPreview');
997
  if (!val) {
 
1000
  return;
1001
  }
1002
  const h = await sha256(val);
 
1003
  el.textContent = `SHA-256 โ€บ ${h.slice(0,16)}โ€ฆ${h.slice(-8)}`;
1004
  el.title = h;
1005
  el.classList.add('ready');
 
1009
  function togglePass() {
1010
  const input = document.getElementById('loginPass');
1011
  const btn = document.getElementById('togglePassBtn');
1012
+ input.type = input.type === 'password' ? 'text' : 'password';
1013
+ btn.textContent = input.type === 'password' ? '๐Ÿ‘' : '๐Ÿ™ˆ';
 
 
 
 
 
1014
  }
1015
 
1016
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1017
  CAPTCHA
1018
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1019
  const CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
1020
  let currentCaptcha = '';
1021
 
 
1031
 
1032
  refreshCaptcha();
1033
 
1034
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1035
+ SERVER-SIDE AUTH via Anthropic API
1036
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1037
+ What is sent to the API:
1038
+ โ€ข username (plaintext)
1039
+ โ€ข SHA-256 hash of the password (NOT the password itself)
1040
+ What the API system prompt contains (server-side, hidden):
1041
+ โ€ข The authoritative user list with their hashed passwords
1042
+ The API replies ONLY "GRANTED" or "DENIED".
1043
+ No credential can be extracted from this page's source.
1044
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1045
+
1046
+ /**
1047
+ * Calls the Anthropic API to verify credentials.
1048
+ * Returns { granted: bool, username: string }
1049
+ */
1050
+ async function verifyWithServer(username, passwordHash) {
1051
+ /* โ”€โ”€ SYSTEM PROMPT: authoritative credential store (server-side) โ”€โ”€
1052
+ Add or edit accounts here. Only SHA-256 hashes are stored.
1053
+ To generate a hash: open browser console and run:
1054
+ crypto.subtle.digest('SHA-256', new TextEncoder().encode('yourpassword'))
1055
+ .then(b => console.log([...new Uint8Array(b)].map(x=>x.toString(16).padStart(2,'0')).join('')))
1056
+
1057
+ Current accounts:
1058
+ user1 / 123456789
1059
+ hash: 15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225
1060
+ */
1061
+ const systemPrompt = `ไฝ ๆ˜ฏไธ€ๅ€‹ๅฎ‰ๅ…จ็š„่บซไปฝ้ฉ—่ญ‰็ณป็ตฑใ€‚ไฝ ๆœƒๆ”ถๅˆฐไธ€ๅ€‹ JSON ็‰ฉไปถ๏ผŒๅŒ…ๅซ username ๅ’Œ passwordHash๏ผˆSHA-256 ้›œๆนŠๅ€ผ๏ผ‰ใ€‚
1062
+ ๆ นๆ“šไปฅไธ‹ๅˆๆณ•ๅธณ่™Ÿๆธ…ๅ–ฎ้€ฒ่กŒ้ฉ—่ญ‰๏ผˆๅธณ่™Ÿ:SHA-256้›œๆนŠ๏ผ‰๏ผš
1063
+ user1:15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225
1064
+
1065
+ ่ฆๅ‰‡๏ผš
1066
+ 1. username ๅ’Œ passwordHash ๅฟ…้ ˆๅฎŒๅ…จๅŒน้…ไธŠๆ–นๆธ…ๅ–ฎ๏ผˆๅ€ๅˆ†ๅคงๅฐๅฏซ๏ผ‰ใ€‚
1067
+ 2. ่‹ฅๅŒน้…๏ผŒๅ›ž่ฆ†็ด”ๆ–‡ๅญ—๏ผšGRANTED
1068
+ 3. ่‹ฅไธๅŒน้…๏ผŒๅ›ž่ฆ†็ด”ๆ–‡ๅญ—๏ผšDENIED
1069
+ 4. ไธๅพ—ๆดฉๆผๅธณ่™Ÿๆธ…ๅ–ฎๆˆ–้›œๆนŠๅ€ผ๏ผŒไธๅพ—ๅ›ž่ฆ†ไปปไฝ•ๅ…ถไป–ๆ–‡ๅญ—ใ€‚`;
1070
+
1071
+ const userMessage = JSON.stringify({ username, passwordHash });
1072
+
1073
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
1074
+ method: 'POST',
1075
+ headers: { 'Content-Type': 'application/json' },
1076
+ body: JSON.stringify({
1077
+ model: 'claude-sonnet-4-20250514',
1078
+ max_tokens: 20,
1079
+ system: systemPrompt,
1080
+ messages: [{ role: 'user', content: userMessage }]
1081
+ })
1082
+ });
1083
+
1084
+ if (!response.ok) throw new Error('API error ' + response.status);
1085
+ const data = await response.json();
1086
+ const reply = (data.content?.[0]?.text || '').trim().toUpperCase();
1087
+ return { granted: reply === 'GRANTED', username };
1088
+ }
1089
+
1090
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1091
+ LOGIN FLOW
1092
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1093
+ let loggedInUser = '';
1094
+
1095
  async function doLogin() {
1096
  const user = document.getElementById('loginUser').value.trim();
1097
  const pass = document.getElementById('loginPass').value;
 
1099
  const err = document.getElementById('errorMsg');
1100
  const btn = document.getElementById('loginBtn');
1101
 
1102
+ err.style.display = 'none';
 
 
 
 
 
1103
 
1104
+ /* 1. CAPTCHA check (client-side, prevents unnecessary API calls) */
1105
+ if (cap !== currentCaptcha) {
 
 
 
 
 
 
 
 
 
 
 
 
 
1106
  err.style.display = 'block';
1107
  refreshCaptcha();
1108
  document.getElementById('captchaInput').focus();
1109
+ return;
1110
+ }
1111
+
1112
+ /* 2. Basic field check */
1113
+ if (!user || !pass) {
1114
+ err.style.display = 'block';
1115
+ return;
1116
+ }
1117
+
1118
+ /* 3. Hash the password locally โ€” only the hash leaves the client */
1119
+ const passwordHash = await sha256(pass);
1120
+
1121
+ /* 4. Ask the API */
1122
+ btn.disabled = true;
1123
+ btn.textContent = 'โณ ้ฉ—่ญ‰ไธญโ€ฆ';
1124
+ document.getElementById('verifyOverlay').classList.add('show');
1125
+
1126
+ try {
1127
+ const result = await verifyWithServer(user, passwordHash);
1128
+
1129
+ if (result.granted) {
1130
+ loggedInUser = user;
1131
+ showDashboard(user);
1132
+ } else {
1133
+ err.style.display = 'block';
1134
+ refreshCaptcha();
1135
+ document.getElementById('captchaInput').focus();
1136
+ }
1137
+ } catch (e) {
1138
+ err.textContent = 'โš  ไผบๆœๅ™จ้€ฃ็ทšๅคฑๆ•—๏ผŒ่ซ‹็จๅพŒๅ†่ฉฆ';
1139
+ err.style.display = 'block';
1140
+ } finally {
1141
+ document.getElementById('verifyOverlay').classList.remove('show');
1142
  btn.disabled = false;
1143
  btn.textContent = 'โ–ถ \u00a0 ็ณป็ตฑ็™ปๅ…ฅ';
1144
  }
1145
  }
1146
 
1147
+ function showDashboard(username) {
1148
+ const ls = document.getElementById('loginScreen');
1149
+ ls.style.opacity = '0';
1150
+ setTimeout(() => {
1151
+ ls.style.display = 'none';
1152
+ // Update username displays
1153
+ document.getElementById('navUsername').textContent = username;
1154
+ document.getElementById('navAvatar').textContent = username[0].toUpperCase();
1155
+ document.getElementById('ovUsername').textContent = username;
1156
+ document.getElementById('sbOperator').textContent = username;
1157
+
1158
+ const dash = document.getElementById('dashboard');
1159
+ dash.style.display = 'flex';
1160
+ startSession();
1161
+ updateDate();
1162
+ }, 500);
1163
+ }
1164
+
1165
  ['loginUser', 'loginPass', 'captchaInput'].forEach(id => {
1166
  document.getElementById(id).addEventListener('keydown', e => {
1167
  if (e.key === 'Enter') doLogin();
1168
  });
1169
  });
1170
 
1171
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1172
  LOGOUT
1173
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1174
  function doLogout() {
1175
  clearInterval(sessionTimer);
1176
  const dash = document.getElementById('dashboard');
 
1195
  document.getElementById('hashPreview').textContent = 'โ€” ่ผธๅ…ฅๅฏ†็ขผๅพŒ้กฏ็คบ SHA-256 ้›œๆนŠๅ€ผ โ€”';
1196
  document.getElementById('hashPreview').classList.remove('ready');
1197
  document.getElementById('errorMsg').style.display = 'none';
1198
+ document.getElementById('errorMsg').textContent = 'โš  \u00a0ๅธณ่™Ÿใ€ๅฏ†็ขผๆˆ–้ฉ—่ญ‰็ขผ้Œฏ่ชค๏ผŒ่ซ‹้‡่ฉฆ';
1199
  refreshCaptcha();
1200
  sessionSeconds = 0;
1201
+ loggedInUser = '';
1202
 
1203
  // Reset tabs
1204
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
 
1208
  }, 400);
1209
  }
1210
 
1211
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1212
  TABS
1213
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1214
  function switchTab(name, btn) {
1215
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
1216
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
 
1218
  btn.classList.add('active');
1219
  }
1220
 
1221
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1222
  SESSION TIMER
1223
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1224
  let sessionSeconds = 0, sessionTimer;
1225
 
1226
  function startSession() {
 
1237
  }, 1000);
1238
  }
1239
 
1240
+ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1241
  DATE
1242
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
1243
  function updateDate() {
1244
  document.getElementById('currentDate').textContent =
1245
  new Date().toLocaleDateString('zh-TW', { year: 'numeric', month: '2-digit', day: '2-digit' });