school44s commited on
Commit
cee43ad
·
verified ·
1 Parent(s): a8e0c36

Upload 13 files

Browse files
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
chatbox.js ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ 'use strict';
3
+
4
+ var API_DEFAULT = 'https://chatbox.npgia-himlam.workers.dev/';
5
+ var ID_PREFIX = 'avchat';
6
+ var MODELS = { nivip: 'nivip', zerta: 'zerta' };
7
+ var MODEL_LABELS = { nivip: 'Nivip', zerta: 'Zerta' };
8
+
9
+ function baseUrl() {
10
+ try {
11
+ var v = localStorage.getItem('anivietdb-chat-api');
12
+ if (v) return v;
13
+ } catch (e) {}
14
+ return API_DEFAULT;
15
+ }
16
+
17
+ function injectCss() {
18
+ if (document.getElementById(ID_PREFIX + '-style')) return;
19
+ var st = document.createElement('style');
20
+ st.id = ID_PREFIX + '-style';
21
+ st.textContent = [
22
+ '.' + ID_PREFIX + '-fab {',
23
+ ' position: fixed; right: 16px; bottom: 16px; width: 56px; height: 56px;',
24
+ ' border-radius: 50%; border: none; cursor: pointer; font-size: 24px; z-index: 9998;',
25
+ ' background: linear-gradient(135deg, #e84393, #a29bfe); color: #fff;',
26
+ ' box-shadow: 0 8px 24px rgba(232,67,147,.5); display: flex; align-items: center; justify-content: center;',
27
+ ' transition: transform .2s;',
28
+ '}',
29
+ '.' + ID_PREFIX + '-fab:hover { transform: scale(1.08); }',
30
+ '.' + ID_PREFIX + '-box {',
31
+ ' position: fixed; right: 16px; bottom: 16px; width: 340px; max-width: calc(100vw - 32px);',
32
+ ' background: #14141f; color: #fff; border-radius: 12px; overflow: hidden;',
33
+ ' box-shadow: 0 8px 30px rgba(0,0,0,.5); z-index: 9999;',
34
+ ' font-family: Roboto, Arial, sans-serif; display: none;',
35
+ '}',
36
+ '.' + ID_PREFIX + '-head { background: #1d1d2e; padding: 12px 16px; border-bottom: 1px solid #333; display: flex; justify-content: space-between; align-items: center; }',
37
+ '.' + ID_PREFIX + '-head h3 { margin: 0; font-size: 15px; color: #fff; }',
38
+ '.' + ID_PREFIX + '-model { background: #14141f; color: #fff; border: 1px solid #444; border-radius: 6px; font-size: 12.5px; padding: 4px 6px; outline: none; cursor: pointer; }',
39
+ '.' + ID_PREFIX + '-close { background: none; border: none; color: #aaa; font-size: 15px; cursor: pointer; padding: 0 4px; }',
40
+ '.' + ID_PREFIX + '-close:hover { color: #fff; }',
41
+ '.' + ID_PREFIX + '-msgs { padding: 14px; max-height: 340px; overflow-y: auto; font-size: 14px; line-height: 1.5; background: #fff; color: #222; }',
42
+ '.' + ID_PREFIX + '-msgs .' + ID_PREFIX + '-user { text-align: right; margin: 6px 0; }',
43
+ '.' + ID_PREFIX + '-msgs .' + ID_PREFIX + '-user::before { content: "Bạn: "; color: #007aff; font-weight: 700; }',
44
+ '.' + ID_PREFIX + '-msgs .' + ID_PREFIX + '-ai { margin: 6px 0; white-space: pre-wrap; word-break: break-word; }',
45
+ '.' + ID_PREFIX + '-msgs .' + ID_PREFIX + '-ai::before { content: "AI: "; color: #e84393; font-weight: 700; }',
46
+ '.' + ID_PREFIX + '-msgs .' + ID_PREFIX + '-err { margin: 6px 0; color: #c0392b; white-space: pre-wrap; word-break: break-word; }',
47
+ '.' + ID_PREFIX + '-input { display: flex; border-top: 1px solid #333; background: #1d1d2e; }',
48
+ '.' + ID_PREFIX + '-input input { flex: 1; padding: 10px 12px; border: none; outline: none; background: #1d1d2e; color: #fff; font-size: 14px; }',
49
+ '.' + ID_PREFIX + '-input button { background: #e84393; color: #fff; border: none; padding: 0 16px; cursor: pointer; font-size: 14px; }',
50
+ '.' + ID_PREFIX + '-input button:disabled { opacity: .5; cursor: wait; }',
51
+ '.' + ID_PREFIX + '-input button:hover:not(:disabled) { background: #d63384; }'
52
+ ].join('\n');
53
+ document.head.appendChild(st);
54
+ }
55
+
56
+ function el(tag, className, html) {
57
+ var n = document.createElement(tag);
58
+ if (className) n.className = className;
59
+ if (html !== undefined) n.innerHTML = html;
60
+ return n;
61
+ }
62
+
63
+ function escapeHtml(s) {
64
+ return String(s).replace(/[&<>"']/g, function (c) {
65
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
66
+ });
67
+ }
68
+
69
+ function build() {
70
+ injectCss();
71
+
72
+ var fab = el('button', ID_PREFIX + '-fab', '&#128172;');
73
+ fab.title = 'Chat với trợ lý gợi ý anime/manga';
74
+
75
+ var box = el('div', ID_PREFIX + '-box');
76
+ box.innerHTML =
77
+ '<div class="' + ID_PREFIX + '-head">' +
78
+ '<h3>Chatbox ANIVIETDB</h3>' +
79
+ '<select class="' + ID_PREFIX + '-model" id="' + ID_PREFIX + '-model" aria-label="Chọn mô hình">' +
80
+ Object.keys(MODELS).map(function (k) {
81
+ return '<option value="' + k + '">' + MODEL_LABELS[k] + '</option>';
82
+ }).join('') +
83
+ '</select>' +
84
+ '<button class="' + ID_PREFIX + '-close" id="' + ID_PREFIX + '-close" aria-label="Đóng">&#10005;</button>' +
85
+ '</div>';
86
+ var msgs = el('div', ID_PREFIX + '-msgs');
87
+ msgs.innerHTML = '<div class="' + ID_PREFIX + '-ai">Chào bạn! Mình là trợ lý gợi ý anime, manga và light novel của ANIVIETDB. Bạn muốn xem gì hôm nay?</div>';
88
+ box.appendChild(msgs);
89
+ var inputRow = el('div', ID_PREFIX + '-input');
90
+ var inp = el('input');
91
+ inp.type = 'text';
92
+ inp.placeholder = 'Nhập tin nhắn...';
93
+ inp.autocomplete = 'off';
94
+ var sendBtn = el('button', null, 'Gửi');
95
+ inputRow.appendChild(inp);
96
+ inputRow.appendChild(sendBtn);
97
+ box.appendChild(inputRow);
98
+ document.body.appendChild(box);
99
+ document.body.appendChild(fab);
100
+
101
+ var closeBtn = box.querySelector('#' + ID_PREFIX + '-close');
102
+ var modelSel = box.querySelector('#' + ID_PREFIX + '-model');
103
+
104
+ function currentModel() {
105
+ var saved = null;
106
+ try { saved = localStorage.getItem('anivietdb-chat-model'); } catch (e) {}
107
+ return MODELS[saved] ? saved : 'nivip';
108
+ }
109
+
110
+ modelSel.value = currentModel();
111
+ modelSel.addEventListener('change', function () {
112
+ try { localStorage.setItem('anivietdb-chat-model', modelSel.value); } catch (e) {}
113
+ });
114
+
115
+ function toggle(show) {
116
+ box.style.display = show ? 'block' : 'none';
117
+ fab.style.display = show ? 'none' : 'block';
118
+ if (show) inp.focus();
119
+ }
120
+
121
+ function send() {
122
+ var msg = inp.value.trim();
123
+ if (!msg) return;
124
+ inp.value = '';
125
+ msgs.innerHTML += '<div class="' + ID_PREFIX + '-user">' + escapeHtml(msg) + '</div>';
126
+ msgs.scrollTop = msgs.scrollHeight;
127
+ var p = el('div', ID_PREFIX + '-ai', '...');
128
+ msgs.appendChild(p);
129
+ msgs.scrollTop = msgs.scrollHeight;
130
+ sendBtn.disabled = true;
131
+ inp.disabled = true;
132
+ fetch(baseUrl().replace(/\/+$/, '') + '/chat', {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({ message: msg, model: modelSel.value })
136
+ }).then(function (r) {
137
+ return r.json().catch(function () { return {}; }).then(function (d) {
138
+ if (!r.ok) {
139
+ var e = new Error((d && d.error) || ('HTTP ' + r.status));
140
+ e.status = r.status;
141
+ throw e;
142
+ }
143
+ return d;
144
+ });
145
+ }).then(function (d) {
146
+ p.className = ID_PREFIX + '-ai';
147
+ p.textContent = d.reply || '...';
148
+ msgs.scrollTop = msgs.scrollHeight;
149
+ }).catch(function (e) {
150
+ p.className = ID_PREFIX + '-err';
151
+ p.textContent = 'Lỗi: ' + (e.message || 'không rõ');
152
+ msgs.scrollTop = msgs.scrollHeight;
153
+ }).finally(function () {
154
+ sendBtn.disabled = false;
155
+ inp.disabled = false;
156
+ inp.focus();
157
+ });
158
+ }
159
+
160
+ fab.addEventListener('click', function () { toggle(true); });
161
+ closeBtn.addEventListener('click', function () { toggle(false); });
162
+ sendBtn.addEventListener('click', send);
163
+ inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') send(); });
164
+ }
165
+
166
+ function init() {
167
+ if (document.getElementById(ID_PREFIX + '-fab')) return;
168
+ build();
169
+ }
170
+
171
+ if (document.readyState === 'loading') {
172
+ document.addEventListener('DOMContentLoaded', init);
173
+ } else {
174
+ init();
175
+ }
176
+ })();
control-panel.html ADDED
@@ -0,0 +1,1348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
7
+ <meta http-equiv="Pragma" content="no-cache">
8
+ <meta http-equiv="Expires" content="0">
9
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%23151515'/><text x='50' y='68' font-size='52' font-family='Arial' font-weight='bold' fill='%23ff5e00' text-anchor='middle'>A</text></svg>">
10
+ <title>Control Panel - ANIVIETDB</title>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="style.css">
13
+ <style>
14
+ :root {
15
+ --cp-bg: #101216;
16
+ --cp-panel: #16181f;
17
+ --cp-panel-2: #1d2029;
18
+ --cp-border: #262a35;
19
+ --cp-ink: #e8eaf2;
20
+ --cp-muted: #8b90a2;
21
+ --cp-accent: #ff5e00;
22
+ --cp-accent-2: #2abdbb;
23
+ --cp-purple: #a3419f;
24
+ }
25
+ * { box-sizing: border-box; margin: 0; padding: 0; }
26
+ html, body { height: 100%; }
27
+ body {
28
+ font-family: 'Inter', 'Roboto', sans-serif;
29
+ background: var(--cp-bg);
30
+ min-height: 100vh; color: var(--cp-ink);
31
+ display: flex;
32
+ flex-direction: row;
33
+ padding: 0;
34
+ }
35
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
36
+ ::-webkit-scrollbar-thumb { background: #2e3342; border-radius: 8px; }
37
+ ::-webkit-scrollbar-track { background: transparent; }
38
+
39
+ /* ---------- Sidebar ---------- */
40
+ .cp-side {
41
+ width: 248px; min-width: 248px;
42
+ background: var(--cp-panel);
43
+ border-right: 1px solid var(--cp-border);
44
+ display: flex; flex-direction: column;
45
+ position: sticky; top: 0; height: 100vh;
46
+ z-index: 40;
47
+ }
48
+ .cp-logo {
49
+ display: flex; align-items: center; gap: 10px;
50
+ padding: 18px 20px; border-bottom: 1px solid var(--cp-border);
51
+ }
52
+ .cp-logo-mark {
53
+ width: 38px; height: 38px; border-radius: 10px;
54
+ background: linear-gradient(135deg, #ff5e00, #a3419f);
55
+ display: grid; place-items: center;
56
+ font-weight: 800; font-size: 18px; color: #fff;
57
+ box-shadow: 0 4px 14px rgba(255,94,0,.35);
58
+ }
59
+ .cp-logo-name { font-weight: 700; font-size: 14px; line-height: 1.15; }
60
+ .cp-logo-sub { font-size: 11px; color: var(--cp-muted); }
61
+ .cp-nav { padding: 12px 12px; flex: 1; overflow-y: auto; }
62
+ .cp-nav-label {
63
+ font-size: 10px; text-transform: uppercase; letter-spacing: .12em;
64
+ color: #5d6275; padding: 12px 12px 6px;
65
+ }
66
+ .cp-nav-item {
67
+ display: flex; align-items: center; gap: 12px;
68
+ padding: 10px 12px; margin: 2px 0;
69
+ border-radius: 10px; color: var(--cp-muted);
70
+ font-size: 13.5px; font-weight: 500;
71
+ cursor: pointer; text-decoration: none; transition: all .15s;
72
+ border: 1px solid transparent;
73
+ }
74
+ .cp-nav-item .ico { width: 18px; text-align: center; font-size: 15px; }
75
+ .cp-nav-item:hover { background: var(--cp-panel-2); color: var(--cp-ink); }
76
+ .cp-nav-item.active {
77
+ background: linear-gradient(135deg, rgba(255,94,0,.16), rgba(163,65,159,.14));
78
+ border-color: rgba(255,94,0,.35);
79
+ color: #fff;
80
+ }
81
+ .cp-nav-foot {
82
+ padding: 14px 16px; border-top: 1px solid var(--cp-border);
83
+ display: flex; align-items: center; gap: 10px;
84
+ }
85
+ .cp-avatar {
86
+ width: 36px; height: 36px; border-radius: 50%;
87
+ background: linear-gradient(135deg, #2abdbb, #2b1d54);
88
+ display: grid; place-items: center; font-weight: 700; color: #fff;
89
+ flex-shrink: 0;
90
+ }
91
+ .cp-nav-foot-info { min-width: 0; flex: 1; }
92
+ .cp-nav-foot-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
93
+ .cp-nav-foot-sub { font-size: 11px; color: var(--cp-muted); }
94
+ .cp-auth-btn {
95
+ background: none; border: 1px solid var(--cp-border); color: var(--cp-muted);
96
+ border-radius: 8px; padding: 5px 10px; font-size: 11px; font-weight: 600;
97
+ cursor: pointer; text-decoration: none; font-family: inherit; transition: all .15s;
98
+ }
99
+ .cp-auth-btn:hover { color: var(--cp-accent); border-color: rgba(255,94,0,.5); }
100
+
101
+ /* ---------- Main ---------- */
102
+ .cp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
103
+ .cp-topbar {
104
+ display: flex; align-items: center; justify-content: space-between; gap: 16px;
105
+ padding: 14px 24px;
106
+ background: rgba(16,18,22,.86);
107
+ backdrop-filter: blur(12px);
108
+ -webkit-backdrop-filter: blur(12px);
109
+ border-bottom: 1px solid var(--cp-border);
110
+ position: sticky; top: 0; z-index: 30;
111
+ }
112
+ .cp-topbar-title { font-size: 16px; font-weight: 700; }
113
+ .cp-topbar-title small { display: block; font-size: 11.5px; color: var(--cp-muted); font-weight: 500; }
114
+ .cp-topbar-links { display: flex; align-items: center; gap: 8px; }
115
+ .cp-btn {
116
+ padding: 8px 14px; border-radius: 10px; border: 1px solid var(--cp-border);
117
+ background: var(--cp-panel-2); color: var(--cp-ink); font-size: 12.5px; font-weight: 600;
118
+ cursor: pointer; text-decoration: none; display: inline-block; font-family: inherit;
119
+ transition: all .15s;
120
+ }
121
+ .cp-btn:hover { border-color: var(--cp-accent); color: var(--cp-accent); }
122
+ .cp-btn-sm { padding: 5px 10px; font-size: 11.5px; border-radius: 8px; }
123
+ .cp-btn-danger { border-color: rgba(255,138,138,.4); color: #ff8a8a; }
124
+ .cp-btn-danger:hover { background: rgba(255,138,138,.08); border-color: #ff8a8a; }
125
+ .cp-burger { display: none; background: none; border: 1px solid var(--cp-border); color: var(--cp-ink); border-radius: 10px; padding: 8px 12px; font-size: 16px; }
126
+
127
+ .cp-content { padding: 24px; flex: 1; }
128
+ /* ---------- Cards / stats / tables ---------- */
129
+ .card {
130
+ background: var(--cp-panel);
131
+ border: 1px solid var(--cp-border);
132
+ border-radius: 16px; padding: 22px; margin-bottom: 24px;
133
+ }
134
+ .card h2 { font-size: 16px; margin-bottom: 4px; }
135
+ .card .sub { font-size: 12px; color: var(--cp-muted); margin-bottom: 16px; }
136
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
137
+ th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid rgba(120,100,230,0.15); }
138
+ th { color: #9a9ab0; font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: .4px; }
139
+ tr:hover td { background: rgba(120,100,230,0.06); }
140
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 20px; font-size: 11px; font-weight: 700; }
141
+ .badge-admin { background: rgba(126,231,135,0.15); color: #7ee787; }
142
+ .badge-user { background: rgba(120,100,230,0.15); color: #b9adff; }
143
+ .btn-del {
144
+ padding: 5px 12px; border-radius: 8px; border: 1px solid rgba(255,138,138,0.5);
145
+ background: transparent; color: #ff8a8a; font-size: 12px; font-weight: 600; cursor: pointer;
146
+ }
147
+ .btn-del:hover { background: rgba(255,138,138,0.15); }
148
+ .btn-del[disabled] { opacity: .4; cursor: not-allowed; }
149
+ .stat-grid {
150
+ display: grid;
151
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
152
+ gap: 14px;
153
+ margin-bottom: 24px;
154
+ }
155
+ .stat-card {
156
+ background: var(--cp-panel);
157
+ border: 1px solid var(--cp-border);
158
+ border-radius: 14px;
159
+ padding: 16px 18px;
160
+ display: flex; flex-direction: column; gap: 6px;
161
+ transition: border-color .2s;
162
+ }
163
+ .stat-card:hover { border-color: rgba(255,94,0,.4); }
164
+ .stat-label { font-size: 12px; color: var(--cp-muted); display: flex; align-items: center; gap: 8px; }
165
+ .stat-label .dot { width: 8px; height: 8px; border-radius: 50%; }
166
+ .stat-value { font-size: 22px; font-weight: 800; letter-spacing: -.01em; }
167
+ .stat-value small { font-size: 12px; color: var(--cp-muted); font-weight: 500; }
168
+ .stat-delta { font-size: 11.5px; font-weight: 600; }
169
+ .stat-delta.up { color: #2abdbb; }
170
+ .stat-delta.down { color: #ff5e00; }
171
+ .content-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
172
+ @media (max-width: 720px) { .content-grid { grid-template-columns: 1fr; } }
173
+ .content-box {
174
+ border: 1px solid rgba(120,100,230,0.2); border-radius: 12px; padding: 14px;
175
+ background: rgba(255,255,255,0.02);
176
+ }
177
+ .content-box h3 { font-size: 14px; margin-bottom: 10px; color: #b9adff; }
178
+ .content-box ul { list-style: none; max-height: 320px; overflow-y: auto; }
179
+ .content-box li {
180
+ padding: 7px 10px; border-radius: 8px; font-size: 12.5px;
181
+ display: flex; justify-content: space-between; gap: 10px;
182
+ }
183
+ .content-box li:nth-child(odd) { background: rgba(120,100,230,0.07); }
184
+ .content-box li .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
185
+ .content-box li .s { color: #9a9ab0; flex-shrink: 0; font-size: 11px; }
186
+ .nsfw-tag { color: #ff8a8a; font-weight: 700; }
187
+ .sfw-tag { color: #7ee787; font-weight: 700; }
188
+ .cat-tabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
189
+ .cat-tab {
190
+ padding: 7px 16px; border-radius: 20px; border: 1px solid rgba(120,100,230,0.3);
191
+ background: transparent; color: #9a9ab0; font-size: 13px; font-weight: 600; cursor: pointer;
192
+ transition: all .2s; font-family: inherit;
193
+ }
194
+ .cat-tab:hover { color: #fff; border-color: #8b7bff; }
195
+ .cat-tab.active { background: linear-gradient(135deg, #7c5cff, #5a3bd6); color: #fff; border-color: transparent; }
196
+ .btn-go {
197
+ display: inline-block; padding: 4px 12px; border-radius: 8px; font-size: 12px; font-weight: 600;
198
+ border: 1px solid rgba(126,231,135,0.45); color: #7ee787; text-decoration: none; text-align: center;
199
+ }
200
+ .btn-go:hover { background: rgba(126,231,135,0.15); }
201
+ .table-wrap { max-height: 420px; overflow-y: auto; border: 1px solid rgba(120,100,230,0.15); border-radius: 12px; }
202
+ .msg { color: #9a9ab0; font-size: 13px; padding: 8px 0; }
203
+ .ok { color: #7ee787; }
204
+ .err { color: #ff8a8a; }
205
+
206
+ .chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
207
+ .chip {
208
+ background: var(--cp-panel);
209
+ border: 1px solid var(--cp-border);
210
+ color: var(--cp-muted);
211
+ border-radius: 999px;
212
+ padding: 7px 16px;
213
+ font-size: 12.5px; font-weight: 600;
214
+ cursor: pointer; transition: all .15s;
215
+ font-family: inherit;
216
+ }
217
+ .chip:hover { color: var(--cp-ink); border-color: #3a4052; }
218
+ .chip.active { background: linear-gradient(135deg, #ff5e00, #e04a00); color: #fff; border-color: transparent; box-shadow: 0 4px 14px rgba(255,94,0,.3); }
219
+
220
+ .product-grid {
221
+ display: grid;
222
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
223
+ gap: 14px;
224
+ }
225
+ .product-card {
226
+ background: var(--cp-panel);
227
+ border: 1px solid var(--cp-border);
228
+ border-radius: 14px;
229
+ overflow: hidden;
230
+ display: flex; flex-direction: column;
231
+ transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
232
+ }
233
+ .product-card:hover { transform: translateY(-6px); border-color: rgba(255,94,0,.45); box-shadow: 0 14px 30px rgba(0,0,0,.45); }
234
+ .product-thumb { position: relative; aspect-ratio: 3/3.6; overflow: hidden; background: #0d0f14; }
235
+ .product-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
236
+ .product-thumb::after { content: ""; position: absolute; inset: 0; background: linear-gradient(to top, rgba(13,15,20,.55), transparent 40%); pointer-events: none; }
237
+ .product-tag {
238
+ position: absolute; top: 10px; left: 10px; z-index: 2;
239
+ background: rgba(13,15,20,.8); backdrop-filter: blur(4px);
240
+ color: #ffb48a; font-size: 10px; font-weight: 700; letter-spacing: .03em;
241
+ border-radius: 999px; padding: 4px 10px;
242
+ border: 1px solid rgba(255,94,0,.4);
243
+ }
244
+ .product-body { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 5px; flex: 1; }
245
+ .product-anime { font-size: 10px; color: var(--cp-muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
246
+ .product-name { font-size: 12.5px; font-weight: 600; line-height: 1.35; color: var(--cp-ink); }
247
+ .product-stock { font-size: 11px; font-weight: 700; }
248
+ .product-stock.low { color: #ff8a8a; }
249
+ .product-stock.ok { color: #2abdbb; }
250
+ .product-price { display: flex; align-items: baseline; gap: 8px; }
251
+ .price-now { font-size: 15px; font-weight: 800; color: #e84393; }
252
+ .price-old { font-size: 12px; color: var(--cp-muted); text-decoration: line-through; }
253
+
254
+ .empty-shop { text-align: center; padding: 50px 20px; color: var(--cp-muted); }
255
+ .empty-shop .big { font-size: 34px; margin-bottom: 10px; }
256
+ /* ---------- Confirm modal ---------- */
257
+ .confirm-overlay {
258
+ position: fixed; inset: 0; z-index: 1000; display: none;
259
+ align-items: center; justify-content: center; padding: 20px;
260
+ background: rgba(8, 9, 18, 0.72); backdrop-filter: blur(4px);
261
+ }
262
+ .confirm-overlay.active { display: flex; }
263
+ .confirm-box {
264
+ width: 100%; max-width: 380px; background: #16182b;
265
+ border: 1px solid rgba(255,138,138,0.35); border-radius: 16px;
266
+ padding: 24px; box-shadow: 0 18px 50px rgba(0,0,0,.55);
267
+ animation: confirmPop .22s ease;
268
+ }
269
+ @keyframes confirmPop {
270
+ from { transform: scale(.92); opacity: 0; }
271
+ to { transform: scale(1); opacity: 1; }
272
+ }
273
+ .confirm-icon {
274
+ width: 52px; height: 52px; margin: 0 auto 14px; border-radius: 50%;
275
+ display: flex; align-items: center; justify-content: center;
276
+ font-size: 24px; color: #ff8a8a; background: rgba(255,138,138,0.12);
277
+ border: 1px solid rgba(255,138,138,0.3);
278
+ }
279
+ .confirm-title { font-size: 17px; font-weight: 700; text-align: center; color: #fff; margin-bottom: 8px; }
280
+ .confirm-text { font-size: 13px; text-align: center; color: #9a9ab0; margin-bottom: 22px; line-height: 1.6; }
281
+ .confirm-text b { color: #ff8a8a; }
282
+ .confirm-actions { display: flex; gap: 10px; }
283
+ .confirm-btn {
284
+ flex: 1; padding: 11px 0; border-radius: 10px; font-size: 14px; font-weight: 700; cursor: pointer;
285
+ border: 1px solid transparent; transition: all .2s; font-family: inherit;
286
+ }
287
+ .confirm-cancel { background: transparent; border-color: rgba(120,100,230,0.4); color: #b9adff; }
288
+ .confirm-cancel:hover { background: rgba(120,100,230,0.1); }
289
+ .confirm-ok { background: linear-gradient(135deg, #ff6b81, #f0334a); color: #fff; }
290
+ .confirm-ok:hover { filter: brightness(1.1); }
291
+ .cp-reason {
292
+ width: 100%; margin: 0 0 18px; padding: 11px 13px;
293
+ background: #0f1122; border: 1px solid rgba(255,138,138,0.35);
294
+ border-radius: 10px; color: #e8eaf2; font-size: 13px; font-family: inherit;
295
+ resize: vertical; outline: none;
296
+ }
297
+ .cp-reason:focus { border-color: #ff8a8a; }
298
+ .cp-reason-err { display: none; font-size: 12px; color: #ff8a8a; margin: -10px 0 14px; }
299
+ .cp-reason-err.show { display: block; }
300
+ #orderModal .confirm-ok.ok-ship { background: linear-gradient(135deg, #2abdbb, #1d9a99); }
301
+ #orderModal .confirm-ok.ok-ship:hover { filter: brightness(1.1); }
302
+
303
+ /* ---------- Mobile ---------- */
304
+ @media (max-width: 992px) {
305
+ .cp-side { position: fixed; left: 0; top: 0; transform: translateX(-100%); transition: transform .25s; }
306
+ .cp-side.open { transform: translateX(0); box-shadow: 0 0 40px rgba(0,0,0,.6); }
307
+ .cp-burger { display: block; }
308
+ .cp-content { padding: 16px; }
309
+ .stat-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
310
+ }
311
+ </style>
312
+ </head>
313
+ <body>
314
+ <!-- Sidebar -->
315
+ <aside class="cp-side" id="cpSide">
316
+ <div class="cp-logo">
317
+ <div class="cp-logo-mark">A</div>
318
+ <div>
319
+ <div class="cp-logo-name">ANIVIETDB Control</div>
320
+ <div class="cp-logo-sub">Panel · Quản trị</div>
321
+ </div>
322
+ </div>
323
+ <nav class="cp-nav">
324
+ <div class="cp-nav-label">Tổng quan</div>
325
+ <a class="cp-nav-item active" data-panel="overview"><span class="ico">&#128200;</span> Thống kê</a>
326
+ <div class="cp-nav-label">Quản trị</div>
327
+ <a class="cp-nav-item" data-panel="users"><span class="ico">&#128101;</span> Người dùng</a>
328
+ <a class="cp-nav-item" data-panel="content"><span class="ico">&#128230;</span> Nội dung</a>
329
+ <div class="cp-nav-label">Cửa hàng</div>
330
+ <a class="cp-nav-item" data-panel="products"><span class="ico">&#127891;</span> Sản phẩm</a>
331
+ <a class="cp-nav-item" data-panel="orders"><span class="ico">&#128722;</span> Đơn hàng</a>
332
+ <a class="cp-nav-item" data-panel="customers"><span class="ico">&#128101;</span> Khách hàng</a>
333
+ <a class="cp-nav-item" data-panel="reports"><span class="ico">&#128200;</span> Báo cáo</a>
334
+ <div class="cp-nav-label">Hệ thống</div>
335
+ <a class="cp-nav-item" data-panel="updates"><span class="ico">&#128260;</span> Cập nhật</a>
336
+ <div class="cp-nav-label">Liên kết</div>
337
+ <a class="cp-nav-item" href="index.html"><span class="ico">&#127968;</span> Trang chủ</a>
338
+ <a class="cp-nav-item" href="sfw/index.html"><span class="ico">&#127916;</span> Xem Anime</a>
339
+ <a class="cp-nav-item" href="shop/"><span class="ico">&#128722;</span> Cửa hàng</a>
340
+ </nav>
341
+ <div class="cp-nav-foot">
342
+ <div class="cp-avatar" id="userAvatar">A</div>
343
+ <div class="cp-nav-foot-info">
344
+ <div class="cp-nav-foot-name" id="who">—</div>
345
+ <div class="cp-nav-foot-sub" id="userRole">Chưa xác thực</div>
346
+ </div>
347
+ <button class="cp-auth-btn" id="logoutBtn">Thoát</button>
348
+ </div>
349
+ </aside>
350
+
351
+ <!-- Main -->
352
+ <main class="cp-main">
353
+ <div class="cp-topbar">
354
+ <div style="display:flex;align-items:center;gap:12px;">
355
+ <button class="cp-burger" id="cpBurger">&#9776;</button>
356
+ <div class="cp-topbar-title">
357
+ Thống kê
358
+ <small>ANIVIETDB Control Panel</small>
359
+ </div>
360
+ </div>
361
+ <div class="cp-topbar-links">
362
+ <a class="cp-btn" href="shop/">&#128722; Cửa hàng</a>
363
+ <a class="cp-btn" href="index.html">&#127968; Trang chủ</a>
364
+ <a class="cp-btn" href="sfw/index.html">&#127916; Xem Anime</a>
365
+ </div>
366
+ </div>
367
+
368
+ <div class="cp-content">
369
+ <div id="accessDenied" style="display:none;">
370
+ <div class="card">
371
+ <div class="msg" id="deniedMsg">Bạn không có quyền truy cập.</div>
372
+ </div>
373
+ </div>
374
+
375
+ <div id="panel">
376
+ <!-- Panel: Thống kê -->
377
+ <div id="panel-overview">
378
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
379
+ <span style="color:var(--cp-muted);font-size:13px;" id="overviewInfo"></span>
380
+ <button class="cp-btn" id="refreshOverview">&#128260; Làm mới</button>
381
+ </div>
382
+ <div class="stat-grid" id="overviewStats"></div>
383
+ <div class="card">
384
+ <h2>Nội dung trang web</h2>
385
+ <p class="sub">Số lượng theo từng phân loại (anime / manga / light novel / gallery)</p>
386
+ <div class="stat-grid" id="contentStats"></div>
387
+ </div>
388
+ </div>
389
+
390
+ <!-- Panel: Người dùng -->
391
+ <div id="panel-users" style="display:none;">
392
+ <div class="card">
393
+ <h2>Người dùng</h2>
394
+ <p class="sub">Tất cả tài khoản và lần đăng nhập gần nhất</p>
395
+ <div class="msg" id="usersMsg"></div>
396
+ <table>
397
+ <thead>
398
+ <tr>
399
+ <th>ID</th>
400
+ <th>Tên đăng nhập</th>
401
+ <th>Email</th>
402
+ <th>Vai trò</th>
403
+ <th>Số lần tham gia</th>
404
+ <th>Đăng nhập gần nhất</th>
405
+ <th>Ngày tạo</th>
406
+ <th></th>
407
+ </tr>
408
+ </thead>
409
+ <tbody id="usersBody"></tbody>
410
+ </table>
411
+ </div>
412
+ </div>
413
+
414
+ <!-- Panel: Nội dung -->
415
+ <div id="panel-content" style="display:none;">
416
+ <div class="card">
417
+ <h2>Quản lý trang web</h2>
418
+ <p class="sub">Nội dung đã upload (anime / manga)</p>
419
+ <div class="cat-tabs" id="catTabs"></div>
420
+ <div class="table-wrap">
421
+ <table>
422
+ <thead>
423
+ <tr>
424
+ <th style="width:50px;">#</th>
425
+ <th>Tên</th>
426
+ <th style="width:90px;">Phân loại</th>
427
+ <th style="width:140px;">Trạng thái</th>
428
+ <th style="width:70px;"></th>
429
+ </tr>
430
+ </thead>
431
+ <tbody id="contentBody"></tbody>
432
+ </table>
433
+ </div>
434
+ <div class="msg" id="contentMsg"></div>
435
+ </div>
436
+ </div>
437
+ <!-- Panel: Sản phẩm -->
438
+ <div id="panel-products" style="display:none;">
439
+ <div class="card">
440
+ <h2>Quản lý sản phẩm</h2>
441
+ <p class="sub">Figure, đồ lưu niệm, manga & Blu-ray đang bán trên ANIVIETDB Shop</p>
442
+ <div style="display:flex;align-items:center;justify-content:space-between;margin:16px 0 10px;">
443
+ <span style="color:var(--cp-muted);font-size:13px;" id="cpProductsInfo">Đang tải...</span>
444
+ <button class="cp-btn" id="refreshProducts">&#128260; Làm mới</button>
445
+ </div>
446
+ <div class="chip-row" id="cpCatChips">
447
+ <button class="chip active" data-cat="all">Tất cả</button>
448
+ </div>
449
+ <div class="product-grid" id="cpProductGrid"></div>
450
+ <div class="empty-shop" id="cpEmptyProducts" style="display:none;">
451
+ <div class="big">&#128517;</div>
452
+ Không có sản phẩm phù hợp.
453
+ </div>
454
+ </div>
455
+ </div>
456
+
457
+ <!-- Panel: Đơn hàng -->
458
+ <div id="panel-orders" style="display:none;">
459
+ <div class="card">
460
+ <h2>Đơn hàng</h2>
461
+ <p class="sub">Đơn hàng từ khách hàng (từ DB shop-system)</p>
462
+ <div style="display:flex;align-items:center;justify-content:space-between;margin:16px 0 10px;">
463
+ <span style="color:var(--cp-muted);font-size:13px;" id="ordersInfo">Đang tải...</span>
464
+ <button class="cp-btn" id="refreshOrders">&#128260; Làm mới</button>
465
+ </div>
466
+ <div class="table-wrap">
467
+ <table>
468
+ <thead>
469
+ <tr>
470
+ <th>Mã đơn</th>
471
+ <th>Khách hàng</th>
472
+ <th>Sản phẩm</th>
473
+ <th style="width:90px;">Số món</th>
474
+ <th style="width:110px;">Tổng tiền</th>
475
+ <th style="width:90px;">Trạng thái</th>
476
+ <th style="width:150px;">Thời gian</th>
477
+ <th style="width:110px;">Thao tác</th>
478
+ </tr>
479
+ </thead>
480
+ <tbody id="ordersBody"></tbody>
481
+ </table>
482
+ </div>
483
+ <div class="msg" id="ordersMsg"></div>
484
+ </div>
485
+ </div>
486
+
487
+ <!-- Panel: Khách hàng -->
488
+ <div id="panel-customers" style="display:none;">
489
+ <div class="card">
490
+ <h2>Khách hàng</h2>
491
+ <p class="sub">Mỗi tài khoản là 1 khách hàng — số đơn / món / chi tiêu lấy từ DB shop-system</p>
492
+ <div class="stat-grid" id="customerStats"></div>
493
+ <div style="display:flex;align-items:center;justify-content:space-between;margin:16px 0 10px;">
494
+ <span style="color:var(--cp-muted);font-size:13px;">Danh sách từ máy chủ <code>shop-system</code></span>
495
+ <button class="cp-btn" id="refreshCustomers">&#128260; Làm mới</button>
496
+ </div>
497
+ <table>
498
+ <thead>
499
+ <tr>
500
+ <th>ID</th>
501
+ <th>Khách hàng</th>
502
+ <th>Đơn hàng</th>
503
+ <th>Món đã mua</th>
504
+ <th>Tổng chi tiêu</th>
505
+ <th>Điểm</th>
506
+ <th>Ngày tạo</th>
507
+ </tr>
508
+ </thead>
509
+ <tbody id="customersBody"></tbody>
510
+ </table>
511
+ </div>
512
+ </div>
513
+
514
+ <!-- Panel: Báo cáo -->
515
+ <div id="panel-reports" style="display:none;">
516
+ <div class="card">
517
+ <div style="display:flex;align-items:center;justify-content:space-between;">
518
+ <div>
519
+ <h2>Báo cáo doanh thu</h2>
520
+ <p class="sub">Tổng hợp doanh thu cửa hàng</p>
521
+ </div>
522
+ <button class="cp-btn" id="refreshReports">&#128260; Làm mới</button>
523
+ </div>
524
+ <div class="stat-grid" id="reportStats"></div>
525
+ <div class="content-box">
526
+ <h3>Doanh thu 7 ngày gần nhất</h3>
527
+ <canvas id="revenueChart" width="800" height="260" style="width:100%;height:auto;display:block;background:#0d0f14;border-radius:10px;"></canvas>
528
+ <div class="msg" style="margin-top:10px;">Biểu đồ cập nhật khi mở panel Báo cáo.</div>
529
+ </div>
530
+ </div>
531
+ </div>
532
+
533
+ <!-- Panel: Cập nhật -->
534
+ <div id="panel-updates" style="display:none;">
535
+ <div class="card">
536
+ <h2>Cập nhật hệ thống</h2>
537
+ <p class="sub">Lịch sử cập nhật phiên bản ANIVIETDB</p>
538
+ <div class="content-box" style="line-height:1.7;">
539
+ <div class="update-item">
540
+ <h3 style="margin:0 0 4px;">v2.4 &#8212; Lu&#7891;ng thanh to&#225;n &amp; h&#7911;y &#273;&#417;n</h3>
541
+ <p class="update-date">17/08/2026</p>
542
+ <ul>
543
+ <li>&#272;&#417;n h&#224;ng c&#243; th&#234;m tr&#7841;ng th&#225;i <code>Ch&#7901; thanh to&#225;n</code> &#8212; kh&#225;ch ph&#7843;i b&#7845;m &#8220;T&#244;i &#273;&#227; chuy&#7875;n kho&#7843;n&#8221; &#273;&#7875; &#273;&#417;n v&#224;o <code>Ch&#7901; x&#225;c nh&#7853;n</code>; n&#7871;u kh&#244;ng thanh to&#225;n trong 5 ph&#250;t, &#273;&#417;n t&#7921; &#273;&#7897;ng h&#7911;y.</li>
544
+ <li>Trang thanh to&#225;n m&#7899;i: QR + th&#244;ng tin &#273;&#417;n (m&#227; &#273;&#417;n, ng&#432;&#7901;i mua, s&#7843;n ph&#7849;m, t&#7893;ng ti&#7873;n), n&#250;t &#8220;T&#244;i &#273;&#227; chuy&#7875;n kho&#7843;n&#8221; ch&#7901; 5 gi&#226;y r&#7891;i quay v&#7873;.</li>
545
+ <li>Kh&#225;ch c&#243; th&#7875; h&#7911;y &#273;&#417;n (<code>Ch&#7901; thanh to&#225;n</code> / <code>Ch&#7901; x&#225;c nh&#7853;n</code>) k&#232;m l&#237; do b&#7857;ng popup; admin h&#7911;y b&#7845;t k&#7923; &#273;&#417;n n&#224;o k&#232;m l&#237; do.</li>
546
+ <li>&#272;&#417;n <code>&#272;&#227; h&#7911;y</code> hi&#7875;n l&#237; do h&#7911;y; stock &#273;&#432;&#7907;c ho&#224;n l&#7841;i.</li>
547
+ <li>Pop-up thay cho alert tr&#236;nh duy&#7879;t khi x&#225;c nh&#7853;n / h&#7911;y &#273;&#417;n; &#273;&#417;n h&#224;ng trong shop ch&#7881; hi&#7875;n &#273;&#417;n c&#7911;a ch&#237;nh m&#236;nh.</li>
548
+ <li>S&#7917;a l&#7895;i hi&#7875;n th&#7883; &#8220;s&#7843;n ph&#7849;m&#8221; trong gi&#7887; h&#224;ng.</li>
549
+ </ul>
550
+ </div>
551
+ <hr style="border:none;border-top:1px solid var(--cp-border);margin:16px 0;">
552
+ <div class="update-item">
553
+ <h3 style="margin:0 0 4px;">v2.3 &#8212; Thanh to&#225;n &amp; x&#225;c nh&#7853;n &#273;&#417;n h&#224;ng</h3>
554
+ <p class="update-date">17/08/2026</p>
555
+ <ul>
556
+ <li>Sau khi &#273;&#7863;t h&#224;ng, kh&#225;ch chuy&#7875;n qua trang thanh to&#225;n (QR &#7911;ng h&#7897; + m&#227; &#273;&#417;n + &#273;&#7871;m ng&#432;&#7907;c th&#7901;i gian thanh to&#225;n).</li>
557
+ <li>Admin b&#7845;m &#8220;X&#225;c nh&#7853;n&#8221; tr&#234;n &#273;&#417;n &#7903; tr&#7841;ng th&#225;i <code>Ch&#7901; x&#225;c nh&#7853;n</code> &#8594; &#273;&#417;n chuy&#7875;n sang <code>&#272;ang giao h&#224;ng</code>, t&#7921; &#273;&#7897;ng th&#224;nh <code>&#272;&#227; giao</code> sau 5 ph&#250;t.</li>
558
+ <li>Trang shop t&#7921; c&#7853;p nh&#7853;t tr&#7841;ng th&#225;i &#273;&#417;n h&#224;ng theo th&#7901;i gian th&#7921;c.</li>
559
+ </ul>
560
+ </div>
561
+ <hr style="border:none;border-top:1px solid var(--cp-border);margin:16px 0;">
562
+ <div class="update-item">
563
+ <h3 style="margin:0 0 4px;">v2.2 &#8212; Shop d&#7915; li&#7879;u th&#7921;c</h3>
564
+ <p class="update-date">17/08/2026</p>
565
+ <ul>
566
+ <li>S&#7843;n ph&#7849;m v&#224; &#273;&#417;n h&#224;ng l&#432;u tr&#432; tr&#234;n DB (worker <code>shop-system</code> + D1): danh m&#7909;c s&#7843;n ph&#7849;m c&#243; t&#7891;n kho (stock), mua h&#224;ng tr&#7915; stock.</li>
567
+ <li>Kh&#225;ch h&#224;ng = t&#224;i kho&#7843;n: m&#7895;i user l&#224; 1 kh&#225;ch h&#224;ng, th&#7889;ng k&#234; &#273;&#417;n h&#224;ng / m&#243;n / t&#7893;ng chi ti&#234;u t&#237;nh t&#7915; DB.</li>
568
+ <li>T&#7893;ng quan &amp; B&#225;o c&#225;o: doanh thu, &#273;&#417;n h&#224;ng, m&#243;n &#273;&#227; b&#225;n l&#7845;y s&#7889; li&#7879;u th&#7921;c t&#7915; DB (kh&#244;ng c&#242;n s&#7889; d&#7921; ki&#7871;n).</li>
569
+ <li>Control Panel: panel &#272;&#417;n h&#224;ng m&#7899;i + qu&#7843;n l&#253; t&#7891;n kho s&#7843;n ph&#7849;m (t&#259;ng/gi&#7843;m stock).</li>
570
+ </ul>
571
+ </div>
572
+ <hr style="border:none;border-top:1px solid var(--cp-border);margin:16px 0;">
573
+ <div class="update-item">
574
+ <h3 style="margin:0 0 4px;">v2.1 &#8212; Shop &amp; Control Panel</h3>
575
+ <p class="update-date">16/08/2026</p>
576
+ <ul>
577
+ <li>Thêm cửa hàng figure, đồ lưu niệm, manga &amp; Blu-ray (shop/index.html) — giỏ hàng, đặt hàng.</li>
578
+ <li>Control Panel: sidebar điều hướng, thống kê, quản lý sản phẩm, khách hàng, báo cáo, cài đặt.</li>
579
+ <li>Trang chủ thêm stage 3 giới thiệu shop với 4 sản phẩm tiêu biểu.</li>
580
+ <li>Đồng bộ khách hàng vào DB D1 riêng <b>shop-system</b> qua worker <code>shop-system</code>.</li>
581
+ </ul>
582
+ </div>
583
+ <hr style="border:none;border-top:1px solid var(--cp-border);margin:16px 0;">
584
+ <div class="update-item">
585
+ <h3 style="margin:0 0 4px;">v2.0 &#8212; Giao diện mới</h3>
586
+ <p class="update-date">15/08/2026</p>
587
+ <ul>
588
+ <li>Trang chủ Crunchyroll-style với hero 3D + poster grid + phim nổi bật.</li>
589
+ <li>Nền poster-grid stage 1 áp dụng toàn bộ trang (style.css).</li>
590
+ <li>Đổi tên nút &quot;Xem feature&quot; thành &quot;Xem phim nổi bật&quot;.</li>
591
+ </ul>
592
+ </div>
593
+ <hr style="border:none;border-top:1px solid var(--cp-border);margin:16px 0;">
594
+ <div class="update-item">
595
+ <h3 style="margin:0 0 4px;">v1.x &#8212; Nền tảng</h3>
596
+ <p class="update-date">Trước 15/08/2026</p>
597
+ <ul>
598
+ <li>Đăng ký / đăng nhập qua worker auth (sginup-loginsystem).</li>
599
+ <li>Sưu tầm anime, manga, manhwa, manhua, light novel &amp; gallery (SFW + NSFW).</li>
600
+ </ul>
601
+ </div>
602
+ </div>
603
+ </div>
604
+ </div>
605
+ </div>
606
+ </div>
607
+ </main>
608
+
609
+ <div class="confirm-overlay" id="confirmOverlay">
610
+ <div class="confirm-box">
611
+ <div class="confirm-icon">&#9888;</div>
612
+ <div class="confirm-title">Xóa người dùng?</div>
613
+ <div class="confirm-text">Bạn có chắc muốn xóa tài khoản <b id="confirmTarget"></b>? Hành động này không thể hoàn tác.</div>
614
+ <div class="confirm-actions">
615
+ <button class="confirm-btn confirm-cancel" id="confirmCancel">Hủy</button>
616
+ <button class="confirm-btn confirm-ok" id="confirmOk">Xóa</button>
617
+ </div>
618
+ </div>
619
+ </div>
620
+
621
+ <!-- Popup xác nhận / hủy đơn -->
622
+ <div class="confirm-overlay" id="orderModal">
623
+ <div class="confirm-box">
624
+ <div class="confirm-icon">&#128722;</div>
625
+ <div class="confirm-title" id="orderModalTitle">Xác nhận đơn</div>
626
+ <div class="confirm-text" id="orderModalText"></div>
627
+ <textarea id="orderModalReason" class="cp-reason" placeholder="Nhập lí do hủy (bắt buộc)..." rows="3" style="display:none;"></textarea>
628
+ <div class="cp-reason-err" id="orderModalReasonErr">⚠️ Vui lòng nhập lí do hủy đơn</div>
629
+ <div class="confirm-actions">
630
+ <button class="confirm-btn confirm-cancel" id="orderModalCancel">Đóng</button>
631
+ <button class="confirm-btn confirm-ok" id="orderModalOk">Xác nhận</button>
632
+ </div>
633
+ </div>
634
+ </div>
635
+ <script src="signup-loginsystem.js?v=9"></script>
636
+ <script>
637
+ var whoEl = document.getElementById('who');
638
+ var roleEl = document.getElementById('userRole');
639
+ var avatarEl = document.getElementById('userAvatar');
640
+ var panel = document.getElementById('panel');
641
+ var denied = document.getElementById('accessDenied');
642
+
643
+ var confirmOverlay = document.getElementById('confirmOverlay');
644
+ var confirmCancel = document.getElementById('confirmCancel');
645
+ var confirmOk = document.getElementById('confirmOk');
646
+
647
+ var PANELS = ['overview', 'users', 'content', 'products', 'orders', 'customers', 'reports', 'updates'];
648
+ var currentCat = 'anime';
649
+
650
+ var IMG = "https://phim.nguonc.com/public/images/";
651
+ var PRODUCTS = [];
652
+ var SHOP_API = localStorage.getItem("anivietdb-shop-api") || "https://shop-system.npgia-himlam.workers.dev/";
653
+ var shopCustomers = [];
654
+ var shopOrders = [];
655
+ var cpStats = null;
656
+
657
+ function api(url, init) {
658
+ var base = SHOP_API.replace(/\/$/, "");
659
+ var token = AuthSystem.getToken();
660
+ init = init || {};
661
+ init.headers = init.headers || {};
662
+ if (token) init.headers["Authorization"] = "Bearer " + token;
663
+ return fetch(base + url, init).then(function (r) {
664
+ return r.json().then(function (data) {
665
+ if (!r.ok) { var e = new Error(data.error || ("HTTP " + r.status)); e.status = r.status; e.data = data; throw e; }
666
+ return data;
667
+ });
668
+ });
669
+ }
670
+
671
+ function fmt(n) { return n.toLocaleString("vi-VN") + "&#273;"; }
672
+ function $(s) { return document.querySelector(s); }
673
+ function $$(s) { return document.querySelectorAll(s); }
674
+
675
+ function closeConfirm() {
676
+ confirmOverlay.classList.remove('active');
677
+ confirmOk.onclick = null;
678
+ }
679
+ confirmCancel.addEventListener('click', closeConfirm);
680
+ confirmOverlay.addEventListener('click', function (e) { if (e.target === confirmOverlay) closeConfirm(); });
681
+ document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeConfirm(); });
682
+
683
+ function switchPanel(name) {
684
+ $$(".cp-nav-item[data-panel]").forEach(function (a) { a.classList.toggle('active', a.dataset.panel === name); });
685
+ PANELS.forEach(function (n) {
686
+ var el = $('#panel-' + n);
687
+ if (el) el.style.display = n === name ? 'block' : 'none';
688
+ });
689
+ var t = document.querySelector('.cp-topbar-title');
690
+ if (t) {
691
+ var map = { overview: 'Thống kê', users: 'Người dùng', content: 'Nội dung', products: 'Sản phẩm', orders: 'Đơn hàng', customers: 'Khách hàng', reports: 'Báo cáo', updates: 'Cập nhật' };
692
+ t.childNodes[0].textContent = map[name] || 'Thống kê';
693
+ }
694
+ if (name === 'overview') renderOverview();
695
+ if (name === 'products') loadShopProducts();
696
+ if (name === 'orders') loadShopOrders();
697
+ if (name === 'customers') loadShopCustomers();
698
+ if (name === 'reports') renderReports();
699
+ if (name === 'updates') renderUpdates();
700
+ }
701
+
702
+ function fmtTime(iso) {
703
+ if (!iso) return '—';
704
+ try {
705
+ var d = new Date(iso);
706
+ return d.toLocaleString('vi-VN', { hour12: false });
707
+ } catch (e) { return iso; }
708
+ }
709
+
710
+ function esc(s) {
711
+ return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
712
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
713
+ });
714
+ }
715
+ function renderUsers(users) {
716
+ var body = document.getElementById('usersBody');
717
+ body.innerHTML = users.map(function (u) {
718
+ var isSelf = u.id === (AuthSystem.getUser() || {}).id;
719
+ var badge = u.role === 'admin' ? '<span class="badge badge-admin">ADMIN</span>' : '<span class="badge badge-user">USER</span>';
720
+ var del = u.role === 'admin'
721
+ ? '<button class="btn-del" disabled title="Không thể xóa Admin">—</button>'
722
+ : '<button class="btn-del" data-id="' + u.id + '">Xóa</button>';
723
+ return '<tr>' +
724
+ '<td>' + u.id + '</td>' +
725
+ '<td>' + esc(u.username) + (isSelf ? ' <span style="color:#7ee787;font-size:11px">(bạn)</span>' : '') + '</td>' +
726
+ '<td>' + esc(u.email) + '</td>' +
727
+ '<td>' + badge + '</td>' +
728
+ '<td>' + (u.login_count || 0) + '</td>' +
729
+ '<td>' + fmtTime(u.last_login_at) + '</td>' +
730
+ '<td>' + fmtTime(u.created_at) + '</td>' +
731
+ '<td>' + del + '</td>' +
732
+ '</tr>';
733
+ }).join('');
734
+ body.querySelectorAll('.btn-del[data-id]').forEach(function (btn) {
735
+ btn.addEventListener('click', function () {
736
+ var id = btn.getAttribute('data-id');
737
+ var row = btn.closest('tr');
738
+ var name = row ? row.cells[1].textContent.trim() : '';
739
+ confirmDelete(id, name);
740
+ });
741
+ });
742
+ }
743
+
744
+ function confirmDelete(id, name) {
745
+ var u = AuthSystem.getUser();
746
+ if (u && String(u.id) === String(id)) { return; }
747
+ document.getElementById('confirmTarget').textContent = name || 'ID ' + id;
748
+ confirmOverlay.classList.add('active');
749
+ confirmOk.onclick = function () {
750
+ confirmOverlay.classList.remove('active');
751
+ deleteUser(id);
752
+ };
753
+ }
754
+
755
+ function deleteUser(id) {
756
+ AuthSystem.adminDeleteUser(id).then(function () {
757
+ loadUsers();
758
+ }).catch(function (e) {
759
+ if (!handleAuthFail(e)) document.getElementById('usersMsg').textContent = 'Lỗi: ' + e.message;
760
+ });
761
+ }
762
+
763
+ function handleAuthFail(e) {
764
+ console.log('[control-panel] FAIL status=', e && e.status, 'msg=', e && e.message, 'data=', e && e.data, 'base=', AuthSystem.baseUrl);
765
+ if (e && e.status === 401) {
766
+ AuthSystem.clearSession();
767
+ return false;
768
+ }
769
+ return false;
770
+ }
771
+
772
+ function loadUsers() {
773
+ var msg = document.getElementById('usersMsg');
774
+ msg.textContent = 'Đang tải...';
775
+ AuthSystem.adminUsers().then(function (data) {
776
+ msg.textContent = 'Tổng cộng ' + data.users.length + ' tài khoản.';
777
+ renderUsers(data.users);
778
+ }).catch(function (e) {
779
+ msg.textContent = 'Lỗi tải danh sách: ' + e.message +
780
+ ' (base=' + AuthSystem.baseUrl + ', token=' + (AuthSystem.getToken() ? 'có' : 'KHÔNG') + ')';
781
+ handleAuthFail(e);
782
+ });
783
+ }
784
+
785
+ function loadContent() {
786
+ var CATS = [
787
+ { key: 'anime', label: 'Anime' },
788
+ { key: 'manga', label: 'Manga' },
789
+ { key: 'manhwa', label: 'Manhwa' },
790
+ { key: 'manhua', label: 'Manhua' },
791
+ { key: 'light-novel', label: 'Light Novel' },
792
+ { key: 'gallery', label: 'Galleries' }
793
+ ];
794
+ var byCat = {};
795
+ CATS.forEach(function (c) { byCat[c.key] = []; });
796
+ var animeErr = null;
797
+ var mangaErr = null;
798
+ var metaErr = null;
799
+ var metaById = {};
800
+
801
+ function push(cat, name, nsfw, status, slug, animePages, id) {
802
+ byCat[cat].push({
803
+ id: id || '',
804
+ name: name || '(không tên)',
805
+ nsfw: !!nsfw,
806
+ status: status || '',
807
+ slug: slug || '',
808
+ animePages: animePages || []
809
+ });
810
+ }
811
+
812
+ var p1 = fetch('data/manga_map.json').then(function (r) {
813
+ if (!r.ok) throw new Error('HTTP ' + r.status);
814
+ return r.json();
815
+ })
816
+ .then(function (arr) {
817
+ (Array.isArray(arr) ? arr : []).forEach(function (a) {
818
+ push('anime', a.titleVN || a.titleEN || a.slug, a.nsfw, a.status, a.slug, a.animePages);
819
+ });
820
+ })
821
+ .catch(function (e) { animeErr = e; });
822
+ var p2 = fetch('data/manga-sources.json').then(function (r) {
823
+ if (!r.ok) throw new Error('HTTP ' + r.status);
824
+ return r.json();
825
+ })
826
+ .then(function (arr) {
827
+ (Array.isArray(arr) ? arr : []).forEach(function (m) {
828
+ var cat = m.type === 'gallery' ? 'gallery' : 'manga';
829
+ push(cat, m.titleVN || m.titleEN || m.slug, m.nsfw, m.status, m.slug, [], m.id);
830
+ });
831
+ })
832
+ .catch(function (e) { mangaErr = e; });
833
+ var p3 = fetch('data/manga-metadata.json').then(function (r) {
834
+ if (!r.ok) throw new Error('HTTP ' + r.status);
835
+ return r.json();
836
+ })
837
+ .then(function (arr) {
838
+ (Array.isArray(arr) ? arr : []).forEach(function (e) {
839
+ if (e && e.metadata && e.metadata.status) metaById[e.id] = e.metadata.status;
840
+ });
841
+ })
842
+ .catch(function (e) { metaErr = e; });
843
+
844
+ Promise.all([p1, p2, p3]).then(function () {
845
+ Object.keys(byCat).forEach(function (cat) {
846
+ byCat[cat].forEach(function (it) {
847
+ if (!it.status && metaById[it.id]) it.status = metaById[it.id];
848
+ });
849
+ });
850
+ var msg = document.getElementById('contentMsg');
851
+ if (animeErr || mangaErr || metaErr) {
852
+ msg.textContent = 'Lỗi tải nội dung: ' +
853
+ (animeErr ? ('manga_map.json → ' + animeErr.message) : '') +
854
+ (animeErr && (mangaErr || metaErr) ? ' | ' : '') +
855
+ (mangaErr ? ('manga-sources.json → ' + mangaErr.message) : '') +
856
+ (mangaErr && metaErr ? ' | ' : '') +
857
+ (metaErr ? ('manga-metadata.json → ' + metaErr.message) : '') +
858
+ '. Nếu mở file trực tiếp (file://), hãy dùng Live Server.';
859
+ msg.style.color = '#f85149';
860
+ } else {
861
+ msg.textContent = '';
862
+ msg.style.color = '';
863
+ }
864
+
865
+ var statsEl = document.getElementById('contentStats');
866
+ if (statsEl) {
867
+ statsEl.innerHTML = CATS.map(function (c) {
868
+ return '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#a3419f;"></span> ' + c.label + '</span><span class="stat-value">' + byCat[c.key].length + '</span></div>';
869
+ }).join('');
870
+ }
871
+
872
+ var tabsWrap = document.getElementById('catTabs');
873
+ tabsWrap.innerHTML = CATS.map(function (c, i) {
874
+ return '<button class="cat-tab' + (i === 0 ? ' active' : '') + '" data-cat="' + c.key + '">' + c.label + '</button>';
875
+ }).join('');
876
+
877
+ var body = document.getElementById('contentBody');
878
+
879
+ function itemLink(cat, item) {
880
+ if (cat === 'anime') {
881
+ var p = item.animePages && item.animePages[0];
882
+ if (p) return (p.indexOf('nsfw/') === 0 ? '' : 'sfw/') + p;
883
+ return (item.nsfw ? 'nsfw/' : 'sfw/') + 'anime/' + item.slug + '/';
884
+ }
885
+ if (cat === 'gallery') return 'sfw/galleries/' + item.slug + '/index.html';
886
+ return 'sfw/manga/' + item.slug + '/index.html';
887
+ }
888
+
889
+ function renderTable() {
890
+ var STATUS_VN = { ongoing: 'Đang ra', completed: 'Hoàn thành', hiatus: 'Tạm ngưng', cancelled: 'Bị hủy' };
891
+ var items = byCat[currentCat];
892
+ body.innerHTML = items.length ? items.map(function (it, i) {
893
+ var tag = it.nsfw
894
+ ? '<span class="nsfw-tag">NSFW</span>'
895
+ : '<span class="sfw-tag">SFW</span>';
896
+ var status = it.status ? (STATUS_VN[it.status] || it.status) : '—';
897
+ return '<tr>' +
898
+ '<td>' + (i + 1) + '</td>' +
899
+ '<td>' + esc(it.name) + '</td>' +
900
+ '<td>' + tag + '</td>' +
901
+ '<td>' + esc(status) + '</td>' +
902
+ '<td style="width:70px;"><a class="btn-go" href="' + esc(itemLink(currentCat, it)) + '" target="_blank">Xem</a></td>' +
903
+ '</tr>';
904
+ }).join('') : '<tr><td colspan="5" class="msg">Chưa có dữ liệu</td></tr>';
905
+ }
906
+
907
+ tabsWrap.addEventListener('click', function (e) {
908
+ var btn = e.target.closest('.cat-tab');
909
+ if (!btn) return;
910
+ tabsWrap.querySelectorAll('.cat-tab').forEach(function (t) { t.classList.remove('active'); });
911
+ btn.classList.add('active');
912
+ currentCat = btn.getAttribute('data-cat');
913
+ renderTable();
914
+ });
915
+
916
+ renderTable();
917
+ });
918
+ }
919
+ // ---------- Shop: Sản phẩm ----------
920
+ var cpCurrentCat = 'all';
921
+ var CATS = [];
922
+
923
+ function cpCardHTML(p) {
924
+ var stockCls = p.stock <= 3 ? 'low' : 'ok';
925
+ var stockTxt = p.stock <= 3 ? 'Sắp hết (' + p.stock + ')' : 'Còn ' + p.stock;
926
+ var tag = '<span class="product-tag">' + p.cat + '</span>';
927
+ var sale = p.old ? ' <span class="price-old">' + fmt(p.old) + '</span>' : '';
928
+ return '<div class="product-card" data-id="' + p.id + '">' +
929
+ '<div class="product-thumb">' + tag +
930
+ '<img src="' + p.img + '" alt="' + p.name + '" loading="lazy">' +
931
+ '</div>' +
932
+ '<div class="product-body">' +
933
+ '<div class="product-anime">' + p.anime + '</div>' +
934
+ '<div class="product-name">' + p.name + '</div>' +
935
+ '<div class="product-price"><span class="price-now">' + fmt(p.price) + '</span>' + sale + '</div>' +
936
+ '<div class="product-stock ' + stockCls + '" id="cpStock-' + p.id + '">' + stockTxt + '</div>' +
937
+ '<div class="product-stock" style="display:flex;gap:8px;align-items:center;margin-top:2px;">' +
938
+ '<button class="cp-btn" data-stock="' + p.id + '" data-delta="-1" style="padding:3px 10px;font-size:12px;">-1</button>' +
939
+ '<button class="cp-btn" data-stock="' + p.id + '" data-delta="1" style="padding:3px 10px;font-size:12px;">+1</button>' +
940
+ '<button class="cp-btn" data-stock="' + p.id + '" data-delta="5" style="padding:3px 10px;font-size:12px;">+5</button>' +
941
+ '</div>' +
942
+ '</div></div>';
943
+ }
944
+
945
+ function renderCpProducts(filterCat, query) {
946
+ filterCat = filterCat || 'all';
947
+ query = (query || '').toLowerCase();
948
+ var list = PRODUCTS.filter(function (p) {
949
+ return (filterCat === 'all' || p.cat === filterCat) &&
950
+ (!query || p.name.toLowerCase().indexOf(query) !== -1 || p.anime.toLowerCase().indexOf(query) !== -1);
951
+ });
952
+ $('#cpProductGrid').innerHTML = list.map(cpCardHTML).join('');
953
+ $('#cpEmptyProducts').style.display = list.length ? 'none' : 'block';
954
+ }
955
+
956
+ function loadShopProducts() {
957
+ var info = $('#cpProductsInfo');
958
+ if (info) info.textContent = 'Đang tải...';
959
+ api('/api/shop/products').then(function (d) {
960
+ PRODUCTS = Array.isArray(d.products) ? d.products : [];
961
+ var seen = [];
962
+ PRODUCTS.forEach(function (p) { if (seen.indexOf(p.cat) === -1) seen.push(p.cat); });
963
+ CATS = seen;
964
+ if (info) info.textContent = PRODUCTS.length + ' sản phẩm';
965
+ var wrap = $('#cpCatChips');
966
+ wrap.innerHTML = '<button class="chip active" data-cat="all">Tất cả <span style="opacity:.7">(' + PRODUCTS.length + ')</span></button>';
967
+ CATS.forEach(function (c) {
968
+ var n = PRODUCTS.filter(function (p) { return p.cat === c; }).length;
969
+ var b = document.createElement('button');
970
+ b.className = 'chip';
971
+ b.dataset.cat = c;
972
+ b.innerHTML = c + " <span style='opacity:.7'>(" + n + ")</span>";
973
+ wrap.appendChild(b);
974
+ });
975
+ renderCpProducts('all', '');
976
+ }).catch(function (e) {
977
+ $('#cpEmptyProducts').style.display = 'block';
978
+ $('#cpEmptyProducts').innerHTML = '<div class="big">&#128517;</div>Lỗi tải sản phẩm: ' + esc(e.message);
979
+ });
980
+ }
981
+
982
+ function setStock(id, delta) {
983
+ var btn = $('#cpProductGrid').querySelector('[data-stock="' + id + '"][data-delta="' + delta + '"]');
984
+ if (btn) { btn.disabled = true; }
985
+ api('/api/shop/products/' + id, {
986
+ method: 'PATCH',
987
+ headers: { 'Content-Type': 'application/json' },
988
+ body: JSON.stringify({ delta: delta })
989
+ }).then(function (d) {
990
+ var p = PRODUCTS.find(function (x) { return x.id === d.product.id; });
991
+ if (p) { p.stock = d.product.stock; }
992
+ var el = $('#cpStock-' + id);
993
+ if (el) {
994
+ var cls = d.product.stock <= 3 ? 'low' : 'ok';
995
+ var txt = d.product.stock <= 3 ? 'Sắp hết (' + d.product.stock + ')' : 'Còn ' + d.product.stock;
996
+ el.className = 'product-stock ' + cls;
997
+ el.textContent = txt;
998
+ }
999
+ }).catch(function (e) {
1000
+ alert('Lỗi cập nhật stock: ' + e.message);
1001
+ }).finally(function () {
1002
+ if (btn) { btn.disabled = false; }
1003
+ });
1004
+ }
1005
+
1006
+ $('#cpProductGrid').addEventListener('click', function (e) {
1007
+ var b = e.target.closest('[data-stock]');
1008
+ if (!b) return;
1009
+ setStock(Number(b.dataset.stock), Number(b.dataset.delta));
1010
+ });
1011
+
1012
+ // ---------- Shop: Khách hàng ----------
1013
+ function renderCustomers() {
1014
+ var bought = shopCustomers.length ? shopCustomers.filter(function (c) { return (c.orders_count || 0) > 0; }).length : 0;
1015
+ var totalSpent = shopCustomers.reduce(function (s, c) { return s + (c.total_spent || 0); }, 0);
1016
+ $('#customerStats').innerHTML =
1017
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#2abdbb;"></span> Tổng khách hàng (user)</span><span class="stat-value">' + (shopCustomers.length || 0) + '</span></div>' +
1018
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#ff5e00;"></span> Đã mua hàng</span><span class="stat-value">' + bought + '</span></div>' +
1019
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#a3419f;"></span> Tổng chi tiêu</span><span class="stat-value">' + fmt(totalSpent) + '</span></div>';
1020
+
1021
+ if (!shopCustomers.length) {
1022
+ $('#customersBody').innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--cp-muted);padding:24px;">Chưa có dữ liệu khách hàng từ máy chủ shop-system. Bấm nút đồng bộ phía dưới.</td></tr>';
1023
+ return;
1024
+ }
1025
+ $('#customersBody').innerHTML = shopCustomers.map(function (c, i) {
1026
+ var name = esc(c.name || 'Khách');
1027
+ var badge = c.role === 'admin' ? '<span class="badge badge-admin">ADMIN</span>' : '<span class="badge badge-user">USER</span>';
1028
+ var spent = (c.total_spent || 0) > 0 ? fmt(c.total_spent) : '<span style="color:var(--cp-muted);">N/A</span>';
1029
+ var items = c.items_count || 0;
1030
+ return '<tr>' +
1031
+ '<td>' + c.id + '</td>' +
1032
+ '<td>' + name + badge + (c.email ? '<div style="font-size:11px;color:var(--cp-muted);">' + esc(c.email) + '</div>' : '') + '</td>' +
1033
+ '<td>' + (c.orders_count || 0) + '</td>' +
1034
+ '<td>' + items + '</td>' +
1035
+ '<td>' + spent + '</td>' +
1036
+ '<td>&#11088; ' + Number(c.points || 0).toLocaleString('vi-VN') + '</td>' +
1037
+ '<td>' + fmtTime(c.created_at) + '</td>' +
1038
+ '</tr>';
1039
+ }).join('');
1040
+ }
1041
+
1042
+ function loadShopCustomers() {
1043
+ if (!AuthSystem.getToken()) return;
1044
+ api('/api/shop/customers').then(function (d) {
1045
+ shopCustomers = Array.isArray(d.customers) ? d.customers : [];
1046
+ renderCustomers();
1047
+ }).catch(function () { renderCustomers(); });
1048
+ }
1049
+
1050
+ $('#refreshCustomers').addEventListener('click', function () {
1051
+ var btn = this;
1052
+ btn.innerHTML = '&#8987; Đang đồng bộ…';
1053
+ loadShopCustomers();
1054
+ setTimeout(function () { btn.innerHTML = '&#128260; Làm mới'; }, 1200);
1055
+ });
1056
+
1057
+ // ---------- Shop: Đơn hàng ----------
1058
+ function renderShopOrders() {
1059
+ $('#ordersInfo').textContent = shopOrders.length ? 'Tổng cộng ' + shopOrders.length + ' đơn hàng.' : 'Chưa có đơn hàng nào.';
1060
+ if (!shopOrders.length) {
1061
+ $('#ordersBody').innerHTML = '<tr><td colspan="8" style="text-align:center;color:var(--cp-muted);padding:24px;">Chưa có đơn hàng.</td></tr>';
1062
+ return;
1063
+ }
1064
+ $('#ordersBody').innerHTML = shopOrders.map(function (o) {
1065
+ var items = (o.items || []).map(function (it) { return it.name + ' x' + it.q; }).join(', ');
1066
+ var action = '';
1067
+ if (o.status === 'Chờ xác nhận') {
1068
+ action = '<div style="display:flex;gap:6px;flex-wrap:wrap;">' +
1069
+ '<button class="cp-btn cp-btn-sm" data-ship="' + o.id + '" title="Xác nhận và chuyển sang giao hàng">&#128666; Xác nhận</button>' +
1070
+ '<button class="cp-btn cp-btn-sm cp-btn-danger" data-cancel="' + o.id + '" title="Hủy đơn (kèm lí do)">&#10060; Hủy</button>' +
1071
+ '</div>';
1072
+ } else if (o.status === 'Chờ thanh toán') {
1073
+ action = '<button class="cp-btn cp-btn-sm cp-btn-danger" data-cancel="' + o.id + '" title="Hủy đơn (kèm lí do)">&#10060; Hủy</button>';
1074
+ } else if (o.status === 'Đang giao hàng') {
1075
+ action = '<div style="display:flex;gap:6px;flex-wrap:wrap;">' +
1076
+ '<span style="font-size:11px;color:var(--cp-muted);align-self:center;">&#128666; Đang giao&hellip;</span>' +
1077
+ '<button class="cp-btn cp-btn-sm cp-btn-danger" data-cancel="' + o.id + '" title="Hủy đơn (kèm lí do)">&#10060; Hủy</button>' +
1078
+ '</div>';
1079
+ } else if (o.status === 'Đã giao') {
1080
+ action = '<span style="font-size:11px;color:#2abdbb;">&#9989; Đã giao</span>';
1081
+ } else if (o.status === 'Đã hủy') {
1082
+ action = '<span style="font-size:11px;color:#ff8a8a;">&#10060; Đã hủy</span>';
1083
+ }
1084
+ var reasonRow = (o.status === 'Đã hủy' && o.cancel_reason)
1085
+ ? '<div style="font-size:11px;color:#ff8a8a;margin-top:3px;">Lí do: ' + esc(o.cancel_reason) + '</div>'
1086
+ : '';
1087
+ return '<tr>' +
1088
+ '<td>' + o.code + '</td>' +
1089
+ '<td>' + esc(o.customer_name) + (o.customer_email ? '<div style="font-size:11px;color:var(--cp-muted);">' + esc(o.customer_email) + '</div>' : '') + '</td>' +
1090
+ '<td>' + esc(items) + '</td>' +
1091
+ '<td>' + o.item_count + '</td>' +
1092
+ '<td>' + fmt(o.total) + '</td>' +
1093
+ '<td>' + esc(o.status) + reasonRow + '</td>' +
1094
+ '<td>' + fmtTime(o.created_at) + '</td>' +
1095
+ '<td>' + action + '</td>' +
1096
+ '</tr>';
1097
+ }).join('');
1098
+ }
1099
+
1100
+ function loadShopOrders() {
1101
+ if (!AuthSystem.getToken()) return;
1102
+ $('#ordersMsg').textContent = 'Đang tải...';
1103
+ api('/api/shop/orders/all').then(function (d) {
1104
+ shopOrders = Array.isArray(d.orders) ? d.orders : [];
1105
+ renderShopOrders();
1106
+ $('#ordersMsg').textContent = '';
1107
+ }).catch(function (e) {
1108
+ $('#ordersMsg').textContent = 'Lỗi tải đơn hàng: ' + e.message;
1109
+ });
1110
+ }
1111
+
1112
+ $('#refreshOrders').addEventListener('click', loadShopOrders);
1113
+ $('#refreshProducts').addEventListener('click', loadShopProducts);
1114
+ $('#refreshOverview').addEventListener('click', renderOverview);
1115
+ $('#refreshReports').addEventListener('click', function () {
1116
+ api('/api/shop/stats').then(function (d) {
1117
+ cpStats = d.stats;
1118
+ renderReports();
1119
+ }).catch(function (e) {
1120
+ $('#reportStats').innerHTML = '<div class="msg err">Lỗi tải lại: ' + esc(e.message) + '</div>';
1121
+ });
1122
+ });
1123
+
1124
+ // ---------- Popup xác nhận / hủy đơn ----------
1125
+ function openOrderModal(type, id) {
1126
+ var order = shopOrders.filter(function (o) { return o.id === id; })[0];
1127
+ var label = order ? order.code : ('#' + id);
1128
+ var modal = document.getElementById('orderModal');
1129
+ document.getElementById('orderModalTitle').textContent =
1130
+ type === 'ship' ? 'Xác nhận đơn ' + label : 'Hủy đơn ' + label;
1131
+ document.getElementById('orderModalText').textContent =
1132
+ type === 'ship'
1133
+ ? 'Xác nhận đơn ' + label + ' đã thanh toán và chuyển sang giao hàng?'
1134
+ : 'Đơn ' + label + ' sẽ bị hủy và trả lại số lượng sản phẩm vào kho. Nhập lí do hủy:';
1135
+ document.getElementById('orderModalOk').innerHTML =
1136
+ type === 'ship' ? '&#128666; Xác nhận' : '&#10060; Xác nhận hủy';
1137
+ document.getElementById('orderModalOk').className =
1138
+ 'confirm-btn confirm-ok' + (type === 'ship' ? ' ok-ship' : '');
1139
+ document.getElementById('orderModalReason').style.display =
1140
+ type === 'ship' ? 'none' : 'block';
1141
+ document.getElementById('orderModalReason').value = '';
1142
+ document.getElementById('orderModalReasonErr').classList.remove('show');
1143
+ modal.classList.add('active');
1144
+ modal.dataset.type = type;
1145
+ modal.dataset.id = id;
1146
+ setTimeout(function () {
1147
+ if (type === 'cancel') document.getElementById('orderModalReason').focus();
1148
+ }, 120);
1149
+ }
1150
+ function closeOrderModal() {
1151
+ document.getElementById('orderModal').classList.remove('active');
1152
+ }
1153
+
1154
+ document.getElementById('orderModalCancel').addEventListener('click', closeOrderModal);
1155
+ document.getElementById('orderModalReason').addEventListener('input', function () {
1156
+ if (this.value.trim()) document.getElementById('orderModalReasonErr').classList.remove('show');
1157
+ });
1158
+ document.getElementById('orderModal').addEventListener('click', function (e) {
1159
+ if (e.target === this) closeOrderModal();
1160
+ });
1161
+ document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeOrderModal(); });
1162
+ document.getElementById('orderModalOk').addEventListener('click', function () {
1163
+ var modal = document.getElementById('orderModal');
1164
+ var type = modal.dataset.type;
1165
+ var id = Number(modal.dataset.id);
1166
+ var order = shopOrders.filter(function (o) { return o.id === id; })[0];
1167
+ var label = order ? order.code : ('#' + id);
1168
+ var btn = this;
1169
+ btn.disabled = true;
1170
+ var url = type === 'ship'
1171
+ ? '/api/shop/orders/' + id + '/ship'
1172
+ : '/api/shop/orders/' + id + '/admin-cancel';
1173
+ var init = { method: 'PATCH' };
1174
+ if (type === 'cancel') {
1175
+ var reason = document.getElementById('orderModalReason').value.trim();
1176
+ if (!reason) {
1177
+ document.getElementById('orderModalReasonErr').classList.add('show');
1178
+ document.getElementById('orderModalReason').focus();
1179
+ btn.disabled = false;
1180
+ return;
1181
+ }
1182
+ init.headers = { 'Content-Type': 'application/json' };
1183
+ init.body = JSON.stringify({ reason: reason });
1184
+ }
1185
+ api(url, init).then(function (d) {
1186
+ closeOrderModal();
1187
+ $('#ordersMsg').textContent = type === 'ship'
1188
+ ? 'Đã xác nhận ' + label + ' → Đang giao hàng.'
1189
+ : 'Đã hủy ' + label + '.';
1190
+ setTimeout(function () { $('#ordersMsg').textContent = ''; }, 3000);
1191
+ loadShopOrders();
1192
+ }).catch(function (e2) {
1193
+ $('#ordersMsg').textContent = 'Lỗi: ' + e2.message;
1194
+ }).finally(function () { btn.disabled = false; });
1195
+ });
1196
+
1197
+ $('#ordersBody').addEventListener('click', function (e) {
1198
+ var ship = e.target.closest('[data-ship]');
1199
+ var cancel = e.target.closest('[data-cancel]');
1200
+ if (ship) openOrderModal('ship', Number(ship.dataset.ship));
1201
+ if (cancel) openOrderModal('cancel', Number(cancel.dataset.cancel));
1202
+ });
1203
+
1204
+ // ---------- Shop: Cập nhật ----------
1205
+ function renderUpdates() {}
1206
+
1207
+ // ---------- Shop: Báo cáo ----------
1208
+ function renderReports() {
1209
+ if (!cpStats) { $('#reportStats').innerHTML = '<div class="msg">Đang tải... (bấm "Thống kê" để tải lại)</div>'; return; }
1210
+ var avg = cpStats.orders > 0 ? Math.round(cpStats.revenue / cpStats.orders) : 0;
1211
+ $('#reportStats').innerHTML =
1212
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#ff5e00;"></span> Doanh thu (tổng)</span><span class="stat-value">' + fmt(cpStats.revenue) + '</span></div>' +
1213
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#2abdbb;"></span> Đơn hàng</span><span class="stat-value">' + cpStats.orders + '</span></div>' +
1214
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#f5c518;"></span> Món đã bán</span><span class="stat-value">' + cpStats.items_sold + '</span></div>' +
1215
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#ff8a8a;"></span> Giá trị đơn TB</span><span class="stat-value">' + avg + '<small>đ</small></span></div>';
1216
+ api('/api/shop/reports').then(function (d) {
1217
+ drawRevenueChart(d.series || []);
1218
+ }).catch(function () {
1219
+ var cv = $('#revenueChart');
1220
+ if (cv) { cv.style.display = 'none'; }
1221
+ });
1222
+ }
1223
+
1224
+ function drawRevenueChart(series) {
1225
+ var cv = $('#revenueChart');
1226
+ if (!cv || !series.length) return;
1227
+ cv.style.display = 'block';
1228
+ var ctx = cv.getContext('2d');
1229
+ var W = cv.width, H = cv.height;
1230
+ ctx.clearRect(0, 0, W, H);
1231
+ ctx.fillStyle = '#0d0f14';
1232
+ ctx.fillRect(0, 0, W, H);
1233
+
1234
+ var max = 1;
1235
+ series.forEach(function (s) { if (s.revenue > max) max = s.revenue; });
1236
+ var padL = 64, padR = 12, padT = 18, padB = 34;
1237
+ var bw = (W - padL - padR) / series.length;
1238
+ var barW = Math.min(44, bw * 0.6);
1239
+ ctx.font = '11px Segoe UI, sans-serif';
1240
+ series.forEach(function (s, i) {
1241
+ var x = padL + i * bw + (bw - barW) / 2;
1242
+ var h = max > 0 ? (s.revenue / max) * (H - padT - padB) : 2;
1243
+ var y = H - padB - h;
1244
+ ctx.fillStyle = s.revenue > 0 ? 'rgba(255,94,0,.85)' : 'rgba(255,255,255,.08)';
1245
+ ctx.beginPath();
1246
+ if (ctx.roundRect) ctx.roundRect(x, y, barW, h, 5); else ctx.rect(x, y, barW, h);
1247
+ ctx.fill();
1248
+ if (s.revenue > 0) {
1249
+ ctx.fillStyle = '#fff';
1250
+ ctx.textAlign = 'center';
1251
+ ctx.fillText((s.revenue / 1000).toFixed(0) + 'K', x + barW / 2, y - 5);
1252
+ }
1253
+ ctx.fillStyle = '#8b90a5';
1254
+ ctx.fillText(s.day.slice(5), x + barW / 2, H - 12);
1255
+ });
1256
+ ctx.strokeStyle = 'rgba(255,255,255,.08)';
1257
+ ctx.beginPath();
1258
+ ctx.moveTo(padL - 6, padT);
1259
+ ctx.lineTo(padL - 6, H - padB);
1260
+ ctx.stroke();
1261
+ }
1262
+
1263
+ // ---------- Overview ----------
1264
+ function renderOverview() {
1265
+ $('#overviewStats').innerHTML = '<div class="msg">Đang tải thống kê...</div>';
1266
+ if (!AuthSystem.getToken()) { $('#overviewStats').innerHTML = '<div class="msg">Cần đăng nhập để xem thống kê.</div>'; return; }
1267
+ api('/api/shop/stats').then(function (d) {
1268
+ cpStats = d.stats;
1269
+ var s = d.stats;
1270
+ var lowCls = s.low_stock > 0 ? 'down' : 'up';
1271
+ var lowTxt = s.low_stock > 0 ? '▼ ' + s.low_stock + ' sắp hết' : '▲ đủ hàng';
1272
+ $('#overviewInfo').textContent = 'Cập nhật: ' + new Date().toLocaleTimeString('vi-VN');
1273
+ $('#overviewStats').innerHTML =
1274
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#ff5e00;"></span> Doanh thu (tổng)</span><span class="stat-value">' + fmt(s.revenue) + '</span></div>' +
1275
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#2abdbb;"></span> Đơn hàng</span><span class="stat-value">' + s.orders + '</span></div>' +
1276
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#a3419f;"></span> Sản phẩm trong kho</span><span class="stat-value">' + s.products + '</span><span class="stat-delta ' + lowCls + '">' + lowTxt + '</span></div>' +
1277
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#f5c518;"></span> Khách hàng (user)</span><span class="stat-value">' + s.customers + '</span></div>' +
1278
+ '<div class="stat-card"><span class="stat-label"><span class="dot" style="background:#f9d423;"></span> Tổng điểm đã tích</span><span class="stat-value">' + Number(s.total_points || 0).toLocaleString('vi-VN') + '</span></div>';
1279
+ }).catch(function (e) {
1280
+ $('#overviewStats').innerHTML = '<div class="msg err">Lỗi tải thống kê: ' + esc(e.message) + '</div>';
1281
+ });
1282
+ }
1283
+
1284
+ // ---------- Boot ----------
1285
+ function boot() {
1286
+ var hasToken = !!AuthSystem.getToken();
1287
+ console.log('[control-panel] boot, hasToken=', hasToken);
1288
+ AuthSystem.me().then(function (user) {
1289
+ console.log('[control-panel] me() ->', user ? user.username + '/' + user.role : null);
1290
+ if (!user) {
1291
+ panel.style.display = 'none';
1292
+ denied.style.display = 'block';
1293
+ var msg = hasToken
1294
+ ? 'Phiên đã hết hạn hoặc không thể kết nối máy chủ. <a href="login.html">Đăng nhập lại</a> hoặc <a href="#" id="retryLink">thử lại</a>.'
1295
+ : 'Bạn chưa đăng nhập. <a href="login.html">Đăng nhập bằng tài khoản Admin</a>.';
1296
+ document.getElementById('deniedMsg').innerHTML = msg;
1297
+ var retry = document.getElementById('retryLink');
1298
+ if (retry) retry.addEventListener('click', function (e) { e.preventDefault(); boot(); });
1299
+ return;
1300
+ }
1301
+ whoEl.textContent = user.username || user.email || '';
1302
+ roleEl.textContent = user.role === 'admin' ? 'Admin' : 'Thành viên';
1303
+ avatarEl.textContent = (user.username || 'A').charAt(0).toUpperCase();
1304
+ if (user.role !== 'admin') {
1305
+ denied.style.display = 'block';
1306
+ return;
1307
+ }
1308
+ panel.style.display = 'block';
1309
+ renderOverview();
1310
+ loadUsers();
1311
+ loadContent();
1312
+ loadShopCustomers();
1313
+ loadShopOrders();
1314
+ });
1315
+ }
1316
+
1317
+ boot();
1318
+ window.addEventListener('pageshow', function (e) {
1319
+ if (e.persisted) boot();
1320
+ });
1321
+
1322
+ document.getElementById('logoutBtn').addEventListener('click', function () {
1323
+ AuthSystem.logout().then(function () { window.location.href = 'login.html'; });
1324
+ });
1325
+
1326
+ // Panel switching
1327
+ $$('.cp-nav-item[data-panel]').forEach(function (a) {
1328
+ a.addEventListener('click', function () {
1329
+ switchPanel(a.dataset.panel);
1330
+ $('#cpSide').classList.remove('open');
1331
+ });
1332
+ });
1333
+ $('#cpBurger').addEventListener('click', function () { $('#cpSide').classList.toggle('open'); });
1334
+
1335
+ // Products filter (chips are built in loadShopProducts; wire up clicks here)
1336
+ (function () {
1337
+ $('#cpCatChips').addEventListener('click', function (e) {
1338
+ var chip = e.target.closest('.chip');
1339
+ if (!chip) return;
1340
+ $('#cpCatChips .chip').forEach(function (c) { c.classList.remove('active'); });
1341
+ chip.classList.add('active');
1342
+ cpCurrentCat = chip.dataset.cat;
1343
+ renderCpProducts(cpCurrentCat, '');
1344
+ });
1345
+ })();
1346
+ </script>
1347
+ </body>
1348
+ </html>
endpoint_API_sercret ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Client ID:"personal-client-ff37a307-28e2-4a7c-9472-d21ae7d95918-fd5e22d2"
2
+ Client Secret:"v41E0aYeKyl8CQiSKqK8014c5URiYM58"
index.html CHANGED
@@ -1,19 +1,1110 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta
7
+ name="description"
8
+ content="ANIVIETDB — Vương Tầm Thế Giới. Một trang web, vô vàn thế giới nội dung anime, manga, light novel."
9
+ />
10
+ <meta name="theme-color" content="#151515" />
11
+ <link
12
+ rel="icon"
13
+ href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%23151515'/><text x='50' y='68' font-size='52' font-family='Arial' font-weight='bold' fill='%23ff5e00' text-anchor='middle'>A</text></svg>"
14
+ />
15
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
17
+ <link
18
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Space+Grotesk:wght@400;500;600;700&display=swap"
19
+ rel="stylesheet"
20
+ />
21
+ <link
22
+ href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
23
+ rel="stylesheet"
24
+ />
25
+ <title>ANIVIETDB · Vươn Tầm Thế Giới</title>
26
+ <style>
27
+ :root {
28
+ --cr-bg: #151515;
29
+ --cr-ink: #ffffff;
30
+ --cr-muted: #8c8c8c;
31
+ --cr-subtle: #bbbbbb;
32
+ --cr-border: #2a2a2a;
33
+ --cr-orange: #ff5e00;
34
+ --cr-orange-hover: #ff7a2a;
35
+ --cr-accent: #2abdbb;
36
+ }
37
+ html,
38
+ body {
39
+ height: 100%;
40
+ overflow-x: hidden;
41
+ }
42
+ body {
43
+ background: var(--cr-bg);
44
+ color: var(--cr-ink);
45
+ font-family: "Inter", system-ui, -apple-system, sans-serif;
46
+ -webkit-font-smoothing: antialiased;
47
+ }
48
+ #bg3d {
49
+ position: fixed;
50
+ inset: 0;
51
+ z-index: 0;
52
+ pointer-events: none;
53
+ }
54
+
55
+ /* ----- Crunchyroll-style hero (stage 1) ----- */
56
+ .cr-hero {
57
+ position: relative;
58
+ z-index: 1;
59
+ min-height: 100vh;
60
+ display: flex;
61
+ flex-direction: column;
62
+ justify-content: center;
63
+ padding: 6.5rem 0 8rem;
64
+ background: var(--cr-bg);
65
+ }
66
+
67
+ .cr-wall {
68
+ position: absolute;
69
+ inset: 0;
70
+ z-index: 0;
71
+ overflow: hidden;
72
+ will-change: transform;
73
+ }
74
+ .cr-grid {
75
+ position: absolute;
76
+ inset: -8px;
77
+ display: grid;
78
+ grid-template-columns: repeat(8, 1fr);
79
+ grid-template-rows: repeat(3, 1fr);
80
+ gap: 2px;
81
+ }
82
+ .cr-cell {
83
+ overflow: hidden;
84
+ }
85
+ .cr-cell img {
86
+ display: block;
87
+ width: 100%;
88
+ height: 100%;
89
+ object-fit: cover;
90
+ filter: brightness(0.42) saturate(0.65);
91
+ opacity: 0.9;
92
+ }
93
+ .cr-wall::after {
94
+ content: "";
95
+ position: absolute;
96
+ inset: 0;
97
+ background:
98
+ linear-gradient(180deg, rgba(21, 21, 21, 0.2), rgba(21, 21, 21, 0.72) 68%, #151515 100%),
99
+ radial-gradient(120% 90% at 50% 0%, rgba(21, 21, 21, 0) 42%, rgba(21, 21, 21, 0.55) 100%);
100
+ }
101
+
102
+ .cr-hero-inner {
103
+ position: relative;
104
+ z-index: 2;
105
+ }
106
+
107
+ .cr-badge {
108
+ display: inline-flex;
109
+ align-items: center;
110
+ gap: 0.5rem;
111
+ border-radius: 999px;
112
+ border: 1px solid rgba(255, 255, 255, 0.16);
113
+ background: rgba(255, 255, 255, 0.05);
114
+ backdrop-filter: blur(8px);
115
+ padding: 0.4rem 0.95rem;
116
+ font-size: 0.75rem;
117
+ font-weight: 500;
118
+ letter-spacing: 0.03em;
119
+ color: var(--cr-subtle);
120
+ }
121
+ .cr-badge .dot {
122
+ width: 6px;
123
+ height: 6px;
124
+ border-radius: 50%;
125
+ background: var(--cr-orange);
126
+ animation: cr-pulse 1.6s ease-in-out infinite;
127
+ }
128
+ @keyframes cr-pulse {
129
+ 0%,
130
+ 100% {
131
+ opacity: 1;
132
+ transform: scale(1);
133
+ }
134
+ 50% {
135
+ opacity: 0.45;
136
+ transform: scale(0.75);
137
+ }
138
+ }
139
+
140
+ .cr-title {
141
+ font-family: "Space Grotesk", "Inter", sans-serif;
142
+ font-weight: 700;
143
+ letter-spacing: -0.025em;
144
+ font-size: clamp(2.7rem, 9vw, 6rem);
145
+ line-height: 1.02;
146
+ color: var(--cr-ink);
147
+ }
148
+ .cr-title .hero-letter {
149
+ display: inline-block;
150
+ will-change: transform;
151
+ }
152
+ .cr-title .grad {
153
+ background: linear-gradient(92deg, var(--cr-orange) 0%, #ff9a5a 45%, #f0abfc 100%);
154
+ -webkit-background-clip: text;
155
+ background-clip: text;
156
+ color: transparent;
157
+ }
158
+
159
+ .cr-sub {
160
+ margin-top: 1.25rem;
161
+ max-width: 42rem;
162
+ font-size: 1.05rem;
163
+ line-height: 1.7;
164
+ color: var(--cr-muted);
165
+ }
166
+
167
+ .cr-cta-row {
168
+ margin-top: 2.25rem;
169
+ display: flex;
170
+ flex-wrap: wrap;
171
+ gap: 0.9rem;
172
+ }
173
+
174
+ .cr-marquee {
175
+ position: absolute;
176
+ inset-inline: 0;
177
+ bottom: 0;
178
+ z-index: 3;
179
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
180
+ background: rgba(21, 21, 21, 0.55);
181
+ backdrop-filter: blur(12px);
182
+ padding: 0.95rem 0;
183
+ overflow: hidden;
184
+ }
185
+ .cr-marquee-track {
186
+ display: flex;
187
+ gap: 2.5rem;
188
+ white-space: nowrap;
189
+ animation: cr-marquee 30s linear infinite;
190
+ will-change: transform;
191
+ }
192
+ .cr-marquee-item {
193
+ display: inline-flex;
194
+ align-items: center;
195
+ gap: 2.5rem;
196
+ font-size: 0.85rem;
197
+ font-weight: 500;
198
+ letter-spacing: 0.04em;
199
+ color: var(--cr-muted);
200
+ }
201
+ .cr-marquee-item .sep {
202
+ width: 6px;
203
+ height: 6px;
204
+ border-radius: 50%;
205
+ background: var(--cr-orange);
206
+ opacity: 0.75;
207
+ }
208
+ @keyframes cr-marquee {
209
+ to {
210
+ transform: translateX(-50%);
211
+ }
212
+ }
213
+
214
+ /* ----- shadcn-style Button (primary / outline) ----- */
215
+ .btn-cr {
216
+ display: inline-flex;
217
+ align-items: center;
218
+ justify-content: center;
219
+ gap: 0.5rem;
220
+ border-radius: 999px;
221
+ font-weight: 600;
222
+ font-size: 0.9375rem;
223
+ line-height: 1.5;
224
+ padding: 0.625rem 1.5rem;
225
+ border: 1px solid transparent;
226
+ cursor: pointer;
227
+ transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease,
228
+ box-shadow 0.2s ease, transform 0.2s ease;
229
+ text-decoration: none;
230
+ white-space: nowrap;
231
+ user-select: none;
232
+ }
233
+ .btn-cr:focus-visible {
234
+ outline: none;
235
+ box-shadow: 0 0 0 2px var(--cr-bg), 0 0 0 4px var(--cr-orange);
236
+ }
237
+ .btn-cr:disabled {
238
+ opacity: 0.5;
239
+ pointer-events: none;
240
+ }
241
+ .btn-cr:hover {
242
+ transform: translateY(-1px);
243
+ }
244
+ .btn-cr-default {
245
+ background: var(--cr-orange);
246
+ color: #000;
247
+ box-shadow: 0 10px 28px rgba(255, 94, 0, 0.32);
248
+ }
249
+ .btn-cr-default:hover {
250
+ background: var(--cr-orange-hover);
251
+ color: #000;
252
+ box-shadow: 0 12px 32px rgba(255, 94, 0, 0.42);
253
+ }
254
+ .btn-cr-outline {
255
+ border-color: rgba(255, 255, 255, 0.32);
256
+ color: var(--cr-ink);
257
+ background: transparent;
258
+ }
259
+ .btn-cr-outline:hover {
260
+ border-color: rgba(255, 255, 255, 0.9);
261
+ background: rgba(255, 255, 255, 0.07);
262
+ color: var(--cr-ink);
263
+ }
264
+ .btn-cr-lg {
265
+ height: 3rem;
266
+ padding-inline: 2.1rem;
267
+ font-size: 1rem;
268
+ border-radius: 999px;
269
+ }
270
+
271
+ /* ----- Feature stage ----- */
272
+ .cr-features {
273
+ position: relative;
274
+ z-index: 1;
275
+ background: var(--cr-bg);
276
+ padding: 5.5rem 0 6rem;
277
+ }
278
+ .cr-features-head {
279
+ text-align: left;
280
+ margin-bottom: 2.5rem;
281
+ }
282
+ .cr-features-head h2 {
283
+ font-family: "Space Grotesk", sans-serif;
284
+ font-weight: 700;
285
+ font-size: clamp(1.8rem, 4vw, 2.8rem);
286
+ letter-spacing: -0.02em;
287
+ color: var(--cr-ink);
288
+ }
289
+ .cr-features-head h2 .grad {
290
+ background: linear-gradient(92deg, var(--cr-orange) 0%, #ff9a5a 60%, #f0abfc 100%);
291
+ -webkit-background-clip: text;
292
+ background-clip: text;
293
+ color: transparent;
294
+ }
295
+ .cr-features-head p {
296
+ margin-top: 0.6rem;
297
+ color: var(--cr-muted);
298
+ font-size: 0.95rem;
299
+ }
300
+ .cr-feature-row {
301
+ display: grid;
302
+ grid-template-columns: repeat(5, minmax(0, 1fr));
303
+ gap: 1.25rem;
304
+ }
305
+ .cr-feature-box {
306
+ position: relative;
307
+ border-radius: 16px;
308
+ overflow: hidden;
309
+ aspect-ratio: 2 / 3;
310
+ background: #1e1e1e;
311
+ border: 1px solid rgba(255, 255, 255, 0.08);
312
+ cursor: pointer;
313
+ text-decoration: none;
314
+ display: flex;
315
+ flex-direction: column;
316
+ justify-content: flex-end;
317
+ will-change: transform;
318
+ transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
319
+ }
320
+ .cr-feature-box:hover {
321
+ transform: translateY(-6px);
322
+ box-shadow: 0 18px 40px rgba(255, 94, 0, 0.2);
323
+ border-color: rgba(255, 94, 0, 0.45);
324
+ }
325
+ .cr-feature-box img {
326
+ position: absolute;
327
+ inset: 0;
328
+ width: 100%;
329
+ height: 100%;
330
+ object-fit: cover;
331
+ transition: transform 0.35s ease;
332
+ }
333
+ .cr-feature-box:hover img {
334
+ transform: scale(1.06);
335
+ }
336
+ .cr-feature-box .shade {
337
+ position: absolute;
338
+ inset: 0;
339
+ background: linear-gradient(to top, rgba(21, 21, 21, 0.95) 8%, rgba(21, 21, 21, 0.25) 55%, transparent);
340
+ }
341
+ .cr-feature-box .info {
342
+ position: relative;
343
+ padding: 1rem 0.9rem;
344
+ color: #fff;
345
+ }
346
+ .cr-feature-box .tag {
347
+ display: inline-block;
348
+ border-radius: 999px;
349
+ background: var(--cr-orange);
350
+ color: #000;
351
+ font-size: 0.6rem;
352
+ font-weight: 700;
353
+ letter-spacing: 0.04em;
354
+ padding: 0.18rem 0.55rem;
355
+ margin-bottom: 0.4rem;
356
+ }
357
+ .cr-feature-box .t {
358
+ font-family: "Space Grotesk", sans-serif;
359
+ font-weight: 600;
360
+ font-size: 1rem;
361
+ line-height: 1.25;
362
+ margin: 0;
363
+ }
364
+ .cr-feature-box .s {
365
+ color: rgba(255, 255, 255, 0.7);
366
+ font-size: 0.72rem;
367
+ margin-top: 0.2rem;
368
+ }
369
+ .cr-feature-box.fade-in {
370
+ opacity: 0;
371
+ transform: translateY(28px);
372
+ }
373
+ .cr-feature-box.fade-in.visible {
374
+ opacity: 1;
375
+ transform: translateY(0);
376
+ transition: opacity 0.6s ease, transform 0.6s ease, box-shadow 0.25s ease,
377
+ border-color 0.25s ease;
378
+ }
379
+
380
+ /* ----- Shop stage (stage 3) ----- */
381
+ .cr-shop {
382
+ position: relative;
383
+ z-index: 1;
384
+ background: linear-gradient(180deg, var(--cr-bg) 0%, #171719 100%);
385
+ padding: 5.5rem 0 6rem;
386
+ }
387
+ .cr-shop-head {
388
+ text-align: center;
389
+ margin-bottom: 2.5rem;
390
+ }
391
+ .cr-shop-head h2 {
392
+ font-family: "Space Grotesk", sans-serif;
393
+ font-weight: 700;
394
+ font-size: clamp(1.8rem, 4vw, 2.8rem);
395
+ letter-spacing: -0.02em;
396
+ color: var(--cr-ink);
397
+ }
398
+ .cr-shop-head h2 .grad {
399
+ background: linear-gradient(92deg, var(--cr-orange) 0%, #ff9a5a 60%, #f0abfc 100%);
400
+ -webkit-background-clip: text;
401
+ background-clip: text;
402
+ color: transparent;
403
+ }
404
+ .cr-shop-head p {
405
+ margin-top: 0.6rem;
406
+ color: var(--cr-muted);
407
+ font-size: 0.95rem;
408
+ }
409
+ .cr-shop-row {
410
+ display: grid;
411
+ grid-template-columns: repeat(4, minmax(0, 1fr));
412
+ gap: 1.25rem;
413
+ }
414
+ .cr-shop-card {
415
+ border-radius: 16px;
416
+ overflow: hidden;
417
+ background: #1d1d1f;
418
+ border: 1px solid rgba(255, 255, 255, 0.08);
419
+ text-decoration: none;
420
+ display: flex;
421
+ flex-direction: column;
422
+ transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
423
+ cursor: pointer;
424
+ }
425
+ .cr-shop-card:hover {
426
+ transform: translateY(-6px);
427
+ box-shadow: 0 18px 40px rgba(255, 94, 0, 0.18);
428
+ border-color: rgba(255, 94, 0, 0.45);
429
+ }
430
+ .cr-shop-card .pic {
431
+ position: relative;
432
+ aspect-ratio: 1 / 1;
433
+ background: #141416;
434
+ overflow: hidden;
435
+ }
436
+ .cr-shop-card .pic img {
437
+ width: 100%;
438
+ height: 100%;
439
+ object-fit: cover;
440
+ transition: transform 0.35s ease;
441
+ }
442
+ .cr-shop-card:hover .pic img {
443
+ transform: scale(1.07);
444
+ }
445
+ .cr-shop-card .pic .cat {
446
+ position: absolute;
447
+ top: 0.6rem;
448
+ left: 0.6rem;
449
+ border-radius: 999px;
450
+ background: rgba(21, 21, 21, 0.75);
451
+ color: var(--cr-orange);
452
+ font-size: 0.62rem;
453
+ font-weight: 700;
454
+ letter-spacing: 0.04em;
455
+ padding: 0.2rem 0.6rem;
456
+ backdrop-filter: blur(4px);
457
+ }
458
+ .cr-shop-card .pic .price {
459
+ position: absolute;
460
+ bottom: 0.6rem;
461
+ right: 0.6rem;
462
+ border-radius: 10px;
463
+ background: var(--cr-orange);
464
+ color: #000;
465
+ font-weight: 800;
466
+ font-size: 0.8rem;
467
+ padding: 0.3rem 0.65rem;
468
+ }
469
+ .cr-shop-card .meta {
470
+ padding: 0.9rem 1rem 1.1rem;
471
+ }
472
+ .cr-shop-card .meta .t {
473
+ font-family: "Space Grotesk", sans-serif;
474
+ font-weight: 600;
475
+ font-size: 0.95rem;
476
+ color: #fff;
477
+ margin: 0 0 0.15rem;
478
+ }
479
+ .cr-shop-card .meta .s {
480
+ color: var(--cr-muted);
481
+ font-size: 0.75rem;
482
+ }
483
+ .cr-shop-card.fade-in {
484
+ opacity: 0;
485
+ transform: translateY(28px);
486
+ }
487
+ .cr-shop-card.fade-in.visible {
488
+ opacity: 1;
489
+ transform: translateY(0);
490
+ transition: opacity 0.6s ease, transform 0.6s ease, box-shadow 0.25s ease,
491
+ border-color 0.25s ease;
492
+ }
493
+ .cr-shop-cta {
494
+ margin-top: 2.75rem;
495
+ text-align: center;
496
+ }
497
+
498
+ /* ----- Footer ----- */
499
+ .cr-footer {
500
+ position: relative;
501
+ z-index: 1;
502
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
503
+ background: var(--cr-bg);
504
+ padding: 3.25rem 1.5rem 3rem;
505
+ text-align: center;
506
+ }
507
+ .cr-footer p {
508
+ max-width: 46rem;
509
+ margin-inline: auto;
510
+ color: var(--cr-muted);
511
+ font-size: 0.85rem;
512
+ line-height: 1.7;
513
+ }
514
+ .cr-footer p + p {
515
+ margin-top: 0.35rem;
516
+ color: var(--cr-subtle);
517
+ font-size: 0.8rem;
518
+ }
519
+ .cr-footer .brand {
520
+ font-family: "Space Grotesk", sans-serif;
521
+ font-weight: 700;
522
+ font-size: 1rem;
523
+ letter-spacing: 0.06em;
524
+ color: var(--cr-ink);
525
+ margin-bottom: 0.9rem;
526
+ }
527
+ .cr-footer .brand span {
528
+ color: var(--cr-orange);
529
+ }
530
+
531
+ @media (max-width: 767.98px) {
532
+ .cr-hero {
533
+ padding-top: 5rem;
534
+ }
535
+ .cr-grid {
536
+ grid-template-columns: repeat(4, 1fr);
537
+ grid-template-rows: repeat(6, 1fr);
538
+ }
539
+ .cr-feature-row {
540
+ grid-template-columns: repeat(2, minmax(0, 1fr));
541
+ gap: 0.9rem;
542
+ }
543
+ .cr-shop-row {
544
+ grid-template-columns: repeat(2, minmax(0, 1fr));
545
+ gap: 0.9rem;
546
+ }
547
+ }
548
+ </style>
549
+ </head>
550
+ <body>
551
+ <div id="root"></div>
552
+
553
+ <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
554
+ <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
555
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
556
+ <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
557
+
558
+ <script>
559
+ (function () {
560
+ var React = window.React;
561
+ var ReactDOM = window.ReactDOM;
562
+ var e = React.createElement;
563
+ var useRef = React.useRef;
564
+ var useEffect = React.useEffect;
565
+
566
+ var POSTERS = [
567
+ "https://phim.nguonc.com/public/images/Film/76.jpg",
568
+ "https://phim.nguonc.com/public/images/Film/666.jpg",
569
+ "https://phim.nguonc.com/public/images/Film/77.jpg",
570
+ "https://phim.nguonc.com/public/images/Film/667.jpg",
571
+ "https://phim.nguonc.com/public/images/Film/259.jpg",
572
+ "https://phim.nguonc.com/public/images/Film/2738.jpg",
573
+ "https://phim.nguonc.com/public/images/Film/3485.jpg",
574
+ "https://phim.nguonc.com/public/images/Film/141438.jpg",
575
+ "https://phim.nguonc.com/public/images/Film/date-a-bullet-thumb.jpg",
576
+ "https://phim.nguonc.com/public/images/Film/3103.jpg",
577
+ "https://phim.nguonc.com/public/images/Post/5/aishiteru-game-wo-owarasetai.jpg",
578
+ "https://phim.nguonc.com/public/images/Film/541.jpg",
579
+ "https://phim.nguonc.com/public/images/Film/536.jpg",
580
+ "https://phim.nguonc.com/public/images/Film/56167.jpg",
581
+ "https://phim.nguonc.com/public/images/Film/56643.jpg",
582
+ "https://phim.nguonc.com/public/images/Film/538.jpg",
583
+ "https://phim.nguonc.com/public/images/Film/540.jpg",
584
+ "https://phim.nguonc.com/public/images/Film/60351.jpg",
585
+ "https://phim.nguonc.com/public/images/Film/2607.jpg",
586
+ "https://phim.nguonc.com/public/images/Film/2661.jpg",
587
+ "https://phim.nguonc.com/public/images/Film/5MgMCnslrxQfOfaq6T5ndkLlcCk.jpg",
588
+ "https://phim.nguonc.com/public/images/Film/134808.jpg",
589
+ "https://phim.nguonc.com/public/images/Film/138379.jpg",
590
+ "https://phim.nguonc.com/public/images/Post/9/seishun-buta-yarou-wa-santa-claus-no-yume-wo-minai-1.jpg"
591
+ ];
592
+
593
+ var MARQUEE = [
594
+ "Anime",
595
+ "Manga",
596
+ "Manhwa",
597
+ "Manhua",
598
+ "Light Novel",
599
+ "Sưu tầm",
600
+ "Vô vàn thế giới"
601
+ ];
602
+
603
+ var FEATURES = [
604
+ { name: "Death Note", sub: "37 tập", tag: "Anime", page: "sfw/anime/universal/death-note/", img: "https://phim.nguonc.com/public/images/Film/244.jpg" },
605
+ { name: "Tensei Slime", sub: "24 tập", tag: "Anime", page: "sfw/anime/universal/tensei-slime/phan-1/", img: "https://phim.nguonc.com/public/images/Film/2658.jpg" },
606
+ { name: "JoJo Phần 1", sub: "26 tập", tag: "Anime", page: "sfw/anime/universal/jojo/phan-1/", img: "https://phim.nguonc.com/public/images/Film/40409.jpg" },
607
+ { name: "Fate/Zero", sub: "13 tập", tag: "Anime", page: "sfw/anime/multiversal/fate/fate-zero/phan-1/", img: "https://phim.nguonc.com/public/images/Film/117644.jpg" },
608
+ { name: "Bocchi the Rock!", sub: "12 tập", tag: "Anime", page: "sfw/anime/universal/bocchi-the-rock/phan-1/", img: "https://phim.nguonc.com/public/images/Film/127956.jpg" }
609
+ ];
610
+
611
+ var SHOP_PRODUCTS = [
612
+ { name: "Figure Gojo Satoru", anime: "Jujutsu Kaisen", cat: "Figure", price: "1.290.000đ", img: "https://phim.nguonc.com/public/images/Film/76.jpg" },
613
+ { name: "Figure Luffy Gear 5", anime: "One Piece", cat: "Figure", price: "1.450.000đ", img: "https://phim.nguonc.com/public/images/Film/666.jpg" },
614
+ { name: "Plushie Anya Forger", anime: "Spy x Family", cat: "Plushie", price: "350.000đ", img: "https://phim.nguonc.com/public/images/Film/3485.jpg" },
615
+ { name: "Mug Steins;Gate", anime: "Steins;Gate", cat: "Mug", price: "185.000đ", img: "https://phim.nguonc.com/public/images/Film/536.jpg" }
616
+ ];
617
+
618
+ function cx() {
619
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
620
+ }
621
+
622
+ function Button(props) {
623
+ var className = cx(
624
+ "btn-cr",
625
+ "btn-cr-" + (props.variant || "default"),
626
+ (props.size || "default") === "lg" ? "btn-cr-lg" : "",
627
+ props.className
628
+ );
629
+ var p = {};
630
+ for (var k in props) {
631
+ if (k === "variant" || k === "size" || k === "className" || k === "children") continue;
632
+ p[k] = props[k];
633
+ }
634
+ return e("button", Object.assign({}, p, { className: className }), props.children);
635
+ }
636
+
637
+ function ThreeBackground() {
638
+ var ref = useRef(null);
639
+ useEffect(function () {
640
+ var canvas = ref.current;
641
+ var renderer = new THREE.WebGLRenderer({
642
+ canvas: canvas,
643
+ alpha: true,
644
+ antialias: true
645
+ });
646
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
647
+ var w = window.innerWidth;
648
+ var h = window.innerHeight;
649
+ var scene = new THREE.Scene();
650
+ var camera = new THREE.PerspectiveCamera(70, w / h, 0.1, 40);
651
+ camera.position.z = 1;
652
+
653
+ function onResize() {
654
+ w = window.innerWidth;
655
+ h = window.innerHeight;
656
+ renderer.setSize(w, h);
657
+ camera.aspect = w / h;
658
+ camera.updateProjectionMatrix();
659
+ }
660
+ onResize();
661
+ window.addEventListener("resize", onResize);
662
+
663
+ var COUNT = 1400;
664
+ var positions = new Float32Array(COUNT * 3);
665
+ for (var i = 0; i < COUNT * 3; i++) positions[i] = (Math.random() - 0.5) * 4;
666
+ var geo = new THREE.BufferGeometry();
667
+ geo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
668
+ var mat = new THREE.PointsMaterial({
669
+ color: 0xffffff,
670
+ size: 0.004,
671
+ transparent: true,
672
+ opacity: 0.65,
673
+ sizeAttenuation: true
674
+ });
675
+ var stars = new THREE.Points(geo, mat);
676
+ scene.add(stars);
677
+
678
+ var glow = new THREE.Mesh(
679
+ new THREE.SphereGeometry(0.16, 32, 32),
680
+ new THREE.MeshBasicMaterial({
681
+ color: 0xff5e00,
682
+ transparent: true,
683
+ opacity: 0.16
684
+ })
685
+ );
686
+ glow.position.set(0.9, 0.55, -0.6);
687
+ scene.add(glow);
688
+
689
+ var raf;
690
+ var mx = 0;
691
+ var my = 0;
692
+ function onMouse(e2) {
693
+ mx = (e2.clientX / w - 0.5) * 2;
694
+ my = (e2.clientY / h - 0.5) * 2;
695
+ }
696
+ window.addEventListener("mousemove", onMouse);
697
+
698
+ function tick() {
699
+ stars.rotation.y += 0.00022;
700
+ stars.rotation.x += 0.00008;
701
+ camera.position.x += (mx * 0.05 - camera.position.x) * 0.04;
702
+ camera.position.y += (-my * 0.05 - camera.position.y) * 0.04;
703
+ camera.lookAt(0, 0, 0);
704
+ renderer.render(scene, camera);
705
+ raf = requestAnimationFrame(tick);
706
+ }
707
+ tick();
708
+
709
+ return function () {
710
+ cancelAnimationFrame(raf);
711
+ window.removeEventListener("resize", onResize);
712
+ window.removeEventListener("mousemove", onMouse);
713
+ renderer.dispose();
714
+ };
715
+ }, []);
716
+ return e("canvas", { id: "bg3d", ref: ref });
717
+ }
718
+
719
+ function Hero() {
720
+ var wallRef = useRef(null);
721
+ useEffect(function () {
722
+ var letters = gsap.utils.toArray(".hero-letter");
723
+ var reveals = gsap.utils.toArray(".cr-reveal");
724
+ gsap.set(letters, { yPercent: 115, opacity: 0 });
725
+ gsap.set(reveals, { y: 26, opacity: 0 });
726
+ var tl = gsap.timeline({ defaults: { ease: "power3.out" } });
727
+ tl.to(letters, { yPercent: 0, opacity: 1, stagger: 0.05, duration: 0.85 }, 0.2)
728
+ .to(reveals, { y: 0, opacity: 1, stagger: 0.1, duration: 0.6 }, 0.6)
729
+ .fromTo(
730
+ wallRef.current,
731
+ { scale: 1.22 },
732
+ { scale: 1.02, duration: 2.4, ease: "power2.out" },
733
+ 0
734
+ );
735
+ return function () {
736
+ tl.kill();
737
+ };
738
+ }, []);
739
+
740
+ var brand = "ANIVIETDB".split("");
741
+ var heroLetters = brand.map(function (c, i) {
742
+ return e("span", { className: "hero-letter", key: i }, c);
743
+ });
744
+
745
+ return e(
746
+ "section",
747
+ { className: "cr-hero" },
748
+ e(
749
+ "div",
750
+ { className: "cr-wall", ref: wallRef },
751
+ e(
752
+ "div",
753
+ { className: "cr-grid" },
754
+ POSTERS.map(function (u, i) {
755
+ return e(
756
+ "div",
757
+ { className: "cr-cell", key: i },
758
+ e("img", { src: u, alt: "", loading: "lazy" })
759
+ );
760
+ })
761
+ )
762
+ ),
763
+ e(
764
+ "div",
765
+ { className: "cr-hero-inner container" },
766
+ e(
767
+ "div",
768
+ { className: "row" },
769
+ e(
770
+ "div",
771
+ { className: "col-12 col-lg-9 col-xl-8" },
772
+ e(
773
+ "span",
774
+ { className: "cr-badge cr-reveal" },
775
+ e("span", { className: "dot" }),
776
+ " Giới thiệu · Vươn Tầm Thế Giới"
777
+ ),
778
+ e(
779
+ "h1",
780
+ { className: "cr-title" },
781
+ heroLetters,
782
+ e(
783
+ "span",
784
+ { className: "d-block" },
785
+ e("span", { className: "grad" }, "Vươn Tầm Thế Giới")
786
+ )
787
+ ),
788
+ e(
789
+ "p",
790
+ { className: "cr-sub cr-reveal" },
791
+ "Nơi hội tụ anime, manga, webtoon và light novel — một trang web, vô vàn thế giới."
792
+ ),
793
+ e(
794
+ "div",
795
+ { className: "cr-cta-row cr-reveal" },
796
+ e(
797
+ Button,
798
+ { size: "lg", onClick: function () { openSfwWithLoginCheck(); } },
799
+ "Bắt đầu"
800
+ ),
801
+ e(
802
+ Button,
803
+ {
804
+ size: "lg",
805
+ variant: "outline",
806
+ onClick: function () {
807
+ var el = document.getElementById("featureStage");
808
+ if (el) el.scrollIntoView({ behavior: "smooth" });
809
+ }
810
+ },
811
+ "Xem phim nổi bật"
812
+ )
813
+ )
814
+ )
815
+ )
816
+ ),
817
+ e(
818
+ "div",
819
+ { className: "cr-marquee" },
820
+ e(
821
+ "div",
822
+ { className: "cr-marquee-track" },
823
+ MARQUEE.concat(MARQUEE).map(function (w, i) {
824
+ return e(
825
+ "span",
826
+ { className: "cr-marquee-item", key: i },
827
+ w,
828
+ e("span", { className: "sep" })
829
+ );
830
+ })
831
+ )
832
+ )
833
+ );
834
+ }
835
+
836
+ function FeatureStage() {
837
+ var secRef = useRef(null);
838
+ useEffect(function () {
839
+ var boxes = secRef.current ? secRef.current.querySelectorAll(".cr-feature-box.fade-in") : [];
840
+ if (!("IntersectionObserver" in window)) {
841
+ Array.prototype.forEach.call(boxes, function (b) { b.classList.add("visible"); });
842
+ return;
843
+ }
844
+ var io = new IntersectionObserver(
845
+ function (entries) {
846
+ entries.forEach(function (en) {
847
+ if (en.isIntersecting) {
848
+ en.target.classList.add("visible");
849
+ io.unobserve(en.target);
850
+ }
851
+ });
852
+ },
853
+ { threshold: 0.15 }
854
+ );
855
+ Array.prototype.forEach.call(boxes, function (b) { io.observe(b); });
856
+ return function () { io.disconnect(); };
857
+ }, []);
858
+
859
+ return e(
860
+ "section",
861
+ { id: "featureStage", className: "cr-features", ref: secRef },
862
+ e(
863
+ "div",
864
+ { className: "container" },
865
+ e(
866
+ "div",
867
+ { className: "cr-features-head" },
868
+ e("h2", null, "Phim nổi bật ", e("span", { className: "grad" }, "Feature")),
869
+ e(
870
+ "p",
871
+ null,
872
+ "Những tựa anime đáng xem nhất, cập nhật liên tục từ ANIVIETDB."
873
+ )
874
+ ),
875
+ e(
876
+ "div",
877
+ { className: "cr-feature-row" },
878
+ FEATURES.map(function (f, i) {
879
+ return e(
880
+ "a",
881
+ {
882
+ key: i,
883
+ href: f.page,
884
+ className: "cr-feature-box fade-in",
885
+ style: { transitionDelay: i * 0.08 + "s" }
886
+ },
887
+ e("img", { src: f.img, alt: f.name, loading: "lazy" }),
888
+ e("div", { className: "shade" }),
889
+ e(
890
+ "div",
891
+ { className: "info" },
892
+ e("span", { className: "tag" }, f.tag),
893
+ e("p", { className: "t" }, f.name),
894
+ e("p", { className: "s" }, f.sub)
895
+ )
896
+ );
897
+ })
898
+ )
899
+ )
900
+ );
901
+ }
902
+
903
+ function openWithLoginCheck(url) {
904
+ function go() { window.location.href = url; }
905
+ function showPopup() {
906
+ var m = document.getElementById("loginReqPopup");
907
+ m.classList.add("active");
908
+ }
909
+ if (window.AuthSystem) {
910
+ window.AuthSystem.me().then(function (user) {
911
+ if (user) { go(); } else { showPopup(); }
912
+ }).catch(function () { showPopup(); });
913
+ return;
914
+ }
915
+ var s = document.createElement("script");
916
+ s.src = "signup-loginsystem.js";
917
+ s.onload = function () {
918
+ if (window.AuthSystem) {
919
+ window.AuthSystem.me().then(function (user) {
920
+ if (user) { go(); } else { showPopup(); }
921
+ }).catch(function () { showPopup(); });
922
+ } else {
923
+ showPopup();
924
+ }
925
+ };
926
+ s.onerror = function () { showPopup(); };
927
+ document.head.appendChild(s);
928
+ }
929
+ function openShopWithLoginCheck() { openWithLoginCheck("shop/index.html"); }
930
+ function openSfwWithLoginCheck() {
931
+ function go() { window.location.href = "sfw/index.html"; }
932
+ function goLogin() { window.location.href = "login.html"; }
933
+ function check() {
934
+ window.AuthSystem.me().then(function (user) {
935
+ if (user) { go(); } else { goLogin(); }
936
+ }).catch(function () { goLogin(); });
937
+ }
938
+ if (window.AuthSystem) { check(); return; }
939
+ var s = document.createElement("script");
940
+ s.src = "signup-loginsystem.js";
941
+ s.onload = function () { if (window.AuthSystem) { check(); } else { goLogin(); } };
942
+ s.onerror = function () { goLogin(); };
943
+ document.head.appendChild(s);
944
+ }
945
+
946
+ function ShopStage() {
947
+ var secRef = useRef(null);
948
+ useEffect(function () {
949
+ var cards = secRef.current ? secRef.current.querySelectorAll(".cr-shop-card.fade-in") : [];
950
+ if (!("IntersectionObserver" in window)) {
951
+ Array.prototype.forEach.call(cards, function (b) { b.classList.add("visible"); });
952
+ return;
953
+ }
954
+ var io = new IntersectionObserver(
955
+ function (entries) {
956
+ entries.forEach(function (en) {
957
+ if (en.isIntersecting) {
958
+ en.target.classList.add("visible");
959
+ io.unobserve(en.target);
960
+ }
961
+ });
962
+ },
963
+ { threshold: 0.15 }
964
+ );
965
+ Array.prototype.forEach.call(cards, function (b) { io.observe(b); });
966
+ return function () { io.disconnect(); };
967
+ }, []);
968
+
969
+ return e(
970
+ "section",
971
+ { id: "shopStage", className: "cr-shop", ref: secRef },
972
+ e(
973
+ "div",
974
+ { className: "container" },
975
+ e(
976
+ "div",
977
+ { className: "cr-shop-head" },
978
+ e("h2", null, "Figure Shop ", e("span", { className: "grad" }, "ANIVIETDB")),
979
+ e(
980
+ "p",
981
+ null,
982
+ "Figure, plushie, manga, Blu-ray và đồ lưu niệm sưu tầm — khám phá ngay, đặt mua tại cửa hàng."
983
+ )
984
+ ),
985
+ e(
986
+ "div",
987
+ { className: "cr-shop-row" },
988
+ SHOP_PRODUCTS.map(function (f, i) {
989
+ return e(
990
+ "a",
991
+ {
992
+ key: i,
993
+ href: "shop/index.html",
994
+ className: "cr-shop-card fade-in",
995
+ style: { transitionDelay: i * 0.08 + "s" }
996
+ },
997
+ e(
998
+ "div",
999
+ { className: "pic" },
1000
+ e("span", { className: "cat" }, f.cat),
1001
+ e("img", { src: f.img, alt: f.name, loading: "lazy" }),
1002
+ e("span", { className: "price" }, f.price)
1003
+ ),
1004
+ e(
1005
+ "div",
1006
+ { className: "meta" },
1007
+ e("p", { className: "t" }, f.name),
1008
+ e("p", { className: "s" }, f.anime)
1009
+ )
1010
+ );
1011
+ })
1012
+ ),
1013
+ e(
1014
+ "div",
1015
+ { className: "cr-shop-cta" },
1016
+ e(
1017
+ Button,
1018
+ {
1019
+ size: "lg",
1020
+ onClick: function () { openShopWithLoginCheck(); }
1021
+ },
1022
+ "Khám phá shop"
1023
+ )
1024
+ )
1025
+ )
1026
+ );
1027
+ }
1028
+
1029
+ function Footer() {
1030
+ return e(
1031
+ "footer",
1032
+ { className: "cr-footer" },
1033
+ e(
1034
+ "p",
1035
+ { className: "brand" },
1036
+ "ANIVIETDB ",
1037
+ e("span", null, "·"),
1038
+ " Vươn Tầm Thế Giới"
1039
+ ),
1040
+ e(
1041
+ "p",
1042
+ null,
1043
+ "© 2026 ANIVIETDB. Tất cả nội dung Anime, Manga, Manhwa, Manhua và Light Novel được sưu tầm và thuộc bản quyền của chủ sở hữu tương ứng."
1044
+ ),
1045
+ e(
1046
+ "p",
1047
+ null,
1048
+ "Trang web sưu tầm cá nhân - Phi thương mại. Mọi quyền được bảo lưu."
1049
+ )
1050
+ );
1051
+ }
1052
+
1053
+ function App() {
1054
+ return e(
1055
+ "main",
1056
+ { className: "position-relative" },
1057
+ e(ThreeBackground),
1058
+ e(Hero),
1059
+ e(FeatureStage),
1060
+ e(ShopStage),
1061
+ e(Footer)
1062
+ );
1063
+ }
1064
+
1065
+ ReactDOM.createRoot(document.getElementById("root")).render(e(App));
1066
+ })();
1067
+ </script>
1068
+
1069
+ <div class="login-req-overlay" id="loginReqPopup">
1070
+ <div class="login-req-popup">
1071
+ <h3>&#128274; Bạn chưa đăng nhập</h3>
1072
+ <p>Hãy đăng nhập để khám phá cửa hàng ANIVIETDB và đặt mua sản phẩm.</p>
1073
+ <div class="login-req-actions">
1074
+ <button class="login-req-btn" id="loginReqGo">Bắt đầu đăng nhập</button>
1075
+ <button class="login-req-btn login-req-ghost" id="loginReqClose">Để sau</button>
1076
+ </div>
1077
+ </div>
1078
+ </div>
1079
+
1080
+ <style>
1081
+ .login-req-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.7); display: none; align-items: center; justify-content: center; z-index: 10000; backdrop-filter: blur(4px); }
1082
+ .login-req-overlay.active { display: flex; }
1083
+ .login-req-popup { background: #1d1d1d; border: 1px solid var(--cr-border); border-radius: 16px; padding: 30px 36px; text-align: center; color: var(--cr-ink); font-family: "Inter", system-ui, sans-serif; max-width: 380px; box-shadow: 0 20px 60px rgba(0,0,0,.6); }
1084
+ .login-req-popup h3 { margin: 0 0 10px; font-size: 20px; font-weight: 700; }
1085
+ .login-req-popup p { margin: 0 0 22px; color: var(--cr-muted); font-size: 14px; line-height: 1.6; }
1086
+ .login-req-actions { display: flex; flex-direction: column; gap: 10px; }
1087
+ .login-req-btn { background: var(--cr-orange); color: #fff; border: none; padding: 12px 20px; border-radius: 10px; font-weight: 600; font-size: 15px; cursor: pointer; transition: background .15s; }
1088
+ .login-req-btn:hover { background: var(--cr-orange-hover); }
1089
+ .login-req-btn.login-req-ghost { background: none; color: var(--cr-muted); border: 1px solid var(--cr-border); }
1090
+ .login-req-btn.login-req-ghost:hover { color: #fff; border-color: #555; }
1091
+ </style>
1092
+
1093
+ <script>
1094
+ (function () {
1095
+ var overlay = document.getElementById("loginReqPopup");
1096
+ var goBtn = document.getElementById("loginReqGo");
1097
+ var closeBtn = document.getElementById("loginReqClose");
1098
+ if (goBtn) goBtn.addEventListener("click", function () {
1099
+ window.location.href = "login.html";
1100
+ });
1101
+ if (closeBtn) closeBtn.addEventListener("click", function () {
1102
+ overlay.classList.remove("active");
1103
+ });
1104
+ if (overlay) overlay.addEventListener("click", function (e) {
1105
+ if (e.target === overlay) overlay.classList.remove("active");
1106
+ });
1107
+ })();
1108
+ </script>
1109
+ </body>
1110
+ </html>
login.html ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
7
+ <meta http-equiv="Pragma" content="no-cache">
8
+ <meta http-equiv="Expires" content="0">
9
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%23151515'/><text x='50' y='68' font-size='52' font-family='Arial' font-weight='bold' fill='%23ff5e00' text-anchor='middle'>A</text></svg>">
10
+ <title>Đăng nhập - ANIVIETDB</title>
11
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
12
+ <style>
13
+ * { box-sizing: border-box; margin: 0; padding: 0; }
14
+ body {
15
+ font-family: 'Roboto', sans-serif;
16
+ background: linear-gradient(135deg, #0b0d16 0%, #1a1033 50%, #2d1b4e 100%);
17
+ min-height: 100vh;
18
+ display: flex; align-items: center; justify-content: center;
19
+ padding: 20px; color: #e8e8f0;
20
+ }
21
+ body::before {
22
+ content: "";
23
+ position: fixed;
24
+ inset: 0;
25
+ z-index: -2;
26
+ pointer-events: none;
27
+ background-color: #151515;
28
+ background-image:
29
+ url("https://phim.nguonc.com/public/images/Film/76.jpg"),
30
+ url("https://phim.nguonc.com/public/images/Film/666.jpg"),
31
+ url("https://phim.nguonc.com/public/images/Film/77.jpg"),
32
+ url("https://phim.nguonc.com/public/images/Film/667.jpg"),
33
+ url("https://phim.nguonc.com/public/images/Film/259.jpg"),
34
+ url("https://phim.nguonc.com/public/images/Film/2738.jpg"),
35
+ url("https://phim.nguonc.com/public/images/Film/3485.jpg"),
36
+ url("https://phim.nguonc.com/public/images/Film/141438.jpg"),
37
+ url("https://phim.nguonc.com/public/images/Film/date-a-bullet-thumb.jpg"),
38
+ url("https://phim.nguonc.com/public/images/Film/3103.jpg"),
39
+ url("https://phim.nguonc.com/public/images/Post/5/aishiteru-game-wo-owarasetai.jpg"),
40
+ url("https://phim.nguonc.com/public/images/Film/541.jpg"),
41
+ url("https://phim.nguonc.com/public/images/Film/536.jpg"),
42
+ url("https://phim.nguonc.com/public/images/Film/56167.jpg"),
43
+ url("https://phim.nguonc.com/public/images/Film/56643.jpg"),
44
+ url("https://phim.nguonc.com/public/images/Film/538.jpg"),
45
+ url("https://phim.nguonc.com/public/images/Film/540.jpg"),
46
+ url("https://phim.nguonc.com/public/images/Film/60351.jpg"),
47
+ url("https://phim.nguonc.com/public/images/Film/2607.jpg"),
48
+ url("https://phim.nguonc.com/public/images/Film/2661.jpg"),
49
+ url("https://phim.nguonc.com/public/images/Film/5MgMCnslrxQfOfaq6T5ndkLlcCk.jpg"),
50
+ url("https://phim.nguonc.com/public/images/Film/134808.jpg"),
51
+ url("https://phim.nguonc.com/public/images/Film/138379.jpg"),
52
+ url("https://phim.nguonc.com/public/images/Post/9/seishun-buta-yarou-wa-santa-claus-no-yume-wo-minai-1.jpg");
53
+ background-size: 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%;
54
+ background-position: 0% 0%, 14.2857% 0%, 28.5714% 0%, 42.8571% 0%, 57.1429% 0%, 71.4286% 0%, 85.7143% 0%, 100% 0%, 0% 50%, 14.2857% 50%, 28.5714% 50%, 42.8571% 50%, 57.1429% 50%, 71.4286% 50%, 85.7143% 50%, 100% 50%, 0% 100%, 14.2857% 100%, 28.5714% 100%, 42.8571% 100%, 57.1429% 100%, 71.4286% 100%, 85.7143% 100%, 100% 100%;
55
+ background-repeat: no-repeat;
56
+ filter: brightness(0.42) saturate(0.65);
57
+ }
58
+ body::after {
59
+ content: "";
60
+ position: fixed;
61
+ inset: 0;
62
+ z-index: -1;
63
+ pointer-events: none;
64
+ background: linear-gradient(180deg, rgba(21,21,21,0.55), rgba(21,21,21,0.78) 68%, #151515 100%),
65
+ radial-gradient(120% 90% at 50% 0%, rgba(21,21,21,0) 42%, rgba(21,21,21,0.5) 100%);
66
+ }
67
+ .auth-card {
68
+ width: 100%; max-width: 400px;
69
+ background: rgba(20, 22, 38, 0.92);
70
+ border: 1px solid rgba(120, 100, 230, 0.35);
71
+ border-radius: 16px;
72
+ padding: 32px 28px;
73
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.55);
74
+ }
75
+ .auth-logo {
76
+ text-align: center; font-size: 22px; font-weight: 700; letter-spacing: 0.5px;
77
+ margin-bottom: 6px;
78
+ }
79
+ .auth-logo span { color: #8b7bff; }
80
+ .auth-sub { text-align: center; color: #9a9ab0; font-size: 13px; margin-bottom: 22px; }
81
+ .auth-tabs { display: flex; margin-bottom: 22px; border-bottom: 1px solid rgba(120,100,230,0.25); }
82
+ .auth-tab {
83
+ flex: 1; text-align: center; padding: 10px 0; cursor: pointer;
84
+ color: #9a9ab0; font-weight: 500; border-bottom: 2px solid transparent; transition: all .2s;
85
+ }
86
+ .auth-tab.active { color: #fff; border-bottom-color: #8b7bff; }
87
+ .field { margin-bottom: 16px; }
88
+ .field label { display: block; font-size: 12px; color: #a9a9c4; margin-bottom: 6px; }
89
+ .field input {
90
+ width: 100%; padding: 12px 14px; border-radius: 10px;
91
+ border: 1px solid rgba(120,100,230,0.3); background: #10121f;
92
+ color: #fff; font-size: 14px; outline: none; transition: border .2s;
93
+ }
94
+ .field input:focus { border-color: #8b7bff; }
95
+ .auth-btn {
96
+ width: 100%; padding: 13px; border: none; border-radius: 10px;
97
+ background: linear-gradient(135deg, #7c5cff, #5a3bd6);
98
+ color: #fff; font-size: 15px; font-weight: 700; cursor: pointer;
99
+ transition: transform .15s, filter .15s;
100
+ }
101
+ .auth-btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
102
+ .auth-btn:disabled { opacity: 0.6; cursor: not-allowed; }
103
+ .auth-error {
104
+ min-height: 20px; color: #ff8a8a; font-size: 13px; margin-top: 12px; text-align: center;
105
+ }
106
+ .auth-ok {
107
+ color: #7ee787; font-size: 13px; text-align: center; margin-top: 12px; min-height: 20px;
108
+ }
109
+ .logout-row { display: flex; align-items: center; justify-content: space-between; margin-top: 18px; } .logged-in { text-align: center; padding: 10px 0; }
110
+ .logged-in .name { font-size: 18px; font-weight: 700; margin-bottom: 4px; }
111
+ .logged-in .email { font-size: 13px; color: #9a9ab0; margin-bottom: 18px; }
112
+ .logged-title {
113
+ text-align: center; font-size: 13px; color: #7ee787; font-weight: 500; margin-bottom: 18px;
114
+ }
115
+ .btn-ghost {
116
+ width: 100%; padding: 12px; border-radius: 10px;
117
+ border: 1px solid rgba(255,138,138,0.4); background: transparent;
118
+ color: #ff8a8a; font-size: 14px; font-weight: 600; cursor: pointer;
119
+ }
120
+ .btn-ghost:hover { background: rgba(255,138,138,0.08); }
121
+ .btn-admin {
122
+ display: block; width: 100%; padding: 12px; border-radius: 10px;
123
+ border: 1px solid rgba(126,231,135,0.45); background: rgba(126,231,135,0.08);
124
+ color: #7ee787; font-size: 14px; font-weight: 700; cursor: pointer;
125
+ text-align: center; text-decoration: none;
126
+ }
127
+ .btn-admin:hover { background: rgba(126,231,135,0.18); }
128
+ .btn-home {
129
+ display: block; width: 100%; padding: 12px; border-radius: 10px;
130
+ border: 1px solid rgba(139,123,255,0.45); background: rgba(139,123,255,0.1);
131
+ color: #b7abff; font-size: 14px; font-weight: 700; cursor: pointer;
132
+ text-align: center; text-decoration: none; margin-bottom: 10px;
133
+ }
134
+ .btn-home:hover { background: rgba(139,123,255,0.2); }
135
+ .btn-shop {
136
+ display: block; width: 100%; padding: 12px; border-radius: 10px;
137
+ border: 1px solid rgba(255,94,0,0.5); background: rgba(255,94,0,0.1);
138
+ color: #ff9a5a; font-size: 14px; font-weight: 700; cursor: pointer;
139
+ text-align: center; text-decoration: none; margin-bottom: 10px;
140
+ }
141
+ .btn-shop:hover { background: rgba(255,94,0,0.2); }
142
+ .auth-back {
143
+ display: block; text-align: center; margin-top: 18px;
144
+ color: #9a9ab0; font-size: 13px; text-decoration: none;
145
+ }
146
+ .auth-back:hover { color: #fff; text-decoration: underline; }
147
+ .auth-copyright {
148
+ text-align: center; margin-top: 20px; padding-top: 14px;
149
+ border-top: 1px solid rgba(120,100,230,0.15);
150
+ color: #6d6d85; font-size: 12px;
151
+ }
152
+ .terms-row {
153
+ display: flex; align-items: flex-start; gap: 8px;
154
+ font-size: 12px; color: #a9a9c4; line-height: 1.5; margin-bottom: 16px;
155
+ }
156
+ .terms-row input { margin-top: 2px; accent-color: #8b7bff; }
157
+ .turnstile-wrap { display: flex; justify-content: center; margin-bottom: 16px; }
158
+ .terms-row a { color: #8b7bff; text-decoration: none; }
159
+ .terms-row a:hover { text-decoration: underline; }
160
+ .terms-modal {
161
+ position: fixed; inset: 0; z-index: 1000;
162
+ display: none; align-items: center; justify-content: center;
163
+ background: rgba(0,0,0,0.6); padding: 20px;
164
+ }
165
+ .terms-modal.open { display: flex; }
166
+ .terms-modal-card {
167
+ width: 100%; max-width: 520px; max-height: 80vh;
168
+ background: #141622; border: 1px solid rgba(120,100,230,0.35);
169
+ border-radius: 14px; display: flex; flex-direction: column; overflow: hidden;
170
+ }
171
+ .terms-modal-head {
172
+ display: flex; align-items: center; justify-content: space-between;
173
+ padding: 14px 18px; border-bottom: 1px solid rgba(120,100,230,0.25);
174
+ }
175
+ .terms-modal-head h3 { font-size: 16px; color: #fff; }
176
+ .terms-modal-close {
177
+ background: none; border: none; color: #9a9ab0; font-size: 20px;
178
+ cursor: pointer; line-height: 1;
179
+ }
180
+ .terms-modal-close:hover { color: #fff; }
181
+ .terms-modal-body { padding: 16px 18px; overflow-y: auto; font-size: 13px; line-height: 1.7; color: #c6c6da; }
182
+ .terms-modal-body h4 { color: #fff; font-size: 14px; margin: 14px 0 6px; }
183
+ .terms-modal-body h4:first-child { margin-top: 0; }
184
+ .terms-modal-foot {
185
+ padding: 12px 18px; border-top: 1px solid rgba(120,100,230,0.25); text-align: right;
186
+ }
187
+ .terms-modal-foot button {
188
+ padding: 9px 18px; border: none; border-radius: 8px;
189
+ background: linear-gradient(135deg, #7c5cff, #5a3bd6); color: #fff;
190
+ font-size: 13px; font-weight: 700; cursor: pointer;
191
+ }
192
+ </style>
193
+ </head>
194
+ <body>
195
+ <div class="auth-card">
196
+ <div class="auth-logo">ANIVIET<span>DB</span></div>
197
+ <p class="auth-sub">Đăng nhập hoặc tạo tài khoản</p>
198
+
199
+ <div class="auth-tabs" id="authTabs">
200
+ <div class="auth-tab active" data-mode="login">Đăng nhập</div>
201
+ <div class="auth-tab" data-mode="register">Đăng ký</div>
202
+ </div>
203
+
204
+ <div id="authForms">
205
+ <div class="field">
206
+ <label id="loginLabel">Email hoặc tên đăng nhập</label>
207
+ <input type="text" id="email" placeholder="Email hoặc tên đăng nhập" autocomplete="username">
208
+ </div>
209
+ <div class="field register-only" style="display:none;">
210
+ <label>Tên đăng nhập</label>
211
+ <input type="text" id="name" placeholder="Tên của bạn" autocomplete="name">
212
+ </div>
213
+ <div class="field">
214
+ <label>Mật khẩu</label>
215
+ <input type="password" id="password" placeholder="••••••••" autocomplete="current-password">
216
+ </div>
217
+ <div class="field register-only" style="display:none;">
218
+ <label>Nhập lại mật khẩu</label>
219
+ <input type="password" id="password2" placeholder="••••••••" autocomplete="new-password">
220
+ </div>
221
+ <label class="terms-row register-only" style="display:none;">
222
+ <input type="checkbox" id="termsCheck">
223
+ <span>Tôi đã đọc và đồng ý với <a href="javascript:void(0)" id="termsLink">Điều khoản sử dụng</a></span>
224
+ </label>
225
+ <div class="turnstile-wrap">
226
+ <div id="turnstile-login" class="cf-turnstile login-only"
227
+ data-sitekey="0x4AAAAAAEQz9I-TXTQoVuii"
228
+ data-action="login"
229
+ data-callback="onTurnstileSuccess"
230
+ data-expired-callback="onTurnstileExpired"
231
+ data-error-callback="onTurnstileError"></div>
232
+ <div id="turnstile-register" class="cf-turnstile register-only" style="display:none;"
233
+ data-sitekey="0x4AAAAAAEQz9I-TXTQoVuii"
234
+ data-action="register"
235
+ data-callback="onTurnstileSuccess"
236
+ data-expired-callback="onTurnstileExpired"
237
+ data-error-callback="onTurnstileError"></div>
238
+ </div>
239
+ <button class="auth-btn" id="submitBtn">Đăng nhập</button>
240
+ </div>
241
+
242
+ <div id="loggedIn" style="display:none;">
243
+ <div class="logged-in">
244
+ <div class="name" id="userName"></div>
245
+ <div class="email" id="userEmail"></div>
246
+ <div class="logged-title">Bạn đã đăng nhập</div>
247
+ </div>
248
+ <div style="display:none; margin-bottom:10px;" id="adminPanelWrap">
249
+ <a href="control-panel.html" class="btn-admin">Control Panel</a>
250
+ </div>
251
+ <a href="sfw/index.html" class="btn-home">Trang chủ</a>
252
+ <a href="shop/index.html" class="btn-shop">&#128722; Cửa hàng</a>
253
+ <button class="btn-ghost" id="logoutBtn">Đăng xuất</button>
254
+ </div>
255
+
256
+ <div class="auth-error" id="error"></div>
257
+ <div class="auth-ok" id="ok"></div>
258
+ <a href="index.html" class="auth-back" id="authBack">&larr; Về trang chủ</a>
259
+ <div class="auth-copyright">© 2026 ANIVIETDB. All rights reserved.</div>
260
+ </div>
261
+
262
+ <div class="terms-modal" id="termsModal">
263
+ <div class="terms-modal-card">
264
+ <div class="terms-modal-head">
265
+ <h3>Điều khoản sử dụng</h3>
266
+ <button class="terms-modal-close" id="termsClose" aria-label="Đóng">&times;</button>
267
+ </div>
268
+ <div class="terms-modal-body">
269
+ <h4>1. Chấp nhận điều khoản</h4>
270
+ <p>Bằng việc truy cập và sử dụng ANIVIETDB, bạn xác nhận đã đọc, hiểu và đồng ý tuân thủ các điều khoản dưới đây. Nếu không đồng ý, vui lòng ngừng sử dụng dịch vụ.</p>
271
+ <h4>2. Mục đích sử dụng</h4>
272
+ <p>ANIVIETDB là nền tảng tổng hợp thông tin anime, manga, manhwa, manhua và light novel phục vụ nhu cầu tìm kiếm, theo dõi và giải trí cá nhân của người dùng. Không được dùng thông tin tại đây cho mục đích thương mại hoặc trái pháp luật.</p>
273
+ <h4>3. Tài khoản người dùng</h4>
274
+ <p>Bạn chịu trách nhiệm bảo mật mật khẩu và mọi hoạt động diễn ra trên tài khoản của mình. Không được tạo tài khoản giả mạo, mạo danh người khác, hoặc lợi dụng tài khoản để gây ảnh hưởng tiêu cực tới cộng đồng.</p>
275
+ <h4>4. Nội dung và bản quyền</h4>
276
+ <p>Toàn bộ thông tin trên ANIVIETDB chỉ mang tính tham khảo. Quyền sở hữu trí tuệ của các tác phẩm thuộc về tác giả và đơn vị phát hành. Nếu bạn là chủ sở hữu và muốn yêu cầu gỡ nội dung, vui lòng liên hệ ban quản trị.</p>
277
+ <h4>5. Hành vi bị cấm</h4>
278
+ <p>Cấm spam, phát tán thông tin sai lệch, phần mềm độc hại, hoặc thực hiện hành vi xâm phạm hệ thống và tài khoản người dùng khác trên nền tảng.</p>
279
+ <h4>6. Từ chối trách nhiệm</h4>
280
+ <p>ANIVIETDB nỗ lực cập nhật thông tin chính xác nhưng không đảm bảo tính đầy đủ, kịp thời của dữ liệu. Website không chịu trách nhiệm về bất kỳ thiệt hại phát sinh từ việc sử dụng dịch vụ.</p>
281
+ <h4>7. Thay đổi điều khoản</h4>
282
+ <p>ANIVIETDB có quyền cập nhật điều khoản bất cứ lúc nào. Việc tiếp tục sử dụng website sau khi có thay đổi đồng nghĩa với việc bạn chấp nhận điều khoản mới.</p>
283
+ </div>
284
+ <div class="terms-modal-foot">
285
+ <button id="termsAgree">Tôi đồng ý</button>
286
+ </div>
287
+ </div>
288
+ </div>
289
+
290
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
291
+ <script src="signup-loginsystem.js?v=8"></script>
292
+ <script>
293
+ var mode = 'login';
294
+ var emailEl = document.getElementById('email');
295
+ var nameEl = document.getElementById('name');
296
+ var passEl = document.getElementById('password');
297
+ var pass2El = document.getElementById('password2');
298
+ var submitBtn = document.getElementById('submitBtn');
299
+ var errorEl = document.getElementById('error');
300
+ var okEl = document.getElementById('ok');
301
+ var authForms = document.getElementById('authForms');
302
+ var loggedIn = document.getElementById('loggedIn');
303
+
304
+ var cooldownTimer = null;
305
+ function updateCooldown() {
306
+ var wait = window.AuthSystem.cooldownRemaining();
307
+ if (wait <= 0) {
308
+ if (cooldownTimer) { clearInterval(cooldownTimer); cooldownTimer = null; }
309
+ errorEl.textContent = '';
310
+ submitBtn.disabled = false;
311
+ return;
312
+ }
313
+ errorEl.textContent = 'Vui lòng đợi ' + Math.ceil(wait / 1000) + ' giây trước khi đăng nhập hoặc tạo tài khoản.';
314
+ submitBtn.disabled = true;
315
+ }
316
+ function startCooldown() {
317
+ if (cooldownTimer) { clearInterval(cooldownTimer); }
318
+ cooldownTimer = setInterval(updateCooldown, 200);
319
+ updateCooldown();
320
+ }
321
+
322
+ function showSession(user) {
323
+ authForms.style.display = 'none';
324
+ document.getElementById('authTabs').style.display = 'none';
325
+ document.querySelector('.auth-sub').style.display = 'none';
326
+ document.getElementById('authBack').style.display = 'none';
327
+ loggedIn.style.display = 'block';
328
+ document.getElementById('userName').textContent = user.username || user.name || '';
329
+ document.getElementById('userEmail').textContent = user.email || '';
330
+ errorEl.textContent = '';
331
+ okEl.textContent = 'Đã đăng nhập.';
332
+ var isAdmin = user.role === 'admin';
333
+ document.getElementById('adminPanelWrap').style.display = isAdmin ? 'block' : 'none';
334
+ }
335
+
336
+ function resetWidget() {
337
+ var el = activeWidget();
338
+ if (el && window.turnstile && typeof window.turnstile.reset === 'function') {
339
+ try { window.turnstile.reset(el); } catch (e) { /* ignore */ }
340
+ }
341
+ submitBtn.disabled = true;
342
+ }
343
+
344
+ function showError(msg) {
345
+ okEl.textContent = '';
346
+ errorEl.textContent = msg;
347
+ if (window.turnstile && !turnstileSolved()) submitBtn.disabled = true;
348
+ else submitBtn.disabled = false;
349
+ }
350
+
351
+ function setMode(m) {
352
+ mode = m;
353
+ document.querySelectorAll('.auth-tab').forEach(function (t) {
354
+ t.classList.toggle('active', t.getAttribute('data-mode') === m);
355
+ });
356
+ document.querySelectorAll('.register-only').forEach(function (el) {
357
+ el.style.display = (m === 'register') ? '' : 'none';
358
+ });
359
+ document.querySelectorAll('.login-only').forEach(function (el) {
360
+ el.style.display = (m === 'login') ? '' : 'none';
361
+ });
362
+ submitBtn.textContent = (m === 'register') ? 'Tạo tài khoản' : 'Đăng nhập';
363
+ errorEl.textContent = '';
364
+ okEl.textContent = '';
365
+ if (window.turnstile && !turnstileSolved()) submitBtn.disabled = true;
366
+ }
367
+
368
+ function activeWidget() {
369
+ return mode === 'register'
370
+ ? document.getElementById('turnstile-register')
371
+ : document.getElementById('turnstile-login');
372
+ }
373
+
374
+ function turnstileSolved() {
375
+ var el = activeWidget();
376
+ return !!(window.turnstile && el && window.turnstile.getResponse(el));
377
+ }
378
+
379
+ function onTurnstileSuccess() { submitBtn.disabled = false; }
380
+ function onTurnstileExpired() { submitBtn.disabled = true; }
381
+ function onTurnstileError() { submitBtn.disabled = true; }
382
+
383
+ var termsModal = document.getElementById('termsModal');
384
+ var termsCheck = document.getElementById('termsCheck');
385
+ document.getElementById('termsLink').addEventListener('click', function () {
386
+ termsModal.classList.add('open');
387
+ });
388
+ document.getElementById('termsClose').addEventListener('click', function () {
389
+ termsModal.classList.remove('open');
390
+ });
391
+ document.getElementById('termsAgree').addEventListener('click', function () {
392
+ termsCheck.checked = true;
393
+ termsModal.classList.remove('open');
394
+ });
395
+ termsModal.addEventListener('click', function (e) {
396
+ if (e.target === termsModal) termsModal.classList.remove('open');
397
+ });
398
+
399
+ document.querySelectorAll('.auth-tab').forEach(function (t) {
400
+ t.addEventListener('click', function () { setMode(t.getAttribute('data-mode')); });
401
+ });
402
+
403
+ function doSubmit() {
404
+ if (!window.AuthSystem || !window.AuthSystem.isConfigured()) {
405
+ showError('Backend chưa được cấu hình. Vui lòng dán URL API.');
406
+ return;
407
+ }
408
+ var wait = window.AuthSystem.cooldownRemaining();
409
+ if (wait > 0) {
410
+ showError('Vui lòng đợi ' + Math.ceil(wait / 1000) + ' giây trước khi đăng nhập hoặc tạo tài khoản.');
411
+ return;
412
+ }
413
+ var loginId = emailEl.value.trim();
414
+ var password = passEl.value;
415
+ if (!loginId || !password) { showError('Vui lòng nhập email/tên đăng nhập và mật khẩu.'); return; }
416
+ if (window.turnstile && !turnstileSolved()) {
417
+ showError('Vui lòng hoàn tất xác minh "Tôi không phải người máy".');
418
+ return;
419
+ }
420
+
421
+ if (mode === 'register') {
422
+ var name = nameEl.value.trim();
423
+ if (!name) { showError('Vui lòng nhập tên đăng nhập.'); return; }
424
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(loginId)) { showError('Email không hợp lệ.'); return; }
425
+ if (password.length < 6) { showError('Mật khẩu phải có ít nhất 6 ký tự.'); return; }
426
+ if (password !== pass2El.value) { showError('Mật khẩu nhập lại không khớp.'); return; }
427
+ if (!termsCheck.checked) { showError('Vui lòng đồng ý với Điều khoản sử dụng.'); return; }
428
+ submitBtn.disabled = true;
429
+ AuthSystem.register(loginId, name, password).then(function (data) {
430
+ showSession(data.user);
431
+ setTimeout(function () { window.location.href = 'sfw/'; }, 1200);
432
+ }).catch(function (e) { resetWidget(); showError(e.message); });
433
+ } else {
434
+ submitBtn.disabled = true;
435
+ AuthSystem.login(loginId, password).then(function (data) {
436
+ showSession(data.user);
437
+ setTimeout(function () {
438
+ window.location.href = (data.user && data.user.role === 'admin') ? 'control-panel.html' : 'sfw/';
439
+ }, 1200);
440
+ }).catch(function (e) { resetWidget(); showError(e.message); });
441
+ }
442
+ }
443
+
444
+ submitBtn.addEventListener('click', doSubmit);
445
+ document.addEventListener('keydown', function (e) { if (e.key === 'Enter') doSubmit(); });
446
+ document.getElementById('logoutBtn').addEventListener('click', function () {
447
+ AuthSystem.logout().then(function () {
448
+ loggedIn.style.display = 'none';
449
+ authForms.style.display = '';
450
+ document.getElementById('authTabs').style.display = '';
451
+ document.querySelector('.auth-sub').style.display = '';
452
+ document.getElementById('authBack').style.display = '';
453
+ setMode('login');
454
+ startCooldown();
455
+ });
456
+ });
457
+
458
+ if (window.AuthSystem.cooldownRemaining() > 0) startCooldown();
459
+ window.AuthSystem.me().then(function (user) { if (user) showSession(user); });
460
+ </script>
461
+ </body>
462
+ </html>
manga-api.js ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ window.MANGA_API = (function () {
2
+ var RATE_MSG = 'MangaDex đang giới hạn yêu cầu. Vui lòng thử lại sau';
3
+
4
+ var RATING_SAFE = '&contentRating[]=safe&contentRating[]=suggestive';
5
+ var RATING_ALL = RATING_SAFE + '&contentRating[]=erotica&contentRating[]=pornographic';
6
+ var FEED_LIMIT = 100;
7
+
8
+ function api(url) {
9
+ function request() {
10
+ return fetch(window.MD_WORKER + url).then(function (r) {
11
+ if (r.status === 404) throw { notFound: true };
12
+ if (r.status === 429) throw { rateLimited: true };
13
+ if (r.status >= 500) throw { serverError: true };
14
+ if (!r.ok) return r.text().then(function (t) { throw new Error('HTTP ' + r.status + (t ? ': ' + t : '')); });
15
+ return r.json();
16
+ });
17
+ }
18
+ return request().catch(function (e) {
19
+ if (!(e && e.rateLimited)) throw e;
20
+ return new Promise(function (resolve) { setTimeout(resolve, 2000); }).then(request);
21
+ });
22
+ }
23
+
24
+ function contentRatingParam(nsfw) { return nsfw ? RATING_ALL : RATING_SAFE; }
25
+
26
+ function feedUrl(mangaId, lang, nsfw, offset) {
27
+ return '/manga/' + mangaId + '/feed?limit=' + FEED_LIMIT + '&offset=' + offset + '&order[chapter]=asc&includes[]=scanlation_group'
28
+ + (lang && lang !== 'all' ? '&translatedLanguage[]=' + lang : '')
29
+ + contentRatingParam(nsfw);
30
+ }
31
+
32
+ function fetchLang(mangaId, lang, nsfw) {
33
+ var all = [];
34
+ var offset = 0;
35
+ function page() {
36
+ return api(feedUrl(mangaId, lang, nsfw, offset)).then(function (j) {
37
+ all = all.concat(j.data || []);
38
+ offset += FEED_LIMIT;
39
+ if (j.data && j.data.length === FEED_LIMIT) return page();
40
+ return all;
41
+ });
42
+ }
43
+ return page();
44
+ }
45
+
46
+ function chooseChapters(mangaId, nsfw, want) {
47
+ var w = want || 'vi';
48
+ return fetchLang(mangaId, w, nsfw).then(function (chs) {
49
+ if (chs.length) return { lang: w, chapters: chs };
50
+ if (w === 'vi') {
51
+ return fetchLang(mangaId, 'en', nsfw).then(function (en) {
52
+ if (en.length) return { lang: 'en', chapters: en };
53
+ return fetchLang(mangaId, 'all', nsfw).then(function (all) { return { lang: 'all', chapters: all }; });
54
+ });
55
+ }
56
+ if (w === 'en') {
57
+ return fetchLang(mangaId, 'all', nsfw).then(function (all) { return { lang: 'all', chapters: all }; });
58
+ }
59
+ return { lang: w, chapters: chs };
60
+ });
61
+ }
62
+
63
+ function chapterPages(chapterId) {
64
+ return api('/at-home/server/' + chapterId).then(function (j) {
65
+ return {
66
+ base: j.baseUrl,
67
+ hash: j.chapter && j.chapter.hash,
68
+ files: (j.chapter && j.chapter.data) || [],
69
+ saver: (j.chapter && j.chapter.dataSaver) || []
70
+ };
71
+ });
72
+ }
73
+
74
+ function pageSrc(base, hash, file, saver) {
75
+ var direct = base + (saver ? '/data-saver/' : '/data/') + hash + '/' + file;
76
+ if (window.MD_WORKER) {
77
+ var workerBase = String(window.MD_WORKER).replace(/\/+$/, '');
78
+ return workerBase + '/img?u=' + encodeURIComponent(direct);
79
+ }
80
+ return direct;
81
+ }
82
+
83
+ function langName(l) { return l === 'vi' ? 'Vietsub' : l === 'en' ? 'English' : 'All'; }
84
+
85
+ function errorMessage(e) {
86
+ if (e && e.notFound) return 'Content not found.';
87
+ if (e && e.rateLimited) return RATE_MSG;
88
+ if (e && e.serverError) return 'MangaDex đang gặp lỗi tạm thời. Vui lòng thử lại sau.';
89
+ return null;
90
+ }
91
+
92
+ function qs(n) { var m = new RegExp('[?&]' + n + '=([^&]*)').exec(location.search); return m ? decodeURIComponent(m[1]) : null; }
93
+
94
+ function chapterLabel(ch) {
95
+ var a = ch.attributes || {};
96
+ var s = a.chapter ? 'Chương ' + a.chapter : 'Chương khác';
97
+ if (a.title) s += ': ' + a.title;
98
+ return s;
99
+ }
100
+
101
+ return {
102
+ RATE_MSG: RATE_MSG,
103
+ api: api,
104
+ fetchLang: fetchLang,
105
+ chooseChapters: chooseChapters,
106
+ chapterPages: chapterPages,
107
+ pageSrc: pageSrc,
108
+ langName: langName,
109
+ errorMessage: errorMessage,
110
+ qs: qs,
111
+ chapterLabel: chapterLabel
112
+ };
113
+ })();
manga-config.js ADDED
@@ -0,0 +1 @@
 
 
1
+ window.MD_WORKER = 'https://anivietdb.npgia-himlam.workers.dev/';
scan_anime_result.json ADDED
@@ -0,0 +1,1314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "Date A Live (Phần 1)",
4
+ "page": "anime/universal/date-a-live/phan-1/",
5
+ "local": 0,
6
+ "src": "Hoàn tất (12/12)",
7
+ "srcName": "Date A Live",
8
+ "diff": null
9
+ },
10
+ {
11
+ "name": "Date A Live: Date to Date (OVA)",
12
+ "page": "anime/universal/date-a-live/ova-date-to-date/",
13
+ "local": 0,
14
+ "src": "FULL",
15
+ "srcName": "Date A Live: Date to Date",
16
+ "diff": null
17
+ },
18
+ {
19
+ "name": "Date A Live II (Phần 2)",
20
+ "page": "anime/universal/date-a-live/phan-2/",
21
+ "local": 0,
22
+ "src": "Hoàn tất (10/10)",
23
+ "srcName": "Date A Live II",
24
+ "diff": null
25
+ },
26
+ {
27
+ "name": "Date A Live II: Kurumi Star Festival (OVA)",
28
+ "page": "anime/universal/date-a-live/ova-kurumi/",
29
+ "local": 0,
30
+ "src": "FULL",
31
+ "srcName": "Date A Live II: Kurumi Star Festival",
32
+ "diff": null
33
+ },
34
+ {
35
+ "name": "Date A Live Movie: Mayuri Judgment",
36
+ "page": "anime/universal/date-a-live/movie-mayuri/",
37
+ "local": 0,
38
+ "src": "FULL",
39
+ "srcName": "Date A Live Movie: Mayuri Judgment",
40
+ "diff": null
41
+ },
42
+ {
43
+ "name": "Date A Live III (Phần 3)",
44
+ "page": "anime/universal/date-a-live/phan-3/",
45
+ "local": 0,
46
+ "src": "Hoàn tất (12/12)",
47
+ "srcName": "Date A Live III",
48
+ "diff": null
49
+ },
50
+ {
51
+ "name": "Date A Live IV (Phần 4)",
52
+ "page": "anime/universal/date-a-live/phan-4/",
53
+ "local": 0,
54
+ "src": "Hoàn tất (12/12)",
55
+ "srcName": "Date A Live IV",
56
+ "diff": null
57
+ },
58
+ {
59
+ "name": "Date A Live V (Phần 5)",
60
+ "page": "anime/universal/date-a-live/phan-5/",
61
+ "local": 0,
62
+ "src": "Hoàn tất (12/12)",
63
+ "srcName": "Date A Live V",
64
+ "diff": null
65
+ },
66
+ {
67
+ "name": "Date A Bullet",
68
+ "page": "anime/universal/date-a-live/spin-off-date-a-bullet/",
69
+ "local": 0,
70
+ "src": "Hoàn tất (2/2)",
71
+ "srcName": "Date A Bullet",
72
+ "diff": null
73
+ },
74
+ {
75
+ "name": "Date A Bullet: Dead or Bullet",
76
+ "page": "anime/universal/date-a-live/spin-off-dead-or-bullet/",
77
+ "local": 0,
78
+ "src": "FULL",
79
+ "srcName": "Date A Bullet: Dead or Bullet",
80
+ "diff": null
81
+ },
82
+ {
83
+ "name": "Aishiteru Game wo Owarasetai",
84
+ "page": "anime/universal/aishiteru-game/",
85
+ "local": 0,
86
+ "src": "Hoàn tất (12/12)",
87
+ "srcName": "Aishiteru Game wo Owarasetai",
88
+ "diff": null
89
+ },
90
+ {
91
+ "name": "Chuunibyou (Phần 1)",
92
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/phan-1/",
93
+ "local": 0,
94
+ "src": "Hoàn tất (12/12)",
95
+ "srcName": "Chuunibyou demo Koi ga Shitai!",
96
+ "diff": null
97
+ },
98
+ {
99
+ "name": "Chuunibyou: Slapstick Noel",
100
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/slapstick-noel/",
101
+ "local": 0,
102
+ "src": "FULL",
103
+ "srcName": "Chuunibyou demo Koi ga Shitai!: Kirameki no... Slapstick Noel",
104
+ "diff": null
105
+ },
106
+ {
107
+ "name": "Chuunibyou Movie: Rikka Kai",
108
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/movie-rikka-kai/",
109
+ "local": 0,
110
+ "src": "FULL",
111
+ "srcName": "Takanashi Rikka Kai: Chuunibyou demo Koi ga Shitai! Movie",
112
+ "diff": null
113
+ },
114
+ {
115
+ "name": "Chuunibyou Ren (Phần 2)",
116
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/phan-2/",
117
+ "local": 0,
118
+ "src": "Hoàn tất (7/7)",
119
+ "srcName": "Chuunibyou demo Koi ga Shitai! Ren Specials",
120
+ "diff": null
121
+ },
122
+ {
123
+ "name": "Chuunibyou Ren Lite",
124
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/ren-lite/",
125
+ "local": 0,
126
+ "src": "Hoàn tất (6/6)",
127
+ "srcName": "Chuunibyou demo Koi ga Shitai! Ren Lite",
128
+ "diff": null
129
+ },
130
+ {
131
+ "name": "Chuunibyou: The Rikka Wars",
132
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/rikka-wars/",
133
+ "local": 0,
134
+ "src": "FULL",
135
+ "srcName": "Chuunibyou demo Koi ga Shitai! Ren: The Rikka Wars",
136
+ "diff": null
137
+ },
138
+ {
139
+ "name": "Chuunibyou Ren Specials",
140
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/ren-specials/",
141
+ "local": 0,
142
+ "src": "Hoàn tất (7/7)",
143
+ "srcName": "Chuunibyou demo Koi ga Shitai! Ren Specials",
144
+ "diff": null
145
+ },
146
+ {
147
+ "name": "Chuunibyou Movie: Take On Me",
148
+ "page": "anime/universal/chuunibyou-demo-koi-ga-shitai/movie-take-on-me/",
149
+ "local": 0,
150
+ "src": "Trailer",
151
+ "srcName": "Hội Chứng Chuunibyou: Chấp Nhận Tôi",
152
+ "diff": null
153
+ },
154
+ {
155
+ "name": "Seishun Buta Yarou (Phần 1)",
156
+ "page": "anime/universal/seishun-buta-yarou/phan-1/",
157
+ "local": 13,
158
+ "src": "Hoàn tất (13/13)",
159
+ "srcName": "Seishun Buta Yarou wa Bunny Girl Senpai no Yume wo Minai",
160
+ "diff": null
161
+ },
162
+ {
163
+ "name": "Hội Chứng Tuổi Thanh Xuân: Thiếu Nữ Mộng Mơ",
164
+ "page": "anime/universal/seishun-buta-yarou/movie-1/",
165
+ "local": 0,
166
+ "src": "FULL",
167
+ "srcName": "Hội chứng tuổi thanh xuân: Thiếu nữ mộng mơ",
168
+ "diff": null
169
+ },
170
+ {
171
+ "name": "Hội Chứng Tuổi Thanh Xuân: Em Gái Xuất Hành",
172
+ "page": "anime/universal/seishun-buta-yarou/movie-2/",
173
+ "local": 0,
174
+ "src": "FULL",
175
+ "srcName": "Hội Chứng Tuổi Thanh Xuân: Em Gái Xuất Hành",
176
+ "diff": null
177
+ },
178
+ {
179
+ "name": "Hội Chứng Tuổi Thanh Xuân: Cô Bé Đeo Cặp Sách",
180
+ "page": "anime/universal/seishun-buta-yarou/movie-3/",
181
+ "local": 0,
182
+ "src": null,
183
+ "srcName": null,
184
+ "diff": null
185
+ },
186
+ {
187
+ "name": "Seishun Buta Yarou (Phần 2)",
188
+ "page": "anime/universal/seishun-buta-yarou/phan-2/",
189
+ "local": 13,
190
+ "src": "Hoàn tất (13/13)",
191
+ "srcName": "Seishun Buta Yarou wa Santa Claus no Yume wo Minai",
192
+ "diff": null
193
+ },
194
+ {
195
+ "name": "Slime (Phần 1)",
196
+ "page": "anime/universal/tensei-slime/phan-1/",
197
+ "local": 0,
198
+ "src": "Hoàn tất (5/5)",
199
+ "srcName": "Tensei shitara Slime Datta Ken OVA",
200
+ "diff": null
201
+ },
202
+ {
203
+ "name": "Slime (Phần 2)",
204
+ "page": "anime/universal/tensei-slime/phan-2/",
205
+ "local": 0,
206
+ "src": "Hoàn tất (24/24)",
207
+ "srcName": "Lúc Đó, Tôi Đã Chuyển Sinh Thành Slime (Phần 2)",
208
+ "diff": null
209
+ },
210
+ {
211
+ "name": "Slime (Phần 3)",
212
+ "page": "anime/universal/tensei-slime/phan-3/",
213
+ "local": 0,
214
+ "src": "Hoàn tất (26/26)",
215
+ "srcName": "Lúc Đó, Tôi Đã Chuyển Sinh Thành Slime (Phần 3)",
216
+ "diff": null
217
+ },
218
+ {
219
+ "name": "Slime (Phần 4)",
220
+ "page": "anime/universal/tensei-slime/phan-4/",
221
+ "local": 0,
222
+ "src": null,
223
+ "srcName": null,
224
+ "diff": null
225
+ },
226
+ {
227
+ "name": "Slime: Nhật Ký Của Rimuru",
228
+ "page": "anime/universal/tensei-slime/nhat-ky-rimuru/",
229
+ "local": 0,
230
+ "src": null,
231
+ "srcName": null,
232
+ "diff": null
233
+ },
234
+ {
235
+ "name": "Slime OVA",
236
+ "page": "anime/universal/tensei-slime/ova/",
237
+ "local": 0,
238
+ "src": "Hoàn tất (5/5)",
239
+ "srcName": "Tensei shitara Slime Datta Ken OVA",
240
+ "diff": null
241
+ },
242
+ {
243
+ "name": "Slime Movie: Mối Liên Kết Đỏ Thẫm",
244
+ "page": "anime/universal/tensei-slime/moi-lien-ket-do-tham/",
245
+ "local": 0,
246
+ "src": null,
247
+ "srcName": null,
248
+ "diff": null
249
+ },
250
+ {
251
+ "name": "Slime Movie: Nước Mắt Đại Dương",
252
+ "page": "anime/universal/tensei-slime/nuoc-mat-dai-duong/",
253
+ "local": 0,
254
+ "src": "FULL",
255
+ "srcName": "Lúc Đó Tôi Chuyển Sinh Thành Slime: Nước Mắt Đại Dương",
256
+ "diff": null
257
+ },
258
+ {
259
+ "name": "Slime: Giấc Mộng Coleus",
260
+ "page": "anime/universal/tensei-slime/giac-mong-coleus/",
261
+ "local": 0,
262
+ "src": "Hoàn tất (3/3)",
263
+ "srcName": "Lúc đó tôi đã chuyển sinh thành Slime: Giấc mộng Coleus",
264
+ "diff": null
265
+ },
266
+ {
267
+ "name": "Arya Bàn Bên Thỉnh Thoảng Lại Trêu Ghẹo Tôi Bằng Tiếng Nga",
268
+ "page": "anime/universal/arya-ban-ben/",
269
+ "local": 0,
270
+ "src": "Hoàn tất (12/12)",
271
+ "srcName": "Arya Bàn Bên Thỉnh Thoảng Lại Trêu Ghẹo Tôi Bằng Tiếng Nga",
272
+ "diff": null
273
+ },
274
+ {
275
+ "name": "Thiên Sứ Nhà Bên (Phần 1)",
276
+ "page": "anime/universal/thien-su-nha-ben/phan-1/",
277
+ "local": 0,
278
+ "src": "Hoàn tất (12/12)",
279
+ "srcName": "Thiên Sứ Nhà Bên (Phần 1)",
280
+ "diff": null
281
+ },
282
+ {
283
+ "name": "Thiên Sứ Nhà Bên (Phần 2)",
284
+ "page": "anime/universal/thien-su-nha-ben/phan-2/",
285
+ "local": 0,
286
+ "src": null,
287
+ "srcName": null,
288
+ "diff": null
289
+ },
290
+ {
291
+ "name": "Gundam 00 (Phần 1)",
292
+ "page": "anime/multiversal/gundam/gundam-00/phan-1/",
293
+ "local": 0,
294
+ "src": "Hoàn tất (25/25)",
295
+ "srcName": "Mobile Suit Gundam 00",
296
+ "diff": null
297
+ },
298
+ {
299
+ "name": "Gundam 00 (Phần 2)",
300
+ "page": "anime/multiversal/gundam/gundam-00/phan-2/",
301
+ "local": 0,
302
+ "src": "Hoàn tất (25/25)",
303
+ "srcName": "Mobile Suit Gundam 00 Second Season",
304
+ "diff": null
305
+ },
306
+ {
307
+ "name": "Gundam 00 Movie: A Wakening of the Trailblazer",
308
+ "page": "anime/multiversal/gundam/gundam-00/movie/",
309
+ "local": 0,
310
+ "src": "FULL",
311
+ "srcName": "Mobile Suit Gundam 00 The Movie: A Wakening of the Trailblazer",
312
+ "diff": null
313
+ },
314
+ {
315
+ "name": "Gundam Build Fighters",
316
+ "page": "anime/multiversal/gundam/gundam-build-fighters/phan-1/",
317
+ "local": 25,
318
+ "src": "Hoàn tất (25/25)",
319
+ "srcName": "Gundam Build Fighters",
320
+ "diff": null
321
+ },
322
+ {
323
+ "name": "Gundam Build Fighters Try",
324
+ "page": "anime/multiversal/gundam/gundam-build-fighters/try/",
325
+ "local": 25,
326
+ "src": "Hoàn tất (25/25)",
327
+ "srcName": "Gundam Build Fighters Try",
328
+ "diff": null
329
+ },
330
+ {
331
+ "name": "Gundam Build Fighters Try: Island Wars",
332
+ "page": "anime/multiversal/gundam/gundam-build-fighters/island-wars/",
333
+ "local": 0,
334
+ "src": "FULL",
335
+ "srcName": "Gundam Build Fighters Try: Island Wars",
336
+ "diff": null
337
+ },
338
+ {
339
+ "name": "Gundam Build Fighters Specials",
340
+ "page": "anime/multiversal/gundam/gundam-build-fighters/specials/",
341
+ "local": 3,
342
+ "src": "Hoàn tất (3/3)",
343
+ "srcName": "Gundam Build Fighters Specials",
344
+ "diff": null
345
+ },
346
+ {
347
+ "name": "Gundam Build Fighters Battlogue",
348
+ "page": "anime/multiversal/gundam/gundam-build-fighters/battlogue/",
349
+ "local": 5,
350
+ "src": "Hoàn tất (5/5)",
351
+ "srcName": "Gundam Build Fighters: Battlogue",
352
+ "diff": null
353
+ },
354
+ {
355
+ "name": "Gundam Build Divers",
356
+ "page": "anime/multiversal/gundam/gundam-build-divers/phan-1/",
357
+ "local": 25,
358
+ "src": "Hoàn tất (25/25)",
359
+ "srcName": "Gundam Build Divers",
360
+ "diff": null
361
+ },
362
+ {
363
+ "name": "Gundam Build Divers Re:Rise",
364
+ "page": "anime/multiversal/gundam/gundam-build-divers/rerise/",
365
+ "local": 13,
366
+ "src": null,
367
+ "srcName": null,
368
+ "diff": null
369
+ },
370
+ {
371
+ "name": "Gundam Build Divers Re:Rise Season 2",
372
+ "page": "anime/multiversal/gundam/gundam-build-divers/rerise-s2/",
373
+ "local": 14,
374
+ "src": null,
375
+ "srcName": null,
376
+ "diff": null
377
+ },
378
+ {
379
+ "name": "Gundam Build Divers Re:Rise OVA",
380
+ "page": "anime/multiversal/gundam/gundam-build-divers/ova/",
381
+ "local": 0,
382
+ "src": null,
383
+ "srcName": null,
384
+ "diff": null
385
+ },
386
+ {
387
+ "name": "Fate/Zero (Phần 1)",
388
+ "page": "anime/multiversal/fate/fate-zero/phan-1/",
389
+ "local": 0,
390
+ "src": null,
391
+ "srcName": null,
392
+ "diff": null
393
+ },
394
+ {
395
+ "name": "Fate/Zero (Phần 2)",
396
+ "page": "anime/multiversal/fate/fate-zero/phan-2/",
397
+ "local": 0,
398
+ "src": null,
399
+ "srcName": null,
400
+ "diff": null
401
+ },
402
+ {
403
+ "name": "Fate/stay night (2006)",
404
+ "page": "anime/multiversal/fate/fate-stay-night/fsn-2006/",
405
+ "local": 0,
406
+ "src": "Hoàn tất (24/24)",
407
+ "srcName": "Fate/stay night",
408
+ "diff": null
409
+ },
410
+ {
411
+ "name": "Fate/stay night: Unlimited Blade Works",
412
+ "page": "anime/multiversal/fate/fate-stay-night/ubw/",
413
+ "local": 0,
414
+ "src": "Hoàn tất (12/12)",
415
+ "srcName": "Fate/stay night: Unlimited Blade Works",
416
+ "diff": null
417
+ },
418
+ {
419
+ "name": "Fate/stay night: UBW (Phần 2)",
420
+ "page": "anime/multiversal/fate/fate-stay-night/ubw-s2/",
421
+ "local": 0,
422
+ "src": "Hoàn tất (13/13)",
423
+ "srcName": "Fate/stay night: Unlimited Blade Works 2nd Season",
424
+ "diff": null
425
+ },
426
+ {
427
+ "name": "Fate/stay night: UBW Sunny Day",
428
+ "page": "anime/multiversal/fate/fate-stay-night/sunny-day/",
429
+ "local": 0,
430
+ "src": null,
431
+ "srcName": null,
432
+ "diff": null
433
+ },
434
+ {
435
+ "name": "Fate/stay night Movie: Unlimited Blade Works",
436
+ "page": "anime/multiversal/fate/fate-stay-night/movie-ubw/",
437
+ "local": 0,
438
+ "src": null,
439
+ "srcName": null,
440
+ "diff": null
441
+ },
442
+ {
443
+ "name": "Heaven\\",
444
+ "page": "anime/multiversal/fate/heavens-feel/movie-1/",
445
+ "local": 0,
446
+ "src": null,
447
+ "srcName": null,
448
+ "diff": null
449
+ },
450
+ {
451
+ "name": "Heaven\\",
452
+ "page": "anime/multiversal/fate/heavens-feel/movie-2/",
453
+ "local": 0,
454
+ "src": null,
455
+ "srcName": null,
456
+ "diff": null
457
+ },
458
+ {
459
+ "name": "Heaven\\",
460
+ "page": "anime/multiversal/fate/heavens-feel/movie-3/",
461
+ "local": 0,
462
+ "src": null,
463
+ "srcName": null,
464
+ "diff": null
465
+ },
466
+ {
467
+ "name": "Fate/kaleid Prisma☆Illya (Phần 1)",
468
+ "page": "anime/multiversal/fate/fate-kaleid/phan-1/",
469
+ "local": 0,
470
+ "src": null,
471
+ "srcName": null,
472
+ "diff": null
473
+ },
474
+ {
475
+ "name": "Fate/kaleid Prisma☆Illya 2wei!",
476
+ "page": "anime/multiversal/fate/fate-kaleid/phan-2/",
477
+ "local": 0,
478
+ "src": "Hoàn tất (10/10)",
479
+ "srcName": "Fate/kaleid liner Prisma☆Illya 2wei!",
480
+ "diff": null
481
+ },
482
+ {
483
+ "name": "Fate/kaleid Prisma☆Illya 2wei Herz!",
484
+ "page": "anime/multiversal/fate/fate-kaleid/phan-3/",
485
+ "local": 0,
486
+ "src": "Hoàn tất (10/10)",
487
+ "srcName": "Fate/kaleid liner Prisma☆Illya 2wei Herz!",
488
+ "diff": null
489
+ },
490
+ {
491
+ "name": "Fate/kaleid Prisma☆Illya 3rei!!",
492
+ "page": "anime/multiversal/fate/fate-kaleid/phan-4/",
493
+ "local": 0,
494
+ "src": null,
495
+ "srcName": null,
496
+ "diff": null
497
+ },
498
+ {
499
+ "name": "Fate/kaleid Prisma☆Illya Movie: Sekka no Chikai",
500
+ "page": "anime/multiversal/fate/fate-kaleid/movie-sekka/",
501
+ "local": 0,
502
+ "src": null,
503
+ "srcName": null,
504
+ "diff": null
505
+ },
506
+ {
507
+ "name": "Fate/kaleid Prisma☆Illya Movie: Licht",
508
+ "page": "anime/multiversal/fate/fate-kaleid/movie-licht/",
509
+ "local": 0,
510
+ "src": null,
511
+ "srcName": null,
512
+ "diff": null
513
+ },
514
+ {
515
+ "name": "Cuộc Chiến Chén Thánh",
516
+ "page": "anime/multiversal/fate/fate-kaleid/cuoc-chien-chen-thanh/",
517
+ "local": 0,
518
+ "src": null,
519
+ "srcName": null,
520
+ "diff": null
521
+ },
522
+ {
523
+ "name": "Fate/Apocrypha",
524
+ "page": "anime/multiversal/fate/fate-apocrypha/",
525
+ "local": 0,
526
+ "src": null,
527
+ "srcName": null,
528
+ "diff": null
529
+ },
530
+ {
531
+ "name": "Fate/Extra: Last Encore",
532
+ "page": "anime/multiversal/fate/fate-extra/last-encore/",
533
+ "local": 0,
534
+ "src": null,
535
+ "srcName": null,
536
+ "diff": null
537
+ },
538
+ {
539
+ "name": "Fate/Extra: Last Encore - Illustrias Tendousetsu",
540
+ "page": "anime/multiversal/fate/fate-extra/illustrias/",
541
+ "local": 0,
542
+ "src": null,
543
+ "srcName": null,
544
+ "diff": null
545
+ },
546
+ {
547
+ "name": "Fate/Grand Order: First Order",
548
+ "page": "anime/multiversal/fate/fate-grand-order/first-order/",
549
+ "local": 0,
550
+ "src": null,
551
+ "srcName": null,
552
+ "diff": null
553
+ },
554
+ {
555
+ "name": "Fate/Grand Order: Moonlight/Lostroom",
556
+ "page": "anime/multiversal/fate/fate-grand-order/moonlight-lostroom/",
557
+ "local": 0,
558
+ "src": "FULL",
559
+ "srcName": "Fate/Grand Order: Moonlight/Lostroom",
560
+ "diff": null
561
+ },
562
+ {
563
+ "name": "Fate/Grand Order: Babylonia",
564
+ "page": "anime/multiversal/fate/fate-grand-order/babylonia/",
565
+ "local": 0,
566
+ "src": null,
567
+ "srcName": null,
568
+ "diff": null
569
+ },
570
+ {
571
+ "name": "Fate/Grand Order: Camelot 1 - Wandering; Agateram",
572
+ "page": "anime/multiversal/fate/fate-grand-order/camelot-1/",
573
+ "local": 0,
574
+ "src": null,
575
+ "srcName": null,
576
+ "diff": null
577
+ },
578
+ {
579
+ "name": "Fate/Grand Order: Camelot 2 - Paladin; Agateram",
580
+ "page": "anime/multiversal/fate/fate-grand-order/camelot-2/",
581
+ "local": 0,
582
+ "src": null,
583
+ "srcName": null,
584
+ "diff": null
585
+ },
586
+ {
587
+ "name": "Fate/Grand Order: Solomon",
588
+ "page": "anime/multiversal/fate/fate-grand-order/solomon/",
589
+ "local": 0,
590
+ "src": "FULL",
591
+ "srcName": "Fate/Grand Order: Shuukyoku Tokuiten - Kani Jikan Shinden Solomon",
592
+ "diff": null
593
+ },
594
+ {
595
+ "name": "Fate/Grand Order: Himuro no Tenchi",
596
+ "page": "anime/multiversal/fate/fate-grand-order/himuro/",
597
+ "local": 0,
598
+ "src": null,
599
+ "srcName": null,
600
+ "diff": null
601
+ },
602
+ {
603
+ "name": "Fate/strange Fake: Whispers of Dawn",
604
+ "page": "anime/multiversal/fate/fate-strange-fake/whispers-of-dawn/",
605
+ "local": 0,
606
+ "src": null,
607
+ "srcName": null,
608
+ "diff": null
609
+ },
610
+ {
611
+ "name": "Fate/strange Fake",
612
+ "page": "anime/multiversal/fate/fate-strange-fake/tv/",
613
+ "local": 0,
614
+ "src": "Hoàn tất (13/13)",
615
+ "srcName": "Fate/strange Fake",
616
+ "diff": null
617
+ },
618
+ {
619
+ "name": "Gundam Build Metaverse",
620
+ "page": "anime/multiversal/gundam/gundam-build-metaverse/",
621
+ "local": 0,
622
+ "src": "Tập 1",
623
+ "srcName": "Gundam Build Metaverse",
624
+ "diff": null
625
+ },
626
+ {
627
+ "name": "JoJo Phần 1",
628
+ "page": "anime/universal/jojo/phan-1/",
629
+ "local": 26,
630
+ "src": null,
631
+ "srcName": null,
632
+ "diff": null
633
+ },
634
+ {
635
+ "name": "JoJo Phần 2",
636
+ "page": "anime/universal/jojo/phan-2/",
637
+ "local": 48,
638
+ "src": "Hoàn tất (48/48)",
639
+ "srcName": "Cuộc phiêu lưu kì lạ của JoJo (Phần 2)",
640
+ "diff": null
641
+ },
642
+ {
643
+ "name": "JoJo Phần 3",
644
+ "page": "anime/universal/jojo/phan-3/",
645
+ "local": 39,
646
+ "src": "FULL",
647
+ "srcName": "Cuộc Phiêu Lưu Kì Lạ Của Jojo: Kim Cương Bất Bại",
648
+ "diff": null
649
+ },
650
+ {
651
+ "name": "JoJo Phần 4",
652
+ "page": "anime/universal/jojo/phan-4/",
653
+ "local": 39,
654
+ "src": "Hoàn tất (39/39)",
655
+ "srcName": "Cuộc phiêu lưu kì lạ của JoJo (Phần 4)",
656
+ "diff": null
657
+ },
658
+ {
659
+ "name": "JoJo Phần 5",
660
+ "page": "anime/universal/jojo/phan-5/",
661
+ "local": 38,
662
+ "src": "Hoàn tất (38/38)",
663
+ "srcName": "Cuộc phiêu lưu kì lạ của JoJo (Phần 5)",
664
+ "diff": null
665
+ },
666
+ {
667
+ "name": "JoJo Steel Ball Run",
668
+ "page": "anime/universal/jojo/steel-ball-run/",
669
+ "local": 0,
670
+ "src": "FULL",
671
+ "srcName": "STEEL BALL RUN: Cuộc phiêu lưu kì lạ của JoJo",
672
+ "diff": null
673
+ },
674
+ {
675
+ "name": "Bocchi the Rock!",
676
+ "page": "anime/universal/bocchi-the-rock/phan-1/",
677
+ "local": 12,
678
+ "src": "Hoàn tất (12/12)",
679
+ "srcName": "Tay rock Bocchi!",
680
+ "diff": null
681
+ },
682
+ {
683
+ "name": "Bocchi the Rock! Movie",
684
+ "page": "anime/universal/bocchi-the-rock/movie/",
685
+ "local": 1,
686
+ "src": "Hoàn tất (2/2)",
687
+ "srcName": "Bocchi the Rock! Movie",
688
+ "diff": null
689
+ },
690
+ {
691
+ "name": "Death Note",
692
+ "page": "anime/universal/death-note/",
693
+ "local": 37,
694
+ "src": "Hoàn tất (37/37)",
695
+ "srcName": "Quyển Sổ Sinh Mệnh",
696
+ "diff": null
697
+ },
698
+ {
699
+ "name": "Kết Hôn Với Người Mình Ghét Nhất Lớp",
700
+ "page": "anime/universal/class-no-daikirai/",
701
+ "local": 12,
702
+ "src": "Hoàn tất (12/12)",
703
+ "srcName": "Class no Daikirai na Joshi to Kekkon suru Koto ni Natta.",
704
+ "diff": null
705
+ },
706
+ {
707
+ "name": "Công Chúa Ma Cà Rồng Tầm Thường",
708
+ "page": "anime/universal/cong-chua-ma-ca-rong/",
709
+ "local": 12,
710
+ "src": "Hoàn tất (12/12)",
711
+ "srcName": "Công Chúa Ma Cà Rồng Tầm Thường",
712
+ "diff": null
713
+ },
714
+ {
715
+ "name": "Vùng Đất Linh Hồn",
716
+ "page": "anime/universal/vung-dat-linh-hon/",
717
+ "local": 0,
718
+ "src": null,
719
+ "srcName": null,
720
+ "diff": null
721
+ },
722
+ {
723
+ "name": "Lời Thì Thầm Của Trái Tim",
724
+ "page": "anime/universal/loi-thi-tham-cua-trai-tim/",
725
+ "local": 0,
726
+ "src": "FULL",
727
+ "srcName": "Lời Thì Thầm Của Trái Tim",
728
+ "diff": null
729
+ },
730
+ {
731
+ "name": "Lâu Đài Di Động Của Howl",
732
+ "page": "anime/universal/lau-dai-di-dong-cua-howl/",
733
+ "local": 0,
734
+ "src": "FULL",
735
+ "srcName": "Lâu Đài Di Động Của Howl",
736
+ "diff": null
737
+ },
738
+ {
739
+ "name": "Shikimori-san",
740
+ "page": "anime/universal/shikimori-san/",
741
+ "local": 12,
742
+ "src": "Hoàn tất (14/14)",
743
+ "srcName": "Kawaii dake ja Nai Shikimori-san",
744
+ "diff": null
745
+ },
746
+ {
747
+ "name": "Dr. Stone (Phần 1)",
748
+ "page": "anime/universal/dr-stone/phan-1/",
749
+ "local": 24,
750
+ "src": "Hoàn tất (24/24)",
751
+ "srcName": "Tiến sĩ Đá (Phần 1)",
752
+ "diff": null
753
+ },
754
+ {
755
+ "name": "Dr. Stone (Phần 2)",
756
+ "page": "anime/universal/dr-stone/phan-2/",
757
+ "local": 11,
758
+ "src": null,
759
+ "srcName": null,
760
+ "diff": null
761
+ },
762
+ {
763
+ "name": "Dr. Stone (Phần 3)",
764
+ "page": "anime/universal/dr-stone/phan-3/",
765
+ "local": 22,
766
+ "src": null,
767
+ "srcName": null,
768
+ "diff": null
769
+ },
770
+ {
771
+ "name": "Dr. Stone (Phần 4)",
772
+ "page": "anime/universal/dr-stone/phan-4/",
773
+ "local": 37,
774
+ "src": null,
775
+ "srcName": null,
776
+ "diff": null
777
+ },
778
+ {
779
+ "name": "Kimi ga Shinu made Koi wo Shitai",
780
+ "page": "anime/universal/kimi-ga-shinu-made-koi-wo-shitai/",
781
+ "local": 6,
782
+ "src": "Tập 6",
783
+ "srcName": "Kimi ga Shinu made Koi wo Shitai",
784
+ "diff": null
785
+ },
786
+ {
787
+ "name": "Oregairu OVA",
788
+ "page": "anime/universal/oregairu/ova/",
789
+ "local": 0,
790
+ "src": "FULL",
791
+ "srcName": "Yahari Ore no Seishun Love Comedy wa Machigatteiru. OVA",
792
+ "diff": null
793
+ },
794
+ {
795
+ "name": "Oregairu (Phần 1)",
796
+ "page": "anime/universal/oregairu/phan-1/",
797
+ "local": 13,
798
+ "src": "Hoàn tất (13/13)",
799
+ "srcName": "Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku",
800
+ "diff": null
801
+ },
802
+ {
803
+ "name": "Oregairu (Phần 2) - Zoku",
804
+ "page": "anime/universal/oregairu/phan-2/",
805
+ "local": 13,
806
+ "src": "Hoàn tất (13/13)",
807
+ "srcName": "Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku",
808
+ "diff": null
809
+ },
810
+ {
811
+ "name": "Oregairu Zoku OVA",
812
+ "page": "anime/universal/oregairu/zoku-ova/",
813
+ "local": 0,
814
+ "src": "FULL",
815
+ "srcName": "Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku OVA",
816
+ "diff": null
817
+ },
818
+ {
819
+ "name": "Oregairu (Phần 3) - Kan",
820
+ "page": "anime/universal/oregairu/phan-3/",
821
+ "local": 12,
822
+ "src": "Hoàn tất (12/12)",
823
+ "srcName": "Yahari Ore no Seishun Love Comedy wa Machigatteiru. Kan",
824
+ "diff": null
825
+ },
826
+ {
827
+ "name": "Oregairu Kan OVA",
828
+ "page": "anime/universal/oregairu/kan-ova/",
829
+ "local": 0,
830
+ "src": "FULL",
831
+ "srcName": "Yahari Ore no Seishun Love Comedy wa Machigatteiru. Kan OVA",
832
+ "diff": null
833
+ },
834
+ {
835
+ "name": "Classroom of the Elite (Phần 1)",
836
+ "page": "anime/universal/classroom-of-the-elite/phan-1/",
837
+ "local": 12,
838
+ "src": "Hoàn tất (13/13)",
839
+ "srcName": "Chào Mừng Đến Lớp Học Đề Cao Thực Lực (Phần 2)",
840
+ "diff": null
841
+ },
842
+ {
843
+ "name": "Classroom of the Elite (Phần 2)",
844
+ "page": "anime/universal/classroom-of-the-elite/phan-2/",
845
+ "local": 13,
846
+ "src": "Hoàn tất (13/13)",
847
+ "srcName": "Chào Mừng Đến Lớp Học Đề Cao Thực Lực (Phần 2)",
848
+ "diff": null
849
+ },
850
+ {
851
+ "name": "Classroom of the Elite (Phần 3)",
852
+ "page": "anime/universal/classroom-of-the-elite/phan-3/",
853
+ "local": 13,
854
+ "src": null,
855
+ "srcName": null,
856
+ "diff": null
857
+ },
858
+ {
859
+ "name": "Classroom of the Elite (Phần 4)",
860
+ "page": "anime/universal/classroom-of-the-elite/phan-4/",
861
+ "local": 16,
862
+ "src": null,
863
+ "srcName": null,
864
+ "diff": null
865
+ },
866
+ {
867
+ "name": "Bukiyou na Senpai",
868
+ "page": "anime/universal/bukiyou-na-senpai/",
869
+ "local": 12,
870
+ "src": "Hoàn tất (12/12)",
871
+ "srcName": "Bukiyou na Senpai.",
872
+ "diff": null
873
+ },
874
+ {
875
+ "name": "Tomodachi no Imouto",
876
+ "page": "anime/universal/tomodachi-no-imouto/",
877
+ "local": 12,
878
+ "src": "Hoàn tất (12/12)",
879
+ "srcName": "Tomodachi no Imouto ga Ore ni dake Uzai",
880
+ "diff": null
881
+ },
882
+ {
883
+ "name": "Class de 2-banme ni Kawaii",
884
+ "page": "anime/universal/class-de-2-banme/",
885
+ "local": 12,
886
+ "src": "Hoàn tất (12/12)",
887
+ "srcName": "Class de 2-banme ni Kawaii Onnanoko to Tomodachi ni Natta",
888
+ "diff": null
889
+ },
890
+ {
891
+ "name": "Kẻ Ngây Thơ, Người Từng Trải",
892
+ "page": "anime/universal/ke-ngay-tho/",
893
+ "local": 12,
894
+ "src": "Hoàn tất (12/12)",
895
+ "srcName": "Kẻ Ngây Thơ, Người Từng Trải Và Câu Chuyện Tình Của Chúng Ta",
896
+ "diff": null
897
+ },
898
+ {
899
+ "name": "Cô Bạn Tôi Thầm Thích Lại Quên Mang Kính Rồi",
900
+ "page": "anime/universal/co-ban-toi-tham-thich/",
901
+ "local": 13,
902
+ "src": "Hoàn tất (13/13)",
903
+ "srcName": "Cô Bạn Tôi Thầm Thích Lại Quên Mang Kính Rồi",
904
+ "diff": null
905
+ },
906
+ {
907
+ "name": "Shigatsu wa Kimi no Uso",
908
+ "page": "anime/universal/shigatsu-wa-kimi-no-uso/",
909
+ "local": 22,
910
+ "src": "Hoàn tất (22/22)",
911
+ "srcName": "Shigatsu wa Kimi no Uso",
912
+ "diff": null
913
+ },
914
+ {
915
+ "name": "Kanojo mo Kanojo (Phần 1)",
916
+ "page": "anime/universal/kanojo-mo-kanojo/phan-1/",
917
+ "local": 12,
918
+ "src": null,
919
+ "srcName": null,
920
+ "diff": null
921
+ },
922
+ {
923
+ "name": "Kanojo mo Kanojo (Phần 2)",
924
+ "page": "anime/universal/kanojo-mo-kanojo/phan-2/",
925
+ "local": 12,
926
+ "src": null,
927
+ "srcName": null,
928
+ "diff": null
929
+ },
930
+ {
931
+ "name": "BokuBen OVA",
932
+ "page": "anime/universal/bokutachi-wa-benkyou-ga-dekinai/ova/",
933
+ "local": 2,
934
+ "src": "Hoàn tất (2/2)",
935
+ "srcName": "Bokutachi wa Benkyou ga Dekinai OVA",
936
+ "diff": null
937
+ },
938
+ {
939
+ "name": "BokuBen (Phần 1)",
940
+ "page": "anime/universal/bokutachi-wa-benkyou-ga-dekinai/phan-1/",
941
+ "local": 13,
942
+ "src": null,
943
+ "srcName": null,
944
+ "diff": null
945
+ },
946
+ {
947
+ "name": "BokuBen (Phần 2)",
948
+ "page": "anime/universal/bokutachi-wa-benkyou-ga-dekinai/phan-2/",
949
+ "local": 13,
950
+ "src": null,
951
+ "srcName": null,
952
+ "diff": null
953
+ },
954
+ {
955
+ "name": "Umayon (Spin-off)",
956
+ "page": "anime/universal/uma-musume/umayon/",
957
+ "local": 12,
958
+ "src": null,
959
+ "srcName": null,
960
+ "diff": null
961
+ },
962
+ {
963
+ "name": "Uma Musume (Phần 1)",
964
+ "page": "anime/universal/uma-musume/phan-1/",
965
+ "local": 13,
966
+ "src": null,
967
+ "srcName": null,
968
+ "diff": null
969
+ },
970
+ {
971
+ "name": "Uma Musume: BNW no Chikai",
972
+ "page": "anime/universal/uma-musume/bnw-no-chikai/",
973
+ "local": 3,
974
+ "src": "Hoàn tất (3/3)",
975
+ "srcName": "Uma Musume: Pretty Derby - BNW no Chikai",
976
+ "diff": null
977
+ },
978
+ {
979
+ "name": "Uma Musume (Phần 2)",
980
+ "page": "anime/universal/uma-musume/phan-2/",
981
+ "local": 13,
982
+ "src": null,
983
+ "srcName": null,
984
+ "diff": null
985
+ },
986
+ {
987
+ "name": "Uma Musume (Phần 3)",
988
+ "page": "anime/universal/uma-musume/phan-3/",
989
+ "local": 13,
990
+ "src": null,
991
+ "srcName": null,
992
+ "diff": null
993
+ },
994
+ {
995
+ "name": "Uma Musume Movie: Shin Jidai",
996
+ "page": "anime/universal/uma-musume/movie/",
997
+ "local": 0,
998
+ "src": "FULL",
999
+ "srcName": "Uma Musume: Pretty Derby - Shin Jidai no Tobira",
1000
+ "diff": null
1001
+ },
1002
+ {
1003
+ "name": "Uma Musume: Road to the Top",
1004
+ "page": "anime/universal/uma-musume/road-to-the-top/",
1005
+ "local": 4,
1006
+ "src": "Hoàn tất (4/4)",
1007
+ "srcName": "Uma Musume: Pretty Derby - Road to the Top",
1008
+ "diff": null
1009
+ },
1010
+ {
1011
+ "name": "Uma Musume: Cinderella Gray",
1012
+ "page": "anime/universal/uma-musume/cinderella-gray/",
1013
+ "local": 23,
1014
+ "src": "Hoàn tất (23/23)",
1015
+ "srcName": "Uma Musume: Cinderella Gray",
1016
+ "diff": null
1017
+ },
1018
+ {
1019
+ "name": "Record of Ragnarok (P1)",
1020
+ "page": "anime/universal/record-of-ragnarok/phan-1/",
1021
+ "local": 12,
1022
+ "src": null,
1023
+ "srcName": null,
1024
+ "diff": null
1025
+ },
1026
+ {
1027
+ "name": "Record of Ragnarok (P2)",
1028
+ "page": "anime/universal/record-of-ragnarok/phan-2/",
1029
+ "local": 15,
1030
+ "src": null,
1031
+ "srcName": null,
1032
+ "diff": null
1033
+ },
1034
+ {
1035
+ "name": "Record of Ragnarok (P3)",
1036
+ "page": "anime/universal/record-of-ragnarok/phan-3/",
1037
+ "local": 15,
1038
+ "src": null,
1039
+ "srcName": null,
1040
+ "diff": null
1041
+ },
1042
+ {
1043
+ "name": "Eminence in Shadow (P1)",
1044
+ "page": "anime/universal/emine-in-shadow/phan-1/",
1045
+ "local": 20,
1046
+ "src": null,
1047
+ "srcName": null,
1048
+ "diff": null
1049
+ },
1050
+ {
1051
+ "name": "Eminence in Shadow (P2)",
1052
+ "page": "anime/universal/emine-in-shadow/phan-2/",
1053
+ "local": 12,
1054
+ "src": null,
1055
+ "srcName": null,
1056
+ "diff": null
1057
+ },
1058
+ {
1059
+ "name": "Bảo Mẫu Bí Mật Của Tiểu Thư",
1060
+ "page": "anime/universal/bao-mau-bi-mat-cua-tieu-thu/",
1061
+ "local": 0,
1062
+ "src": "Tập 7",
1063
+ "srcName": "Bảo Mẫu Bí Mật Của Tiểu Thư",
1064
+ "diff": null
1065
+ },
1066
+ {
1067
+ "name": "Thiếu Nữ Quái Vật Caramelise",
1068
+ "page": "anime/universal/thieu-nu-quai-vat-caramelise/",
1069
+ "local": 0,
1070
+ "src": "Tập 7",
1071
+ "srcName": "Thiếu Nữ Quái Vật Caramelise",
1072
+ "diff": null
1073
+ },
1074
+ {
1075
+ "name": "Nhật Ký Thường Ngày Của Tiên Vương (Phần 1)",
1076
+ "page": "anime/universal/nhat-ky-tien-vuong/phan-1/",
1077
+ "local": 0,
1078
+ "src": "Hoàn tất (12/12)",
1079
+ "srcName": "Nhật Ký Thường Ngày Của Tiên Vương (Phần 3)",
1080
+ "diff": null
1081
+ },
1082
+ {
1083
+ "name": "Nhật Ký Thường Ngày Của Tiên Vương (Phần 2)",
1084
+ "page": "anime/universal/nhat-ky-tien-vuong/phan-2/",
1085
+ "local": 0,
1086
+ "src": null,
1087
+ "srcName": null,
1088
+ "diff": null
1089
+ },
1090
+ {
1091
+ "name": "Nhật Ký Thường Ngày Của Tiên Vương (Phần 3)",
1092
+ "page": "anime/universal/nhat-ky-tien-vuong/phan-3/",
1093
+ "local": 0,
1094
+ "src": null,
1095
+ "srcName": null,
1096
+ "diff": null
1097
+ },
1098
+ {
1099
+ "name": "Nhật Ký Thường Ngày Của Tiên Vương (Phần 4)",
1100
+ "page": "anime/universal/nhat-ky-tien-vuong/phan-4/",
1101
+ "local": 0,
1102
+ "src": null,
1103
+ "srcName": null,
1104
+ "diff": null
1105
+ },
1106
+ {
1107
+ "name": "Nhật Ký Thường Ngày Của Tiên Vương (Phần 5)",
1108
+ "page": "anime/universal/nhat-ky-tien-vuong/phan-5/",
1109
+ "local": 0,
1110
+ "src": null,
1111
+ "srcName": null,
1112
+ "diff": null
1113
+ },
1114
+ {
1115
+ "name": "Ore wo Suki nano wa Omae dake ka yo",
1116
+ "page": "anime/universal/ore-wo-suki/phan-1/",
1117
+ "local": 0,
1118
+ "src": "Hoàn tất (12/12)",
1119
+ "srcName": "Ore wo Suki nano wa Omae dake ka yo",
1120
+ "diff": null
1121
+ },
1122
+ {
1123
+ "name": "Ore wo Suki Movie: Oretachi no Game Set",
1124
+ "page": "anime/universal/ore-wo-suki/movie/",
1125
+ "local": 0,
1126
+ "src": null,
1127
+ "srcName": null,
1128
+ "diff": null
1129
+ },
1130
+ {
1131
+ "name": "Boku no Kokoro no Yabai Yatsu (Phần 1)",
1132
+ "page": "anime/universal/boku-yabai/phan-1/",
1133
+ "local": 0,
1134
+ "src": "Hoàn tất (12/12)",
1135
+ "srcName": "Boku no Kokoro no Yabai Yatsu",
1136
+ "diff": null
1137
+ },
1138
+ {
1139
+ "name": "Boku no Kokoro no Yabai Yatsu (Phần 2)",
1140
+ "page": "anime/universal/boku-yabai/phan-2/",
1141
+ "local": 0,
1142
+ "src": null,
1143
+ "srcName": null,
1144
+ "diff": null
1145
+ },
1146
+ {
1147
+ "name": "Thất Nghiệp Chuyển Sinh (Phần 1)",
1148
+ "page": "anime/universal/that-nghiep-chuyen-sinh/phan-1/",
1149
+ "local": 0,
1150
+ "src": "Hoàn tất (23/23)",
1151
+ "srcName": "Thất Nghiệp Chuyển Sinh (Phần 1)",
1152
+ "diff": null
1153
+ },
1154
+ {
1155
+ "name": "Thất Nghiệp Chuyển Sinh (Phần 2)",
1156
+ "page": "anime/universal/that-nghiep-chuyen-sinh/phan-2/",
1157
+ "local": 0,
1158
+ "src": "Hoàn tất (25/25)",
1159
+ "srcName": "Thất Nghiệp Chuyển Sinh (Phần 2)",
1160
+ "diff": null
1161
+ },
1162
+ {
1163
+ "name": "Thất Nghiệp Chuyển Sinh (Phần 3)",
1164
+ "page": "anime/universal/that-nghiep-chuyen-sinh/phan-3/",
1165
+ "local": 0,
1166
+ "src": "Tập 8",
1167
+ "srcName": "Thất Nghiệp Chuyển Sinh (Phần 3)",
1168
+ "diff": null
1169
+ },
1170
+ {
1171
+ "name": "Dáng Hình Thanh Âm",
1172
+ "page": "anime/universal/dang-hinh-thanh-am/",
1173
+ "local": 0,
1174
+ "src": "FULL",
1175
+ "srcName": "Dáng Hình Thanh Âm",
1176
+ "diff": null
1177
+ },
1178
+ {
1179
+ "name": "Sasayaku You ni Koi wo Utau",
1180
+ "page": "anime/universal/sasayaku/",
1181
+ "local": 0,
1182
+ "src": "Hoàn tất (12/12)",
1183
+ "srcName": "Sasayaku You ni Koi wo Utau",
1184
+ "diff": null
1185
+ },
1186
+ {
1187
+ "name": "Amagami-san Chi no Enmusubi",
1188
+ "page": "anime/universal/amagami-san/",
1189
+ "local": 0,
1190
+ "src": "Hoàn tất (24/24)",
1191
+ "srcName": "Amagami-san Chi no Enmusubi",
1192
+ "diff": null
1193
+ },
1194
+ {
1195
+ "name": "Senpai Của Tôi Thật Phiền Phức",
1196
+ "page": "anime/universal/senpai-ga-uzai/",
1197
+ "local": 0,
1198
+ "src": "Hoàn tất (12/12)",
1199
+ "srcName": "Senpai ga Uzai Kouhai no Hanashi",
1200
+ "diff": null
1201
+ },
1202
+ {
1203
+ "name": "Mirai Nikki",
1204
+ "page": "anime/universal/mirai-nikki/",
1205
+ "local": 0,
1206
+ "src": "Hoàn tất (26/26)",
1207
+ "srcName": "Mirai Nikki",
1208
+ "diff": null
1209
+ },
1210
+ {
1211
+ "name": "Mirai Nikki: Redial",
1212
+ "page": "anime/universal/mirai-nikki-redial/",
1213
+ "local": 0,
1214
+ "src": "FULL",
1215
+ "srcName": "Mirai Nikki: Redial",
1216
+ "diff": null
1217
+ },
1218
+ {
1219
+ "name": "Toumei na Yoru ni Kakeru Kimi to, Me ni Mienai Koi wo Shita.",
1220
+ "page": "anime/universal/toumei-na-yoru-ni-kakeru-kimi-to-me-ni-mienai-koi-wo-shita/",
1221
+ "local": 0,
1222
+ "src": "Tập 7",
1223
+ "srcName": "Toumei na Yoru ni Kakeru Kimi to, Me ni Mienai Koi wo Shita.",
1224
+ "diff": null
1225
+ },
1226
+ {
1227
+ "name": "Oh Boy, Was I Wrong About Her",
1228
+ "page": "anime/universal/oh-boy-was-i-wrong-about-her/",
1229
+ "local": 0,
1230
+ "src": "Tập 7",
1231
+ "srcName": "Oh Boy, Was I Wrong About Her",
1232
+ "diff": null
1233
+ },
1234
+ {
1235
+ "name": "Kiếm Cơm Bằng Trò Chơi Sinh Tử",
1236
+ "page": "anime/universal/kiem-com-bang-tro-choi-sinh-tu/",
1237
+ "local": 0,
1238
+ "src": "Hoàn tất (11/11)",
1239
+ "srcName": "Kiếm Cơm Bằng Trò Chơi Sinh Tử",
1240
+ "diff": null
1241
+ },
1242
+ {
1243
+ "name": "Hoa thơm kiêu hãnh",
1244
+ "page": "anime/universal/hoa-thom-kieu-hanh/",
1245
+ "local": 0,
1246
+ "src": "Hoàn tất (13/13)",
1247
+ "srcName": "Hoa thơm kiêu hãnh",
1248
+ "diff": null
1249
+ },
1250
+ {
1251
+ "name": "Tôi Sẽ Đỡ Hết Mọi Thứ: Người Mạnh Nhất Thế Giới Với Hiểu Lầm Ngược Đời Muốn Làm Mạo Hiểm Giả",
1252
+ "page": "anime/universal/toi-se-do-het-moi-thu-nguoi-manh-nhat-the-gioi-voi-hieu-lam-nguoc-doi-muon-lam-mao-hiem-gia/",
1253
+ "local": 0,
1254
+ "src": "Hoàn tất (12/12)",
1255
+ "srcName": "Tôi Sẽ Đỡ Hết Mọi Thứ: Người Mạnh Nhất Thế Giới Với Hiểu Lầm Ngược Đời Muốn Làm Mạo Hiểm Giả",
1256
+ "diff": null
1257
+ },
1258
+ {
1259
+ "name": "Tình Bạn Trai Gái Liệu Có Bền Lâu?",
1260
+ "page": "anime/universal/tinh-ban-trai-gai-lieu-co-ben-lau/",
1261
+ "local": 0,
1262
+ "src": "Hoàn tất (12/12)",
1263
+ "srcName": "Tình Bạn Trai Gái Liệu Có Bền Lâu?",
1264
+ "diff": null
1265
+ },
1266
+ {
1267
+ "name": "Chuyển Sinh Thành Đệ Thất Hoàng Tử, Tôi Quyết Định Trau Dồi Ma Thuật (Phần 1)",
1268
+ "page": "anime/universal/chuyen-sinh-thanh-de-that-hoang-tu-toi-quyet-dinh-trau-doi-ma-thuat-phan-1/",
1269
+ "local": 0,
1270
+ "src": "Hoàn tất (12/12)",
1271
+ "srcName": "Chuyển Sinh Thành Đệ Thất Hoàng Tử, Tôi Quyết Định Trau Dồi Ma Thuật (Phần 1)",
1272
+ "diff": null
1273
+ },
1274
+ {
1275
+ "name": "Chuyển Sinh Thành Đệ Thất Hoàng Tử, Tôi Quyết Định Trau Dồi Ma Thuật (Phần 2)",
1276
+ "page": "anime/universal/chuyen-sinh-thanh-de-that-hoang-tu-toi-quyet-dinh-trau-doi-ma-thuat-phan-2/",
1277
+ "local": 0,
1278
+ "src": "Hoàn tất (12/12)",
1279
+ "srcName": "Chuyển Sinh Thành Đệ Thất Hoàng Tử, Tôi Quyết Định Trau Dồi Ma Thuật (Phần 2)",
1280
+ "diff": null
1281
+ },
1282
+ {
1283
+ "name": "Nguyệt Đạo Dị Giới (Phần 1)",
1284
+ "page": "anime/universal/nguyet-dao-di-gioi-phan-1/",
1285
+ "local": 0,
1286
+ "src": "Hoàn tất (12/12)",
1287
+ "srcName": "Nguyệt Đạo Dị Giới (Phần 1)",
1288
+ "diff": null
1289
+ },
1290
+ {
1291
+ "name": "Nguyệt Đạo Dị Giới (Phần 2)",
1292
+ "page": "anime/universal/nguyet-dao-di-gioi-phan-2/",
1293
+ "local": 0,
1294
+ "src": "Hoàn tất (25/25)",
1295
+ "srcName": "Nguyệt Đạo Dị Giới (Phần 2)",
1296
+ "diff": null
1297
+ },
1298
+ {
1299
+ "name": "Mashle",
1300
+ "page": "anime/universal/mashle/",
1301
+ "local": 0,
1302
+ "src": "Hoàn tất (12/12)",
1303
+ "srcName": "Mashle",
1304
+ "diff": null
1305
+ },
1306
+ {
1307
+ "name": "Mashle 2nd Season",
1308
+ "page": "anime/universal/mashle-2nd-season/",
1309
+ "local": 0,
1310
+ "src": "Hoàn tất (12/12)",
1311
+ "srcName": "Mashle 2nd Season",
1312
+ "diff": null
1313
+ }
1314
+ ]
scan_missing_episodes.json ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "Lời Thì Thầm Của Trái Tim",
4
+ "page": "anime/universal/loi-thi-tham-cua-trai-tim/",
5
+ "local": 1,
6
+ "src": 2,
7
+ "missing": [
8
+ {
9
+ "name": "Full",
10
+ "slug": "tap-full",
11
+ "embed": "https://embed14.streamc.xyz/embed.php?hash=d1f64f6d09c52e3173de404da54103bf"
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ "name": "Lâu Đài Di Động Của Howl",
17
+ "page": "anime/universal/lau-dai-di-dong-cua-howl/",
18
+ "local": 1,
19
+ "src": 2,
20
+ "missing": [
21
+ {
22
+ "name": "Full",
23
+ "slug": "tap-full",
24
+ "embed": "https://embed14.streamc.xyz/embed.php?hash=4c6515b6c3fbb1ddb17170807501dbc7"
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "name": "Classroom of the Elite (Phần 1)",
30
+ "page": "anime/universal/classroom-of-the-elite/phan-1/",
31
+ "local": 12,
32
+ "src": 13,
33
+ "missing": [
34
+ {
35
+ "name": "13",
36
+ "slug": "tap-13",
37
+ "embed": "https://embed13.streamc.xyz/embed.php?hash=581db20d7f51959c1bdaa933fb442cfa"
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "name": "Bảo Mẫu Bí Mật Của Tiểu Thư",
43
+ "page": "anime/universal/bao-mau-bi-mat-cua-tieu-thu/",
44
+ "local": 6,
45
+ "src": 7,
46
+ "missing": [
47
+ {
48
+ "name": "7",
49
+ "slug": "tap-7",
50
+ "embed": "https://embed14.streamc.xyz/embed.php?hash=60226eae761a770d80b248a4d13dd127"
51
+ }
52
+ ]
53
+ },
54
+ {
55
+ "name": "Thiếu Nữ Quái Vật Caramelise",
56
+ "page": "anime/universal/thieu-nu-quai-vat-caramelise/",
57
+ "local": 6,
58
+ "src": 7,
59
+ "missing": [
60
+ {
61
+ "name": "7",
62
+ "slug": "tap-7",
63
+ "embed": "https://embed18.streamc.xyz/embed.php?hash=092bb7e9d6b02aa4f8e8b4392e616339"
64
+ }
65
+ ]
66
+ },
67
+ {
68
+ "name": "Thất Nghiệp Chuyển Sinh (Phần 3)",
69
+ "page": "anime/universal/that-nghiep-chuyen-sinh/phan-3/",
70
+ "local": 7,
71
+ "src": 8,
72
+ "missing": [
73
+ {
74
+ "name": "8",
75
+ "slug": "tap-8",
76
+ "embed": "https://embed1.streamc.xyz/embed.php?hash=2498fe2437b0fd5bd0500bc22ff7b6b1"
77
+ }
78
+ ]
79
+ },
80
+ {
81
+ "name": "Toumei na Yoru ni Kakeru Kimi to, Me ni Mienai Koi wo Shita.",
82
+ "page": "anime/universal/toumei-na-yoru-ni-kakeru-kimi-to-me-ni-mienai-koi-wo-shita/",
83
+ "local": 6,
84
+ "src": 7,
85
+ "missing": [
86
+ {
87
+ "name": "7",
88
+ "slug": "tap-7",
89
+ "embed": "https://embed2.streamc.xyz/embed.php?hash=7bc1401d400ceefc0c01f0940fb95eb1"
90
+ }
91
+ ]
92
+ },
93
+ {
94
+ "name": "Oh Boy, Was I Wrong About Her",
95
+ "page": "anime/universal/oh-boy-was-i-wrong-about-her/",
96
+ "local": 6,
97
+ "src": 7,
98
+ "missing": [
99
+ {
100
+ "name": "7",
101
+ "slug": "tap-7",
102
+ "embed": "https://embed12.streamc.xyz/embed.php?hash=11cbc5514e2bdf1c4b5f7ecfc322033a"
103
+ }
104
+ ]
105
+ },
106
+ {
107
+ "name": "Chuyển Sinh Thành Đệ Thất Hoàng Tử, Tôi Quyết Định Trau Dồi Ma Thuật (Phần 2)",
108
+ "page": "anime/universal/chuyen-sinh-thanh-de-that-hoang-tu-toi-quyet-dinh-trau-doi-ma-thuat-phan-2/",
109
+ "local": 12,
110
+ "src": 14,
111
+ "missing": [
112
+ {
113
+ "name": "11",
114
+ "slug": "tap-11",
115
+ "embed": "https://embed13.streamc.xyz/embed.php?hash=ad8d259c7d0d55959461a92e51d5d224"
116
+ },
117
+ {
118
+ "name": "12",
119
+ "slug": "tap-12",
120
+ "embed": "https://embed11.streamc.xyz/embed.php?hash=879224235c38bd2fe533db3be3243b0f"
121
+ }
122
+ ]
123
+ }
124
+ ]
scan_multi_server.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "Lời Thì Thầm Của Trái Tim",
4
+ "page": "anime/universal/loi-thi-tham-cua-trai-tim/",
5
+ "servers": [
6
+ {
7
+ "name": "Vietsub #1",
8
+ "count": 1,
9
+ "items": [
10
+ {
11
+ "name": "FULL",
12
+ "embed": "https://embed12.streamc.xyz/embed.php?hash=159da7b3838872aec76fe20e24148967"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "name": "Lồng Tiếng #1",
18
+ "count": 1,
19
+ "items": [
20
+ {
21
+ "name": "Full",
22
+ "embed": "https://embed14.streamc.xyz/embed.php?hash=d1f64f6d09c52e3173de404da54103bf"
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "name": "Lâu Đài Di Động Của Howl",
30
+ "page": "anime/universal/lau-dai-di-dong-cua-howl/",
31
+ "servers": [
32
+ {
33
+ "name": "Vietsub #1",
34
+ "count": 1,
35
+ "items": [
36
+ {
37
+ "name": "Full",
38
+ "embed": "https://embed13.streamc.xyz/embed.php?hash=b6bd17a0e4c3fc30931c6f9bc4d2e64e"
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "name": "Lồng Tiếng #1",
44
+ "count": 1,
45
+ "items": [
46
+ {
47
+ "name": "Full",
48
+ "embed": "https://embed14.streamc.xyz/embed.php?hash=4c6515b6c3fbb1ddb17170807501dbc7"
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }
54
+ ]
signup-loginsystem.js ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function (window) {
2
+ 'use strict';
3
+
4
+ var API_BASE = localStorage.getItem('anivietdb-auth-api') || 'https://sginup-loginsystem.npgia-himlam.workers.dev/';
5
+
6
+ var MEM = {};
7
+ var STORE = 'localStorage';
8
+ try {
9
+ if (localStorage.getItem('anivietdb-token')) MEM.token = localStorage.getItem('anivietdb-token');
10
+ if (localStorage.getItem('anivietdb-user')) MEM.user = JSON.parse(localStorage.getItem('anivietdb-user'));
11
+ } catch (e) { /* localStorage might be blocked */ }
12
+ if (!MEM.token) {
13
+ try {
14
+ if (sessionStorage.getItem('anivietdb-token')) MEM.token = sessionStorage.getItem('anivietdb-token');
15
+ if (sessionStorage.getItem('anivietdb-user')) MEM.user = JSON.parse(sessionStorage.getItem('anivietdb-user'));
16
+ } catch (e) { /* ignore */ }
17
+ }
18
+
19
+ function save() {
20
+ try {
21
+ if (MEM.token) localStorage.setItem('anivietdb-token', MEM.token);
22
+ else localStorage.removeItem('anivietdb-token');
23
+ if (MEM.user) localStorage.setItem('anivietdb-user', JSON.stringify(MEM.user));
24
+ else localStorage.removeItem('anivietdb-user');
25
+ } catch (e) {
26
+ try {
27
+ if (MEM.token) sessionStorage.setItem('anivietdb-token', MEM.token);
28
+ else sessionStorage.removeItem('anivietdb-token');
29
+ if (MEM.user) sessionStorage.setItem('anivietdb-user', JSON.stringify(MEM.user));
30
+ else sessionStorage.removeItem('anivietdb-user');
31
+ } catch (e2) { /* ignore */ }
32
+ }
33
+ }
34
+
35
+ function setSession(data) {
36
+ MEM.token = data && data.token ? data.token : null;
37
+ MEM.user = data && data.user ? data.user : null;
38
+ save();
39
+ }
40
+
41
+ function clearSession() {
42
+ MEM.token = null;
43
+ MEM.user = null;
44
+ save();
45
+ }
46
+
47
+ var COOLDOWN_MS = 10 * 1000;
48
+ var COOLDOWN_KEY = 'anivietdb-cooldown';
49
+
50
+ function setCooldown() {
51
+ try { sessionStorage.setItem(COOLDOWN_KEY, String(Date.now() + COOLDOWN_MS)); } catch (e) { /* ignore */ }
52
+ }
53
+
54
+ function cooldownRemaining() {
55
+ try {
56
+ var until = Number(sessionStorage.getItem(COOLDOWN_KEY));
57
+ var r = until - Date.now();
58
+ return r > 0 ? r : 0;
59
+ } catch (e) { return 0; }
60
+ }
61
+
62
+ function request(path, method, body) {
63
+ var headers = { 'Content-Type': 'application/json' };
64
+ if (MEM.token) headers['Authorization'] = 'Bearer ' + MEM.token;
65
+
66
+ var sep = path.indexOf('?') === -1 ? '?' : '&';
67
+ var url = API_BASE + path + sep + '_t=' + Date.now();
68
+ var init = { method: method || 'GET', headers: headers, cache: 'no-store' };
69
+ if (body) init.body = JSON.stringify(body);
70
+
71
+ return fetch(url, init).then(function (resp) {
72
+ return resp.json().catch(function () { return {}; }).then(function (data) {
73
+ if (!resp.ok) {
74
+ var err = new Error((data && data.error) || ('HTTP ' + resp.status));
75
+ err.status = resp.status;
76
+ err.data = data;
77
+ throw err;
78
+ }
79
+ return data;
80
+ });
81
+ });
82
+ }
83
+
84
+ function turnstileToken() {
85
+ try {
86
+ if (!window.turnstile || typeof window.turnstile.getResponse !== 'function') return '';
87
+ var widgets = document.querySelectorAll('.cf-turnstile');
88
+ for (var i = 0; i < widgets.length; i++) {
89
+ var el = widgets[i];
90
+ if (el.offsetParent === null) continue;
91
+ return window.turnstile.getResponse(el) || '';
92
+ }
93
+ return '';
94
+ } catch (e) { return ''; }
95
+ }
96
+
97
+ window.AuthSystem = {
98
+ baseUrl: API_BASE,
99
+
100
+ setBaseUrl: function (url) {
101
+ localStorage.setItem('anivietdb-auth-api', url);
102
+ API_BASE = url;
103
+ },
104
+
105
+ isConfigured: function () {
106
+ return !!API_BASE && API_BASE.indexOf('__PASTE') === -1;
107
+ },
108
+
109
+ isLoggedIn: function () {
110
+ return !!MEM.token;
111
+ },
112
+
113
+ getToken: function () {
114
+ return MEM.token;
115
+ },
116
+
117
+ getUser: function () {
118
+ return MEM.user;
119
+ },
120
+
121
+ register: function (email, username, password) {
122
+ return request('/api/auth/register', 'POST', { email: email, username: username, password: password, 'cf-turnstile-response': turnstileToken() })
123
+ .then(function (data) { setSession(data); return data; });
124
+ },
125
+
126
+ login: function (emailOrUsername, password) {
127
+ return request('/api/auth/login', 'POST', { email: emailOrUsername, password: password, 'cf-turnstile-response': turnstileToken() })
128
+ .then(function (data) { setSession(data); return data; });
129
+ },
130
+
131
+ logout: function () {
132
+ return request('/api/auth/logout', 'POST')
133
+ .then(function (data) { clearSession(); setCooldown(); return data; })
134
+ .catch(function () { clearSession(); setCooldown(); });
135
+ },
136
+
137
+ cooldownRemaining: cooldownRemaining,
138
+
139
+ isAdmin: function () {
140
+ return !!(MEM.user && MEM.user.role === 'admin');
141
+ },
142
+
143
+ adminUsers: function () {
144
+ return request('/api/auth/users', 'GET');
145
+ },
146
+
147
+ adminDeleteUser: function (id) {
148
+ return request('/api/auth/users/' + id, 'DELETE');
149
+ },
150
+
151
+ me: function () {
152
+ if (!MEM.token) return Promise.resolve(null);
153
+ return request('/api/auth/me', 'GET')
154
+ .then(function (data) {
155
+ if (data && data.user) {
156
+ MEM.user = data.user;
157
+ save();
158
+ }
159
+ return data.user || null;
160
+ })
161
+ .catch(function (err) {
162
+ if (err && err.status === 401) clearSession();
163
+ return null;
164
+ });
165
+ },
166
+
167
+ clearSession: clearSession
168
+ };
169
+ })(window);
style.css CHANGED
@@ -1,28 +1,1899 @@
 
 
 
 
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
 
 
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
 
1
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2
+
3
+ :root {
4
+ --header-gradient: linear-gradient(135deg, #ff9a9e, #fad0c4);
5
+ --accent-color: #ff9a9e;
6
+ --ep-color: #ff4b5c;
7
+ }
8
+
9
  body {
10
+ font-family: 'Roboto', sans-serif;
11
+ background: #f5f7fa;
12
+ color: #333;
13
+ padding: 20px;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
  }
18
 
19
+ .container {
20
+ max-width: 1000px;
21
+ margin: 40px auto;
22
+ background: rgba(255,255,255,0.88);
23
+ backdrop-filter: blur(10px);
24
+ -webkit-backdrop-filter: blur(10px);
25
+ border-radius: 12px;
26
+ box-shadow: 0 4px 20px rgba(0,0,0,0.1);
27
+ overflow: hidden;
28
+ flex: 1;
29
  }
30
 
31
+ .header {
32
+ display: flex;
33
+ background: var(--header-gradient);
34
+ padding: 20px;
35
+ align-items: center;
36
  }
37
 
38
+ .poster-wrapper {
39
+ flex-shrink: 0;
40
+ width: 200px;
41
+ border-radius: 8px;
42
+ overflow: hidden;
43
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2);
44
  }
45
 
46
+ .poster {
47
+ display: block;
48
+ width: 100%;
49
+ height: auto;
50
+ aspect-ratio: 3 / 4;
51
+ object-fit: cover;
52
+ background: #222;
53
+ }
54
+
55
+ .info {
56
+ margin-left: 20px;
57
+ color: #222;
58
+ }
59
+
60
+ .info h1 { margin: 0; font-size: 28px; font-weight: 700; }
61
+ .alt-title { margin: 8px 0; font-size: 15px; opacity: 0.75; font-style: italic; }
62
+ .info-table td { padding: 3px 8px 3px 0; vertical-align: top; font-size: 14px; }
63
+ .info-table .label { font-weight: 600; opacity: 0.75; white-space: nowrap; }
64
+
65
+ .description {
66
+ padding: 20px;
67
+ font-size: 16px;
68
+ line-height: 1.6;
69
+ }
70
+
71
+ .details {
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ padding: 0 20px 20px;
75
+ }
76
+
77
+ .detail-item {
78
+ flex: 1 1 200px;
79
+ margin: 10px;
80
+ background: #f0f0f0;
81
+ padding: 12px;
82
+ border-radius: 8px;
83
+ font-size: 14px;
84
+ }
85
+
86
+ .episodes {
87
+ padding: 20px;
88
+ background: #fafafa;
89
+ }
90
+
91
+ .episodes h2 {
92
+ margin-bottom: 15px;
93
+ font-size: 22px;
94
+ border-left: 4px solid var(--accent-color);
95
+ padding-left: 10px;
96
+ }
97
+
98
+ .lang-note {
99
+ display: inline-block;
100
+ margin: 0 0 12px;
101
+ padding: 6px 12px;
102
+ border-radius: 20px;
103
+ background: #fff3cd;
104
+ color: #856404;
105
+ border: 1px solid #ffd75e;
106
+ font-size: 13px;
107
+ font-weight: 600;
108
+ }
109
+
110
+ .episode-list {
111
+ display: grid;
112
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
113
+ gap: 15px;
114
+ }
115
+
116
+ .episode-card {
117
+ background: #fff;
118
+ border: 1px solid #ddd;
119
+ border-radius: 8px;
120
+ text-align: center;
121
+ padding: 10px;
122
+ cursor: pointer;
123
+ transition: transform 0.2s;
124
+ user-select: none;
125
+ }
126
+
127
+ .episode-card:hover {
128
+ transform: translateY(-5px);
129
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
130
+ }
131
+
132
+ .ep-num {
133
+ font-weight: 700;
134
+ font-size: 14px;
135
+ color: var(--ep-color);
136
+ }
137
+
138
+ .modal-overlay {
139
+ display: none;
140
+ position: fixed;
141
+ inset: 0;
142
+ background: rgba(0,0,0,0.88);
143
+ z-index: 1000;
144
+ justify-content: center;
145
+ align-items: center;
146
+ padding: 20px;
147
+ }
148
+
149
+ .modal-overlay.active { display: flex; }
150
+
151
+ .modal-content {
152
+ background: #111;
153
+ border-radius: 12px;
154
+ overflow: hidden;
155
+ width: 100%;
156
+ max-width: 960px;
157
+ box-shadow: 0 8px 40px rgba(0,0,0,0.6);
158
+ animation: modalIn 0.2s ease-out;
159
+ }
160
+
161
+ @keyframes modalIn {
162
+ from { opacity: 0; transform: scale(0.95); }
163
+ to { opacity: 1; transform: scale(1); }
164
+ }
165
+
166
+ .modal-header {
167
+ display: flex;
168
+ justify-content: space-between;
169
+ align-items: center;
170
+ padding: 10px 16px;
171
+ background: #1a1a1a;
172
+ }
173
+
174
+ .modal-title { font-weight: 700; font-size: 15px; color: #fff; }
175
+
176
+ .close-btn {
177
+ background: none;
178
+ border: none;
179
+ color: #999;
180
+ font-size: 28px;
181
+ cursor: pointer;
182
+ line-height: 1;
183
+ padding: 0 4px;
184
+ transition: color 0.2s;
185
+ }
186
+
187
+ .close-btn:hover { color: #fff; }
188
+
189
+ .modal-body {
190
+ position: relative;
191
+ width: 100%;
192
+ padding-top: 56.25%;
193
+ }
194
+
195
+ .modal-body iframe {
196
+ position: absolute;
197
+ inset: 0;
198
+ width: 100%;
199
+ height: 100%;
200
+ border: none;
201
+ }
202
+
203
+ .vi-popup-overlay {
204
+ display: none;
205
+ position: fixed;
206
+ inset: 0;
207
+ background: rgba(0,0,0,0.88);
208
+ z-index: 1000;
209
+ justify-content: center;
210
+ align-items: center;
211
+ padding: 20px;
212
+ }
213
+
214
+ .vi-popup-overlay.active { display: flex; }
215
+
216
+ .vi-popup {
217
+ background: #15151a;
218
+ border: 1px solid #2a2a35;
219
+ border-radius: 14px;
220
+ max-width: 420px;
221
+ width: 100%;
222
+ padding: 26px 22px 22px;
223
+ text-align: center;
224
+ box-shadow: 0 10px 40px rgba(0,0,0,0.6);
225
+ animation: modalIn 0.2s ease-out;
226
+ }
227
+
228
+ .vi-popup .vi-popup-title { color: #fff; font-size: 17px; font-weight: 700; margin: 0 0 10px; }
229
+ .vi-popup .vi-popup-msg { color: #aaa; font-size: 14px; line-height: 1.65; margin: 0 0 20px; }
230
+ .vi-popup .vi-popup-actions { display: flex; flex-direction: column; gap: 10px; }
231
+ .vi-popup .vi-popup-actions button {
232
+ padding: 12px 16px;
233
+ border: none;
234
+ border-radius: 8px;
235
+ font-size: 14px;
236
+ font-weight: 700;
237
+ cursor: pointer;
238
+ font-family: inherit;
239
+ transition: background 0.2s, color 0.2s;
240
+ }
241
+ .vi-popup .vi-continue { background: #667eea; color: #fff; }
242
+ .vi-popup .vi-continue:hover { background: #7a90f5; }
243
+ .vi-popup .vi-cancel { background: #23232b; color: #ccc; }
244
+ .vi-popup .vi-cancel:hover { color: #fff; }
245
+ .vi-popup a.vi-cancel { display: block; text-decoration: none; text-align: center; }
246
+
247
+ /* Home page */
248
+ .home-header {
249
+ background: linear-gradient(135deg, #667eea, #764ba2);
250
+ padding: 40px 20px;
251
+ text-align: center;
252
+ }
253
+ .site-title {
254
+ font-size: 36px;
255
+ font-weight: 700;
256
+ margin-bottom: 16px;
257
+ letter-spacing: 3px;
258
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
259
+ color: #fff;
260
+ }
261
+ .site-desc {
262
+ color: rgba(255,255,255,0.85);
263
+ font-size: 14px;
264
+ margin-bottom: 20px;
265
+ }
266
+ .search-box {
267
+ display: flex;
268
+ max-width: 400px;
269
+ margin: 0 auto;
270
+ border-radius: 8px;
271
+ overflow: hidden;
272
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
273
+ }
274
+ .search-box input {
275
+ flex: 1;
276
+ padding: 12px 16px;
277
+ border: none;
278
+ outline: none;
279
+ font-size: 15px;
280
+ font-family: inherit;
281
+ }
282
+ .search-box button {
283
+ padding: 12px 18px;
284
+ border: none;
285
+ background: #222;
286
+ color: #fff;
287
+ cursor: pointer;
288
+ font-size: 16px;
289
+ }
290
+ .search-box button:hover { background: #000; }
291
+
292
+ .home-nav {
293
+ display: flex;
294
+ gap: 8px;
295
+ padding: 16px 20px;
296
+ border-bottom: 1px solid #eee;
297
+ flex-wrap: wrap;
298
+ }
299
+ .home-nav a {
300
+ text-decoration: none;
301
+ color: #555;
302
+ padding: 6px 14px;
303
+ border-radius: 4px;
304
+ font-size: 14px;
305
+ transition: background 0.2s;
306
+ cursor: pointer;
307
+ }
308
+ .home-nav a:hover,
309
+ .home-nav a.active { background: #667eea; color: #fff; }
310
+
311
+ .random-section {
312
+ text-align: center;
313
+ padding: 12px 20px;
314
+ border-bottom: 1px solid #eee;
315
+ }
316
+ .random-section button {
317
+ padding: 10px 24px;
318
+ font-size: 15px;
319
+ font-weight: 600;
320
+ border: none;
321
+ border-radius: 8px;
322
+ background: linear-gradient(135deg, #667eea, #764ba2);
323
+ color: #fff;
324
+ cursor: pointer;
325
+ transition: transform 0.2s, box-shadow 0.2s;
326
+ font-family: inherit;
327
+ }
328
+ .random-section button:hover {
329
+ transform: translateY(-2px);
330
+ box-shadow: 0 6px 20px rgba(102,126,234,0.4);
331
+ }
332
+
333
+ .stats-bar {
334
+ display: flex;
335
+ gap: 12px;
336
+ padding: 10px 20px;
337
+ background: #f9f9f9;
338
+ border-bottom: 1px solid #eee;
339
+ font-size: 13px;
340
+ color: #666;
341
+ flex-wrap: wrap;
342
+ justify-content: center;
343
+ }
344
+ .stats-bar .stat-item span { font-weight: 700; color: #333; }
345
+
346
+ .category-section { margin: 0; }
347
+ .category-title {
348
+ font-size: 16px;
349
+ font-weight: 700;
350
+ padding: 12px 0 8px 10px;
351
+ margin: 16px 20px 0;
352
+ border-left: 4px solid var(--accent-color);
353
+ }
354
+
355
+ .movie-grid {
356
+ display: grid;
357
+ grid-template-columns: repeat(4, 1fr);
358
+ gap: 20px;
359
+ padding: 20px;
360
+ }
361
+ .movie-card {
362
+ background: #fff;
363
+ border-radius: 8px;
364
+ overflow: hidden;
365
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
366
+ transition: transform 0.2s;
367
+ text-decoration: none;
368
+ color: inherit;
369
+ display: block;
370
+ }
371
+ .movie-card:hover { transform: translateY(-4px); box-shadow: 0 6px 16px rgba(0,0,0,0.15); }
372
+ .movie-thumb {
373
+ position: relative;
374
+ width: 100%;
375
+ aspect-ratio: 3 / 4;
376
+ background: #222;
377
+ }
378
+ .movie-thumb img {
379
+ display: block;
380
+ width: 100%;
381
+ height: 100%;
382
+ object-fit: cover;
383
+ }
384
+ .movie-type {
385
+ position: absolute;
386
+ top: 6px;
387
+ left: 6px;
388
+ background: rgba(0,0,0,0.8);
389
+ color: #fff;
390
+ font-size: 10px;
391
+ padding: 2px 6px;
392
+ border-radius: 3px;
393
+ text-transform: uppercase;
394
+ }
395
+ .movie-ep {
396
+ position: absolute;
397
+ bottom: 6px;
398
+ left: 6px;
399
+ background: rgba(0,0,0,0.8);
400
+ color: #fff;
401
+ font-size: 11px;
402
+ padding: 2px 6px;
403
+ border-radius: 3px;
404
+ }
405
+ .movie-info { padding: 10px 12px; }
406
+ .movie-title {
407
+ font-size: 14px;
408
+ font-weight: 700;
409
+ margin-bottom: 4px;
410
+ display: -webkit-box;
411
+ -webkit-line-clamp: 2;
412
+ -webkit-box-orient: vertical;
413
+ overflow: hidden;
414
+ }
415
+ .movie-original { font-size: 12px; color: #888; }
416
+
417
+ /* Footer */
418
+ .footer {
419
+ text-align: center;
420
+ padding: 20px;
421
+ font-size: 12px;
422
+ color: #888;
423
+ border-top: 1px solid #eee;
424
+ margin-top: 20px;
425
+ line-height: 1.8;
426
+ }
427
+
428
+ /* Back link */
429
+ .back-link {
430
+ display: inline-flex;
431
+ align-items: center;
432
+ gap: 6px;
433
+ margin: 12px 0 12px 20px;
434
+ padding: 8px 16px;
435
+ text-decoration: none;
436
+ color: #667eea;
437
+ font-size: 13px;
438
+ font-weight: 600;
439
+ border-radius: 8px;
440
+ background: #f0f0ff;
441
+ border: 1px solid #e0e0ff;
442
+ transition: all 0.25s;
443
+ position: relative;
444
+ overflow: hidden;
445
+ z-index: 1;
446
+ }
447
+ .back-link::before {
448
+ content: '';
449
+ position: absolute;
450
+ inset: 0;
451
+ background: linear-gradient(135deg, #667eea18, #764ba218);
452
+ z-index: -1;
453
+ opacity: 0;
454
+ transition: opacity 0.25s;
455
+ border-radius: 8px;
456
+ }
457
+ .back-link:hover {
458
+ transform: translateX(-3px);
459
+ border-color: #667eea;
460
+ color: #764ba2;
461
+ }
462
+ .back-link:hover::before {
463
+ opacity: 1;
464
+ }
465
+
466
+ .next-link {
467
+ margin-left: 4px;
468
+ }
469
+ .next-link:hover {
470
+ transform: translateX(3px);
471
+ }
472
+
473
+ /* UI Toggle */
474
+ .ui-toggle {
475
+ display: flex;
476
+ align-items: center;
477
+ justify-content: center;
478
+ gap: 6px;
479
+ margin-top: 14px;
480
+ }
481
+ .ui-btn {
482
+ padding: 6px 14px;
483
+ border: 1px solid rgba(255,255,255,0.3);
484
+ background: transparent;
485
+ color: rgba(255,255,255,0.7);
486
+ border-radius: 6px;
487
+ cursor: pointer;
488
+ font-size: 13px;
489
+ font-family: inherit;
490
+ transition: all 0.2s;
491
+ line-height: 1.4;
492
+ }
493
+ .ui-btn.active {
494
+ background: rgba(255,255,255,0.2);
495
+ color: #fff;
496
+ border-color: rgba(255,255,255,0.6);
497
+ box-shadow: 0 0 12px rgba(255,255,255,0.1);
498
+ }
499
+ .ui-btn:hover {
500
+ background: rgba(255,255,255,0.1);
501
+ }
502
+
503
+ /* Mobile UI */
504
+ body.ui-mobile {
505
+ padding: 0;
506
+ background: #fff;
507
+ }
508
+ body.ui-mobile .container {
509
+ margin: 0;
510
+ border-radius: 0;
511
+ box-shadow: none;
512
+ }
513
+ body.ui-mobile .home-header {
514
+ padding: 24px 12px 16px;
515
+ }
516
+ body.ui-mobile .site-title {
517
+ font-size: 24px;
518
+ margin-bottom: 8px;
519
+ }
520
+ body.ui-mobile .site-desc {
521
+ font-size: 12px;
522
+ margin-bottom: 12px;
523
+ }
524
+ body.ui-mobile .search-box {
525
+ max-width: 100%;
526
+ }
527
+ body.ui-mobile .search-box input {
528
+ padding: 10px 12px;
529
+ font-size: 14px;
530
+ }
531
+ body.ui-mobile .search-box button {
532
+ padding: 10px 14px;
533
+ }
534
+ body.ui-mobile .ui-toggle {
535
+ margin-top: 10px;
536
+ }
537
+ body.ui-mobile .ui-btn {
538
+ padding: 5px 12px;
539
+ font-size: 12px;
540
+ }
541
+ body.ui-mobile .settings-link {
542
+ font-size: 17px;
543
+ }
544
+ body.ui-mobile .stats-bar {
545
+ font-size: 11px;
546
+ gap: 6px;
547
+ padding: 8px;
548
+ }
549
+ body.ui-mobile .random-section {
550
+ padding: 8px 12px;
551
+ }
552
+ body.ui-mobile .random-section button {
553
+ padding: 8px 18px;
554
+ font-size: 13px;
555
+ width: 100%;
556
+ max-width: 300px;
557
+ }
558
+ body.ui-mobile .home-nav {
559
+ overflow-x: auto;
560
+ flex-wrap: nowrap;
561
+ padding: 10px 8px;
562
+ gap: 4px;
563
+ -webkit-overflow-scrolling: touch;
564
+ }
565
+ body.ui-mobile .home-nav a {
566
+ white-space: nowrap;
567
+ padding: 8px 14px;
568
+ font-size: 13px;
569
+ flex-shrink: 0;
570
+ }
571
+ body.ui-mobile .movie-grid {
572
+ grid-template-columns: repeat(2, 1fr);
573
+ gap: 10px;
574
+ padding: 10px;
575
+ }
576
+ body.ui-mobile .movie-card {
577
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
578
+ border-radius: 10px;
579
+ }
580
+ body.ui-mobile .movie-thumb {
581
+ border-radius: 10px 10px 0 0;
582
+ }
583
+ body.ui-mobile .movie-card:hover {
584
+ transform: none;
585
+ }
586
+ body.ui-mobile .movie-title {
587
+ font-size: 13px;
588
+ }
589
+ body.ui-mobile .movie-original {
590
+ font-size: 11px;
591
+ }
592
+ body.ui-mobile .movie-ep {
593
+ font-size: 10px;
594
+ bottom: 4px;
595
+ left: 4px;
596
+ }
597
+ body.ui-mobile .movie-type {
598
+ font-size: 9px;
599
+ top: 4px;
600
+ left: 4px;
601
+ }
602
+ body.ui-mobile .pagination {
603
+ gap: 4px;
604
+ padding: 12px 8px;
605
+ }
606
+ body.ui-mobile .pg-btn {
607
+ min-width: 32px;
608
+ height: 32px;
609
+ font-size: 13px;
610
+ }
611
+ body.ui-mobile .category-title {
612
+ margin: 8px 8px 0;
613
+ font-size: 14px;
614
+ padding: 8px 0 6px 8px;
615
+ }
616
+ body.ui-mobile .footer {
617
+ padding: 14px 12px;
618
+ font-size: 11px;
619
+ }
620
+
621
+ /* Mobile detail page */
622
+ body.ui-mobile .header {
623
+ flex-direction: column;
624
+ align-items: center;
625
+ text-align: center;
626
+ padding: 16px;
627
+ }
628
+ body.ui-mobile .poster-wrapper {
629
+ width: 160px;
630
+ border-radius: 10px;
631
+ }
632
+ body.ui-mobile .info {
633
+ margin-left: 0;
634
+ margin-top: 12px;
635
+ }
636
+ body.ui-mobile .info h1 {
637
+ font-size: 20px;
638
+ }
639
+ body.ui-mobile .info-table td {
640
+ font-size: 13px;
641
+ }
642
+ body.ui-mobile .description {
643
+ padding: 16px;
644
+ font-size: 14px;
645
+ }
646
+ body.ui-mobile .details {
647
+ padding: 0 16px 16px;
648
+ }
649
+ body.ui-mobile .detail-item {
650
+ flex: 1 1 100%;
651
+ margin: 4px 0;
652
+ padding: 10px;
653
+ font-size: 13px;
654
+ }
655
+ body.ui-mobile .episodes {
656
+ padding: 16px;
657
+ }
658
+ body.ui-mobile .episodes h2 {
659
+ font-size: 18px;
660
+ }
661
+ body.ui-mobile .episode-list {
662
+ grid-template-columns: repeat(3, 1fr);
663
+ gap: 8px;
664
+ }
665
+ body.ui-mobile .episode-card {
666
+ padding: 8px 4px;
667
+ }
668
+ body.ui-mobile .episode-card:hover {
669
+ transform: none;
670
+ }
671
+ body.ui-mobile .ep-num {
672
+ font-size: 12px;
673
+ }
674
+ body.ui-mobile .back-link {
675
+ margin: 8px 0 8px 12px;
676
+ padding: 6px 12px;
677
+ font-size: 12px;
678
+ }
679
+ body.ui-mobile .modal-overlay {
680
+ padding: 4px;
681
+ }
682
+ body.ui-mobile .modal-content {
683
+ border-radius: 8px;
684
+ }
685
+ body.ui-mobile .modal-header {
686
+ padding: 8px 12px;
687
+ }
688
+ body.ui-mobile .modal-title {
689
+ font-size: 13px;
690
+ }
691
+
692
+ /* Series hub */
693
+ .series-header {
694
+ display: flex;
695
+ gap: 20px;
696
+ padding: 20px;
697
+ background: linear-gradient(135deg, #667eea22, #764ba222);
698
+ align-items: center;
699
+ }
700
+ .series-poster {
701
+ width: 160px;
702
+ flex-shrink: 0;
703
+ border-radius: 8px;
704
+ overflow: hidden;
705
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
706
+ }
707
+ .series-poster img {
708
+ width: 100%;
709
+ display: block;
710
+ aspect-ratio: 3/4;
711
+ object-fit: cover;
712
+ }
713
+ .series-info h1 {
714
+ font-size: 26px;
715
+ margin-bottom: 6px;
716
+ }
717
+ .series-desc {
718
+ margin-top: 10px;
719
+ font-size: 14px;
720
+ color: #555;
721
+ line-height: 1.6;
722
+ }
723
+ .series-grid {
724
+ display: grid;
725
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
726
+ gap: 16px;
727
+ padding: 20px;
728
+ }
729
+ .series-card {
730
+ text-decoration: none;
731
+ color: inherit;
732
+ border-radius: 10px;
733
+ overflow: hidden;
734
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
735
+ transition: transform 0.25s, box-shadow 0.25s;
736
+ border-bottom: 3px solid var(--card-color);
737
+ background: #fff;
738
+ }
739
+ .series-card:hover {
740
+ transform: translateY(-5px);
741
+ box-shadow: 0 8px 20px rgba(0,0,0,0.12);
742
+ }
743
+ .series-card-thumb {
744
+ width: 100%;
745
+ aspect-ratio: 3/4;
746
+ overflow: hidden;
747
+ background: #222;
748
+ }
749
+ .series-card-thumb img {
750
+ width: 100%;
751
+ height: 100%;
752
+ object-fit: cover;
753
+ display: block;
754
+ }
755
+ .series-card-body {
756
+ padding: 10px 12px;
757
+ }
758
+ .series-card-body h3 {
759
+ font-size: 14px;
760
+ font-weight: 700;
761
+ margin-bottom: 4px;
762
+ }
763
+ .series-card-body p {
764
+ font-size: 12px;
765
+ color: #888;
766
+ }
767
+
768
+ /* Pagination */
769
+ .pagination {
770
+ display: flex;
771
+ justify-content: center;
772
+ align-items: center;
773
+ gap: 6px;
774
+ padding: 20px;
775
+ flex-wrap: wrap;
776
+ }
777
+ .pg-btn {
778
+ min-width: 36px;
779
+ height: 36px;
780
+ border: 1px solid #ddd;
781
+ border-radius: 6px;
782
+ background: #fff;
783
+ color: #555;
784
+ font-size: 14px;
785
+ font-weight: 600;
786
+ cursor: pointer;
787
+ transition: all 0.2s;
788
+ font-family: inherit;
789
+ display: inline-flex;
790
+ align-items: center;
791
+ justify-content: center;
792
+ }
793
+ .pg-btn:hover:not(.disabled):not(.active) {
794
+ border-color: #667eea;
795
+ color: #667eea;
796
+ }
797
+ .pg-btn.active {
798
+ background: #667eea;
799
+ border-color: #667eea;
800
+ color: #fff;
801
+ }
802
+ .pg-btn.disabled {
803
+ opacity: 0.35;
804
+ cursor: default;
805
+ }
806
+
807
+ @media (max-width: 700px) {
808
+ body { padding: 10px; }
809
+ .container { margin: 16px auto; }
810
+ .header { flex-direction: column; align-items: center; text-align: center; padding: 20px; }
811
+ .poster-wrapper { width: 160px; border-radius: 10px; }
812
+ .info { margin-left: 0; }
813
+ .episode-list { grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); }
814
+ .modal-overlay { padding: 8px; }
815
+ .movie-grid { grid-template-columns: repeat(3, 1fr); gap: 14px; padding: 14px; }
816
+ .movie-card { border-radius: 10px; }
817
+ .movie-thumb { border-radius: 10px 10px 0 0; }
818
+ .series-header { flex-direction: column; text-align: center; }
819
+ .series-poster { width: 140px; }
820
+ .series-info h1 { font-size: 20px; }
821
+ .series-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; padding: 12px; }
822
+ }
823
+
824
+ @media (max-width: 480px) {
825
+ .movie-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; padding: 10px; }
826
+ .movie-card { border-radius: 10px; }
827
+ .movie-thumb { border-radius: 10px 10px 0 0; }
828
+ }
829
+
830
+ /* Settings link */
831
+ .settings-link {
832
+ color: rgba(255,255,255,0.6);
833
+ text-decoration: none;
834
+ font-size: 20px;
835
+ line-height: 1;
836
+ transition: color 0.2s, transform 0.2s;
837
+ display: inline-flex;
838
+ align-items: center;
839
+ }
840
+ .settings-link:hover {
841
+ color: #fff;
842
+ transform: rotate(60deg);
843
+ }
844
+
845
+ /* Settings page */
846
+ .settings-page {
847
+ display: flex;
848
+ justify-content: center;
849
+ align-items: center;
850
+ min-height: 300px;
851
+ padding: 40px 20px;
852
+ }
853
+ .settings-box {
854
+ background: #f9f9f9;
855
+ border-radius: 12px;
856
+ padding: 40px;
857
+ text-align: center;
858
+ max-width: 400px;
859
+ width: 100%;
860
+ box-shadow: 0 2px 12px rgba(0,0,0,0.06);
861
+ }
862
+ .settings-title {
863
+ font-size: 22px;
864
+ margin-bottom: 24px;
865
+ color: #333;
866
+ }
867
+ .settings-field {
868
+ display: flex;
869
+ gap: 8px;
870
+ }
871
+ .settings-field input {
872
+ flex: 1;
873
+ padding: 12px 16px;
874
+ border: 2px solid #ddd;
875
+ border-radius: 8px;
876
+ font-size: 15px;
877
+ font-family: inherit;
878
+ outline: none;
879
+ transition: border-color 0.2s;
880
+ }
881
+ .settings-field input:focus {
882
+ border-color: #667eea;
883
+ }
884
+ .settings-field button {
885
+ padding: 12px 20px;
886
+ border: none;
887
+ border-radius: 8px;
888
+ background: linear-gradient(135deg, #667eea, #764ba2);
889
+ color: #fff;
890
+ font-size: 15px;
891
+ font-weight: 600;
892
+ cursor: pointer;
893
+ font-family: inherit;
894
+ transition: transform 0.2s;
895
+ }
896
+ .settings-field button:hover {
897
+ transform: translateY(-2px);
898
+ }
899
+ .settings-error {
900
+ color: #e74c3c;
901
+ font-size: 13px;
902
+ margin-top: 12px;
903
+ min-height: 20px;
904
+ }
905
+ body.ui-mobile .settings-box {
906
+ padding: 24px 16px;
907
+ }
908
+ body.ui-mobile .settings-field {
909
+ flex-direction: column;
910
+ }
911
+ body.ui-mobile .settings-title {
912
+ font-size: 18px;
913
+ }
914
+
915
+ /* Age confirmation modal */
916
+ .age-modal-content {
917
+ max-width: 420px !important;
918
+ }
919
+ .age-modal-body {
920
+ padding: 30px 24px;
921
+ text-align: center;
922
+ background: #1a1a1a;
923
+ }
924
+ .age-warning-icon {
925
+ font-size: 48px;
926
+ color: #ff4444;
927
+ margin-bottom: 12px;
928
+ }
929
+ .age-warning-text {
930
+ font-size: 20px;
931
+ font-weight: 700;
932
+ color: #fff;
933
+ margin-bottom: 8px;
934
+ }
935
+ .age-warning-desc {
936
+ font-size: 13px;
937
+ color: #999;
938
+ margin-bottom: 24px;
939
+ line-height: 1.5;
940
+ }
941
+ .age-buttons {
942
+ display: flex;
943
+ gap: 12px;
944
+ justify-content: center;
945
+ }
946
+ .age-btn {
947
+ padding: 12px 24px;
948
+ border: none;
949
+ border-radius: 8px;
950
+ font-size: 15px;
951
+ font-weight: 600;
952
+ cursor: pointer;
953
+ font-family: inherit;
954
+ transition: transform 0.2s, box-shadow 0.2s;
955
+ }
956
+ .age-btn:hover {
957
+ transform: translateY(-2px);
958
+ }
959
+ .age-btn-yes {
960
+ background: linear-gradient(135deg, #28a745, #20c997);
961
+ color: #fff;
962
+ }
963
+ .age-btn-yes:hover {
964
+ box-shadow: 0 4px 16px rgba(40,167,69,0.4);
965
+ }
966
+ .age-btn-no {
967
+ background: linear-gradient(135deg, #dc3545, #c82333);
968
+ color: #fff;
969
+ }
970
+ .age-btn-no:hover {
971
+ box-shadow: 0 4px 16px rgba(220,53,69,0.4);
972
+ }
973
+ body.ui-mobile .age-modal-body {
974
+ padding: 20px 16px;
975
+ }
976
+ body.ui-mobile .age-warning-text {
977
+ font-size: 17px;
978
+ }
979
+ body.ui-mobile .age-btn {
980
+ font-size: 13px;
981
+ padding: 10px 16px;
982
+ }
983
+
984
+ /* Adult card on homepage */
985
+ .adult-card {
986
+ border-bottom: 3px solid #cc0000 !important;
987
+ }
988
+ .adult-badge-tag {
989
+ background: rgba(200,0,0,0.9) !important;
990
+ }
991
+
992
+ /* Adult mode page (legacy) */
993
+ .adult-mode {
994
+ background: #0d0d0d;
995
+ }
996
+ .adult-container {
997
+ background: rgba(26,26,26,0.82);
998
+ backdrop-filter: blur(12px);
999
+ -webkit-backdrop-filter: blur(12px);
1000
+ box-shadow: 0 4px 20px rgba(255,0,0,0.08);
1001
+ }
1002
+ .adult-header {
1003
+ background: linear-gradient(135deg, #1a0000, #4a0000) !important;
1004
+ }
1005
+ .adult-title {
1006
+ color: #ff4444 !important;
1007
+ text-shadow: 0 0 20px rgba(255,0,0,0.3) !important;
1008
+ }
1009
+ .adult-badge {
1010
+ display: inline-block;
1011
+ background: #ff0000;
1012
+ color: #fff;
1013
+ font-size: 12px;
1014
+ padding: 2px 8px;
1015
+ border-radius: 4px;
1016
+ vertical-align: super;
1017
+ margin-left: 6px;
1018
+ font-weight: 700;
1019
+ letter-spacing: 1px;
1020
+ }
1021
+ .adult-desc {
1022
+ color: rgba(255,100,100,0.7) !important;
1023
+ }
1024
+ .adult-stats {
1025
+ background: #222 !important;
1026
+ border-bottom-color: #333 !important;
1027
+ color: #888 !important;
1028
+ }
1029
+ .adult-stats .stat-item span {
1030
+ color: #ff6666 !important;
1031
+ }
1032
+ .adult-random {
1033
+ border-bottom-color: #333 !important;
1034
+ }
1035
+ .adult-random button {
1036
+ background: linear-gradient(135deg, #cc0000, #660000) !important;
1037
+ }
1038
+ .adult-random button:hover {
1039
+ box-shadow: 0 6px 20px rgba(255,0,0,0.3) !important;
1040
+ }
1041
+ .adult-back-link {
1042
+ background: #222 !important;
1043
+ border-color: #440000 !important;
1044
+ color: #ff6666 !important;
1045
+ }
1046
+ .adult-back-link:hover {
1047
+ border-color: #ff0000 !important;
1048
+ color: #ff4444 !important;
1049
+ }
1050
+ .adult-card {
1051
+ border-bottom: 3px solid #cc0000 !important;
1052
+ }
1053
+ .adult-type-badge {
1054
+ background: rgba(200,0,0,0.9) !important;
1055
+ }
1056
+ .adult-footer {
1057
+ border-top-color: #333 !important;
1058
+ color: #666 !important;
1059
+ }
1060
+ body.ui-mobile.adult-mode {
1061
+ background: #0d0d0d;
1062
+ }
1063
+
1064
+ /* Light Novel list view */
1065
+ .ln-list {
1066
+ padding: 16px 20px;
1067
+ display: flex;
1068
+ flex-direction: column;
1069
+ gap: 12px;
1070
+ }
1071
+ .ln-list-item {
1072
+ display: flex;
1073
+ gap: 16px;
1074
+ background: #fff;
1075
+ border-radius: 10px;
1076
+ padding: 14px;
1077
+ box-shadow: 0 1px 6px rgba(0,0,0,0.07);
1078
+ cursor: pointer;
1079
+ transition: transform 0.2s, box-shadow 0.2s;
1080
+ border: 1px solid #eee;
1081
+ }
1082
+ .ln-list-item:hover {
1083
+ transform: translateY(-2px);
1084
+ box-shadow: 0 4px 14px rgba(0,0,0,0.1);
1085
+ }
1086
+ .ln-cover {
1087
+ width: 100px;
1088
+ flex-shrink: 0;
1089
+ border-radius: 6px;
1090
+ overflow: hidden;
1091
+ }
1092
+ .ln-cover img {
1093
+ width: 100%;
1094
+ display: block;
1095
+ aspect-ratio: 3/4;
1096
+ object-fit: cover;
1097
+ background: #222;
1098
+ }
1099
+ .ln-info {
1100
+ flex: 1;
1101
+ min-width: 0;
1102
+ }
1103
+ .ln-title {
1104
+ font-size: 16px;
1105
+ font-weight: 700;
1106
+ margin-bottom: 4px;
1107
+ color: #222;
1108
+ }
1109
+ .ln-author {
1110
+ font-size: 12px;
1111
+ color: #888;
1112
+ margin-bottom: 6px;
1113
+ }
1114
+ .ln-genres {
1115
+ display: flex;
1116
+ flex-wrap: wrap;
1117
+ gap: 4px;
1118
+ margin-bottom: 6px;
1119
+ }
1120
+ .ln-genre-tag {
1121
+ display: inline-block;
1122
+ padding: 2px 8px;
1123
+ border-radius: 4px;
1124
+ background: #667eea;
1125
+ color: #fff;
1126
+ font-size: 10px;
1127
+ font-weight: 600;
1128
+ }
1129
+ .ln-desc {
1130
+ font-size: 13px;
1131
+ color: #666;
1132
+ line-height: 1.5;
1133
+ display: -webkit-box;
1134
+ -webkit-line-clamp: 2;
1135
+ -webkit-box-orient: vertical;
1136
+ overflow: hidden;
1137
+ }
1138
+ .ln-status {
1139
+ display: inline-block;
1140
+ margin-top: 6px;
1141
+ font-size: 11px;
1142
+ color: #28a745;
1143
+ font-weight: 600;
1144
+ }
1145
+ body.ui-mobile .ln-list {
1146
+ padding: 10px 12px;
1147
+ gap: 10px;
1148
+ }
1149
+ body.ui-mobile .ln-list-item {
1150
+ padding: 10px;
1151
+ }
1152
+ body.ui-mobile .ln-cover {
1153
+ width: 80px;
1154
+ }
1155
+ body.ui-mobile .ln-title {
1156
+ font-size: 14px;
1157
+ }
1158
+ body.ui-mobile .ln-desc {
1159
+ font-size: 12px;
1160
+ -webkit-line-clamp: 1;
1161
+ }
1162
+
1163
+ /* LN Detail modal */
1164
+ .ln-detail-content {
1165
+ max-width: 700px !important;
1166
+ }
1167
+ .ln-detail-body {
1168
+ padding: 20px;
1169
+ background: #1a1a1a;
1170
+ max-height: 80vh;
1171
+ overflow-y: auto;
1172
+ }
1173
+ .ln-detail-header {
1174
+ display: flex;
1175
+ gap: 20px;
1176
+ margin-bottom: 24px;
1177
+ }
1178
+ .ln-detail-cover {
1179
+ width: 160px;
1180
+ flex-shrink: 0;
1181
+ border-radius: 8px;
1182
+ aspect-ratio: 3/4;
1183
+ object-fit: cover;
1184
+ background: #222;
1185
+ }
1186
+ .ln-detail-info {
1187
+ flex: 1;
1188
+ min-width: 0;
1189
+ }
1190
+ .ln-detail-info h2 {
1191
+ font-size: 22px;
1192
+ color: #fff;
1193
+ margin-bottom: 6px;
1194
+ }
1195
+ .ln-detail-author {
1196
+ font-size: 13px;
1197
+ color: #aaa;
1198
+ margin-bottom: 8px;
1199
+ }
1200
+ .ln-detail-genres {
1201
+ margin-bottom: 10px;
1202
+ }
1203
+ .ln-detail-desc {
1204
+ font-size: 14px;
1205
+ color: #ccc;
1206
+ line-height: 1.6;
1207
+ margin-bottom: 10px;
1208
+ }
1209
+ .ln-detail-status {
1210
+ font-size: 12px;
1211
+ color: #28a745;
1212
+ font-weight: 600;
1213
+ }
1214
+ .ln-detail-chapters h3 {
1215
+ font-size: 18px;
1216
+ color: #fff;
1217
+ margin-bottom: 12px;
1218
+ border-left: 4px solid #667eea;
1219
+ padding-left: 10px;
1220
+ }
1221
+ .ln-chapter-item {
1222
+ display: flex;
1223
+ gap: 12px;
1224
+ padding: 10px 14px;
1225
+ background: #222;
1226
+ border-radius: 6px;
1227
+ margin-bottom: 6px;
1228
+ cursor: pointer;
1229
+ transition: background 0.2s;
1230
+ align-items: center;
1231
+ }
1232
+ .ln-chapter-item:hover {
1233
+ background: #333;
1234
+ }
1235
+ .ln-ch-num {
1236
+ font-size: 13px;
1237
+ font-weight: 700;
1238
+ color: #667eea;
1239
+ white-space: nowrap;
1240
+ }
1241
+ .ln-ch-title {
1242
+ font-size: 13px;
1243
+ color: #ccc;
1244
+ }
1245
+ body.ui-mobile .ln-detail-body {
1246
+ padding: 14px;
1247
+ }
1248
+ body.ui-mobile .ln-detail-header {
1249
+ flex-direction: column;
1250
+ align-items: center;
1251
+ text-align: center;
1252
+ }
1253
+ body.ui-mobile .ln-detail-cover {
1254
+ width: 130px;
1255
+ }
1256
+ body.ui-mobile .ln-detail-info h2 {
1257
+ font-size: 18px;
1258
+ }
1259
+
1260
+ /* LN Reader modal */
1261
+ .ln-reader-content {
1262
+ max-width: 800px !important;
1263
+ max-height: 90vh;
1264
+ display: flex;
1265
+ flex-direction: column;
1266
+ }
1267
+ .ln-reader-body {
1268
+ padding: 24px 28px;
1269
+ background: #f9f7f4;
1270
+ color: #333;
1271
+ line-height: 1.9;
1272
+ font-size: 16px;
1273
+ overflow-y: auto;
1274
+ flex: 1;
1275
+ }
1276
+ .ln-reader-body p {
1277
+ margin-bottom: 16px;
1278
+ text-align: justify;
1279
+ }
1280
+ .ln-reader-body img {
1281
+ max-width: 100%;
1282
+ display: block;
1283
+ margin: 16px auto;
1284
+ border-radius: 4px;
1285
+ }
1286
+ .ln-reader-nav {
1287
+ display: flex;
1288
+ justify-content: space-between;
1289
+ padding: 12px 20px;
1290
+ background: #1a1a1a;
1291
+ gap: 10px;
1292
+ }
1293
+ .ln-reader-btn {
1294
+ padding: 10px 20px;
1295
+ border: none;
1296
+ border-radius: 6px;
1297
+ background: #667eea;
1298
+ color: #fff;
1299
+ font-size: 14px;
1300
+ font-weight: 600;
1301
+ cursor: pointer;
1302
+ font-family: inherit;
1303
+ transition: background 0.2s;
1304
+ }
1305
+ .ln-reader-btn:hover {
1306
+ background: #5a6fd6;
1307
+ }
1308
+ body.ui-mobile .ln-reader-body {
1309
+ padding: 16px 18px;
1310
+ font-size: 15px;
1311
+ }
1312
+ body.ui-mobile .ln-reader-nav {
1313
+ padding: 10px 12px;
1314
+ }
1315
+ body.ui-mobile .ln-reader-btn {
1316
+ font-size: 12px;
1317
+ padding: 8px 14px;
1318
+ }
1319
+
1320
+ /* Manga list layout */
1321
+ .manga-list {
1322
+ display: flex;
1323
+ flex-direction: column;
1324
+ gap: 12px;
1325
+ padding: 20px;
1326
+ }
1327
+ .manga-list-item {
1328
+ display: flex;
1329
+ gap: 16px;
1330
+ background: #fff;
1331
+ border-radius: 8px;
1332
+ padding: 12px;
1333
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
1334
+ transition: transform 0.2s, box-shadow 0.2s;
1335
+ text-decoration: none;
1336
+ color: inherit;
1337
+ }
1338
+ .manga-list-item:hover {
1339
+ transform: translateY(-2px);
1340
+ box-shadow: 0 6px 16px rgba(0,0,0,0.15);
1341
+ }
1342
+ .manga-list-thumb {
1343
+ position: relative;
1344
+ flex: 0 0 96px;
1345
+ width: 96px;
1346
+ aspect-ratio: 3 / 4;
1347
+ background: #222;
1348
+ border-radius: 6px;
1349
+ overflow: hidden;
1350
+ }
1351
+ .manga-list-thumb img {
1352
+ display: block;
1353
+ width: 100%;
1354
+ height: 100%;
1355
+ object-fit: cover;
1356
+ }
1357
+ .manga-list-badge {
1358
+ position: absolute;
1359
+ top: 6px;
1360
+ left: 6px;
1361
+ background: rgba(0,0,0,0.8);
1362
+ color: #fff;
1363
+ font-size: 10px;
1364
+ padding: 2px 6px;
1365
+ border-radius: 3px;
1366
+ text-transform: uppercase;
1367
+ }
1368
+ .manga-list-badge.vi {
1369
+ background: rgba(102,126,234,0.95);
1370
+ }
1371
+ .manga-list-info {
1372
+ flex: 1;
1373
+ min-width: 0;
1374
+ display: flex;
1375
+ flex-direction: column;
1376
+ gap: 6px;
1377
+ }
1378
+ .manga-list-name {
1379
+ font-size: 15px;
1380
+ font-weight: 700;
1381
+ line-height: 1.35;
1382
+ }
1383
+ .manga-list-orig {
1384
+ font-size: 12px;
1385
+ color: #888;
1386
+ font-style: italic;
1387
+ }
1388
+ .manga-list-meta {
1389
+ display: flex;
1390
+ flex-wrap: wrap;
1391
+ gap: 6px;
1392
+ }
1393
+ .manga-list-chip {
1394
+ font-size: 11px;
1395
+ color: #555;
1396
+ background: #f1f1f1;
1397
+ padding: 2px 8px;
1398
+ border-radius: 20px;
1399
+ }
1400
+ .ln-tags {
1401
+ display: flex;
1402
+ flex-wrap: wrap;
1403
+ gap: 6px;
1404
+ }
1405
+ .ln-tag {
1406
+ font-size: 11px;
1407
+ color: #667eea;
1408
+ background: #eef0fb;
1409
+ padding: 2px 8px;
1410
+ border-radius: 20px;
1411
+ }
1412
+ body.adult-mode .manga-list-item {
1413
+ background: #2a2a2a;
1414
+ box-shadow: 0 2px 8px rgba(0,0,0,0.5);
1415
+ }
1416
+ body.adult-mode .manga-list-name { color: #eee; }
1417
+ body.adult-mode .manga-list-orig { color: #999; }
1418
+ body.adult-mode .manga-list-chip { color: #ccc; background: #3a3a3a; }
1419
+ body.adult-mode .ln-tag { color: #9aa8ff; background: #333a55; }
1420
+ body.ui-mobile .manga-list-thumb {
1421
+ flex-basis: 80px;
1422
+ width: 80px;
1423
+ }
1424
+ body.ui-mobile .manga-list-item {
1425
+ gap: 12px;
1426
+ padding: 10px;
1427
+ }
1428
+
1429
+ /* ===== Home React (av-*) ===== */
1430
+ body:has(#root) {
1431
+ padding: 0;
1432
+ }
1433
+ #root .container {
1434
+ max-width: 1140px;
1435
+ margin: 0 auto;
1436
+ background: none;
1437
+ box-shadow: none;
1438
+ overflow: visible;
1439
+ }
1440
+
1441
+ /* Hero */
1442
+ .av-hero {
1443
+ background: linear-gradient(135deg, #ff9a9e, #fad0c4);
1444
+ color: #2a1a1c;
1445
+ }
1446
+ .av-hero-adult {
1447
+ background: linear-gradient(135deg, #1a0505, #4a0e0e);
1448
+ color: #ffd9d9;
1449
+ }
1450
+ .av-hero-title {
1451
+ font-size: 42px;
1452
+ font-weight: 800;
1453
+ letter-spacing: 1px;
1454
+ margin: 0;
1455
+ }
1456
+ .av-hero-badge {
1457
+ font-size: 13px;
1458
+ font-weight: 700;
1459
+ padding: 4px 12px;
1460
+ border-radius: 20px;
1461
+ }
1462
+ .av-hero-desc {
1463
+ font-size: 16px;
1464
+ opacity: 0.85;
1465
+ margin-bottom: 0;
1466
+ }
1467
+ .av-search {
1468
+ display: flex;
1469
+ gap: 8px;
1470
+ max-width: 480px;
1471
+ margin: 22px auto 0;
1472
+ }
1473
+ .av-search .form-control {
1474
+ border-radius: 24px;
1475
+ padding: 12px 18px;
1476
+ border: none;
1477
+ box-shadow: 0 4px 14px rgba(0,0,0,0.12);
1478
+ }
1479
+ .av-search-btn {
1480
+ border-radius: 24px;
1481
+ padding: 0 18px;
1482
+ background: #667eea;
1483
+ color: #fff;
1484
+ border: none;
1485
+ }
1486
+ .av-search-btn:hover { background: #5568d8; color: #fff; }
1487
+ .av-random-btn {
1488
+ border-radius: 24px;
1489
+ background: #fff;
1490
+ color: #667eea;
1491
+ border: 1px solid #e0e0ff;
1492
+ font-weight: 600;
1493
+ }
1494
+ .av-random-btn:hover { background: #eef0fb; color: #5568d8; }
1495
+
1496
+ /* Stats */
1497
+ .av-stats-inner {
1498
+ display: flex;
1499
+ flex-wrap: wrap;
1500
+ gap: 8px 18px;
1501
+ justify-content: center;
1502
+ background: #fff;
1503
+ border-radius: 12px;
1504
+ box-shadow: 0 4px 16px rgba(0,0,0,0.08);
1505
+ padding: 12px 20px;
1506
+ font-size: 13px;
1507
+ color: #555;
1508
+ margin-top: -14px;
1509
+ position: relative;
1510
+ z-index: 2;
1511
+ }
1512
+ .av-stat strong { color: #667eea; }
1513
+ body.adult-mode .av-stats-inner { background: #222; color: #bbb; }
1514
+ body.adult-mode .av-stat strong { color: #9aa8ff; }
1515
+
1516
+ /* Nav */
1517
+ .av-nav { gap: 6px; flex-wrap: wrap; }
1518
+ .av-nav .nav-link {
1519
+ border-radius: 20px;
1520
+ padding: 7px 16px;
1521
+ color: #555;
1522
+ font-weight: 600;
1523
+ font-size: 14px;
1524
+ }
1525
+ .av-nav .nav-link.active {
1526
+ background: linear-gradient(135deg, #667eea, #764ba2);
1527
+ color: #fff;
1528
+ }
1529
+ body.adult-mode .av-nav .nav-link { color: #bbb; }
1530
+ body.adult-mode .av-nav .nav-link.active { background: #8a0e0e; }
1531
+
1532
+ /* Movie card */
1533
+ .av-card { display: block; text-decoration: none; color: inherit; }
1534
+ .av-card:hover { color: inherit; }
1535
+ .av-card-thumb {
1536
+ position: relative;
1537
+ border-radius: 10px;
1538
+ overflow: hidden;
1539
+ aspect-ratio: 3 / 4;
1540
+ background: #222;
1541
+ box-shadow: 0 4px 12px rgba(0,0,0,0.12);
1542
+ transition: transform 0.25s;
1543
+ }
1544
+ .av-card:hover .av-card-thumb { transform: translateY(-3px); }
1545
+ .av-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
1546
+ .av-badge {
1547
+ position: absolute;
1548
+ top: 6px;
1549
+ left: 6px;
1550
+ font-size: 10px;
1551
+ font-weight: 700;
1552
+ padding: 2px 7px;
1553
+ border-radius: 6px;
1554
+ background: rgba(102,126,234,0.95);
1555
+ color: #fff;
1556
+ }
1557
+ .av-badge-ep {
1558
+ left: auto;
1559
+ right: 6px;
1560
+ background: rgba(0,0,0,0.7);
1561
+ color: #fff;
1562
+ max-width: calc(100% - 60px);
1563
+ overflow: hidden;
1564
+ text-overflow: ellipsis;
1565
+ white-space: nowrap;
1566
+ }
1567
+ .av-badge-adult { background: #e11; }
1568
+ .av-card-body { padding: 8px 2px 0; }
1569
+ .av-card-title {
1570
+ font-size: 13px;
1571
+ font-weight: 600;
1572
+ margin: 0;
1573
+ line-height: 1.35;
1574
+ display: -webkit-box;
1575
+ -webkit-line-clamp: 2;
1576
+ -webkit-box-orient: vertical;
1577
+ overflow: hidden;
1578
+ min-height: 2.7em;
1579
+ }
1580
+ .av-card-orig {
1581
+ font-size: 11px;
1582
+ color: #888;
1583
+ margin: 3px 0 0;
1584
+ white-space: nowrap;
1585
+ overflow: hidden;
1586
+ text-overflow: ellipsis;
1587
+ }
1588
+
1589
+ /* Manga row */
1590
+ .av-manga-list { display: flex; flex-direction: column; gap: 14px; }
1591
+ .av-manga-row {
1592
+ display: flex;
1593
+ gap: 16px;
1594
+ text-decoration: none;
1595
+ color: inherit;
1596
+ background: #fff;
1597
+ border-radius: 12px;
1598
+ padding: 12px;
1599
+ box-shadow: 0 2px 10px rgba(0,0,0,0.06);
1600
+ transition: all 0.2s;
1601
+ }
1602
+ .av-manga-row:hover {
1603
+ box-shadow: 0 6px 18px rgba(0,0,0,0.12);
1604
+ transform: translateY(-2px);
1605
+ color: inherit;
1606
+ }
1607
+ .av-manga-thumb {
1608
+ position: relative;
1609
+ flex-shrink: 0;
1610
+ width: 72px;
1611
+ border-radius: 8px;
1612
+ overflow: hidden;
1613
+ aspect-ratio: 3 / 4;
1614
+ background: #222;
1615
+ }
1616
+ .av-manga-thumb img { width: 100%; height: 100%; object-fit: cover; }
1617
+ .av-manga-badge {
1618
+ position: absolute;
1619
+ left: 4px;
1620
+ bottom: 4px;
1621
+ font-size: 9px;
1622
+ font-weight: 700;
1623
+ padding: 1px 6px;
1624
+ border-radius: 5px;
1625
+ background: #e22;
1626
+ color: #fff;
1627
+ }
1628
+ .av-manga-badge.vi { background: #28a745; }
1629
+ .av-manga-info { min-width: 0; flex: 1; }
1630
+ .av-manga-name { font-size: 15px; font-weight: 700; margin: 0; }
1631
+ .av-manga-orig { font-size: 12px; color: #888; margin: 2px 0 6px; }
1632
+ .av-manga-meta { display: flex; flex-wrap: wrap; gap: 6px; }
1633
+ .av-chip { font-size: 11px; color: #555; background: #f1f1f1; padding: 2px 9px; border-radius: 20px; }
1634
+ .av-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
1635
+ .av-tag { font-size: 10px; color: #667eea; background: #eef0fb; padding: 2px 8px; border-radius: 20px; }
1636
+ body.adult-mode .av-manga-row { background: #2a2a2a; }
1637
+ body.adult-mode .av-manga-name { color: #eee; }
1638
+ body.adult-mode .av-manga-orig { color: #999; }
1639
+ body.adult-mode .av-chip { color: #ccc; background: #3a3a3a; }
1640
+ body.adult-mode .av-tag { color: #9aa8ff; background: #333a55; }
1641
+
1642
+ /* Light Novel */
1643
+ .av-ln-list { display: flex; flex-direction: column; gap: 12px; }
1644
+ .av-ln-row {
1645
+ display: flex;
1646
+ gap: 16px;
1647
+ cursor: pointer;
1648
+ background: #fff;
1649
+ border-radius: 12px;
1650
+ padding: 12px;
1651
+ box-shadow: 0 2px 10px rgba(0,0,0,0.06);
1652
+ transition: all 0.2s;
1653
+ }
1654
+ .av-ln-row:hover { box-shadow: 0 6px 18px rgba(0,0,0,0.12); transform: translateY(-2px); }
1655
+ .av-ln-cover {
1656
+ flex-shrink: 0;
1657
+ width: 60px;
1658
+ border-radius: 8px;
1659
+ overflow: hidden;
1660
+ aspect-ratio: 3 / 4;
1661
+ background: #222;
1662
+ }
1663
+ .av-ln-cover img { width: 100%; height: 100%; object-fit: cover; }
1664
+ .av-ln-info { flex: 1; min-width: 0; }
1665
+ .av-ln-title { font-size: 15px; font-weight: 700; margin: 0; }
1666
+ .av-ln-author { font-size: 12px; color: #888; margin: 2px 0 6px; }
1667
+ .av-ln-desc { font-size: 12px; color: #666; line-height: 1.5; margin: 6px 0 0; }
1668
+ body.adult-mode .av-ln-row { background: #2a2a2a; }
1669
+ body.adult-mode .av-ln-title { color: #eee; }
1670
+ body.adult-mode .av-ln-author { color: #999; }
1671
+ body.adult-mode .av-ln-desc { color: #bbb; }
1672
+
1673
+ /* LN modal */
1674
+ .av-ln-modal {
1675
+ position: fixed;
1676
+ inset: 0;
1677
+ background: rgba(0,0,0,0.6);
1678
+ z-index: 1050;
1679
+ padding: 20px;
1680
+ overflow-y: auto;
1681
+ }
1682
+ .av-ln-modal .modal-dialog { margin: 0 auto; max-width: 720px; }
1683
+ .av-ln-modal-content { border-radius: 14px; }
1684
+ .av-ln-cover-lg {
1685
+ width: 150px;
1686
+ border-radius: 10px;
1687
+ aspect-ratio: 3 / 4;
1688
+ object-fit: cover;
1689
+ flex-shrink: 0;
1690
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2);
1691
+ }
1692
+
1693
+ /* ============ Dark theme (site-wide, non-adult) ============ */
1694
+ body:not(.adult-mode):not(.reader-mode) {
1695
+ background: #0f0f1a;
1696
+ color: #cfd3e8;
1697
+ }
1698
+
1699
+ body:not(.adult-mode):not(.reader-mode) .container {
1700
+ background: rgba(22,24,43,0.82);
1701
+ backdrop-filter: blur(12px);
1702
+ -webkit-backdrop-filter: blur(12px);
1703
+ box-shadow: 0 4px 24px rgba(0,0,0,0.45);
1704
+ border: 1px solid rgba(140,120,220,0.12);
1705
+ }
1706
+
1707
+ body:not(.adult-mode):not(.reader-mode) .header {
1708
+ background: linear-gradient(135deg, #2b1d54 0%, #6a2c91 60%, #a3419f 100%);
1709
+ color: #fff;
1710
+ }
1711
+
1712
+ body:not(.adult-mode):not(.reader-mode) .info { color: #e4e6f5; }
1713
+ body:not(.adult-mode):not(.reader-mode) .info h1 { color: #fff; }
1714
+ body:not(.adult-mode):not(.reader-mode) .alt-title { color: #b9bede; }
1715
+ body:not(.adult-mode):not(.reader-mode) .info-table td { color: #cfd3e8; }
1716
+ body:not(.adult-mode):not(.reader-mode) .info-table .label { color: #a9aed0; }
1717
+
1718
+ body:not(.adult-mode):not(.reader-mode) .description { color: #cfd3e8; }
1719
+
1720
+ body:not(.adult-mode):not(.reader-mode) .detail-item {
1721
+ background: #1e2136;
1722
+ color: #cfd3e8;
1723
+ }
1724
+
1725
+ body:not(.adult-mode):not(.reader-mode) .episodes {
1726
+ background: #14162a;
1727
+ }
1728
+
1729
+ body:not(.adult-mode):not(.reader-mode) .episodes h2 { color: #e4e6f5; }
1730
+
1731
+ body:not(.adult-mode):not(.reader-mode) .lang-note {
1732
+ background: #2a2f4e;
1733
+ color: #ffd75e;
1734
+ border: 1px solid #4a4f75;
1735
+ }
1736
+
1737
+ body:not(.adult-mode):not(.reader-mode) .episode-card {
1738
+ background: #1e2136;
1739
+ border: 1px solid #2a2e4a;
1740
+ color: #cfd3e8;
1741
+ }
1742
+ body:not(.adult-mode):not(.reader-mode) .episode-card:hover {
1743
+ background: #242850;
1744
+ border-color: #6a2c91;
1745
+ box-shadow: 0 6px 16px rgba(0,0,0,0.35);
1746
+ }
1747
+ body:not(.adult-mode):not(.reader-mode) .ep-num { color: #ff9aa9; }
1748
+
1749
+ body:not(.adult-mode):not(.reader-mode) .back-link {
1750
+ color: #a78bfa;
1751
+ border-color: rgba(140,120,220,0.3);
1752
+ }
1753
+ body:not(.adult-mode):not(.reader-mode) .back-link:hover { color: #ff9aa9; border-color: #6a2c91; }
1754
+
1755
+ body:not(.adult-mode):not(.reader-mode) .footer {
1756
+ color: #9aa0bf;
1757
+ border-top: 1px solid #232640;
1758
+ }
1759
+
1760
+ body:not(.adult-mode):not(.reader-mode) .tag-chip {
1761
+ background: #1e2136;
1762
+ color: #b9bede;
1763
+ border: 1px solid #2a2e4a;
1764
+ }
1765
+ body:not(.adult-mode):not(.reader-mode) .tags-row { border-top-color: #232640; }
1766
+
1767
+ body:not(.adult-mode):not(.reader-mode) .series-header { background: linear-gradient(135deg, #2b1d54, #4a2a7a); }
1768
+ body:not(.adult-mode):not(.reader-mode) .series-info h1 { color: #fff; }
1769
+ body:not(.adult-mode):not(.reader-mode) .series-desc { color: #b9bede; }
1770
+ body:not(.adult-mode):not(.reader-mode) .series-card { background: #1e2136; border-color: #232640; }
1771
+ body:not(.adult-mode):not(.reader-mode) .series-card:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.4); }
1772
+ body:not(.adult-mode):not(.reader-mode) .series-card-body h3 { color: #e4e6f5; }
1773
+ body:not(.adult-mode):not(.reader-mode) .series-card-body p { color: #9aa0bf; }
1774
+
1775
+ body:not(.adult-mode):not(.reader-mode) .movie-card {
1776
+ background: #1e2136;
1777
+ border: 1px solid #2a2e4a;
1778
+ box-shadow: 0 4px 14px rgba(0,0,0,0.35);
1779
+ }
1780
+ body:not(.adult-mode):not(.reader-mode) .movie-card:hover {
1781
+ border-color: #6a2c91;
1782
+ box-shadow: 0 14px 30px rgba(0,0,0,0.45);
1783
+ }
1784
+ body:not(.adult-mode):not(.reader-mode) .movie-title { color: #e4e6f5; }
1785
+ body:not(.adult-mode):not(.reader-mode) .movie-original { color: #9aa0bf; }
1786
+
1787
+ body:not(.adult-mode):not(.reader-mode) .manga-list-item {
1788
+ background: #1e2136;
1789
+ border: 1px solid #2a2e4a;
1790
+ box-shadow: 0 3px 10px rgba(0,0,0,0.35);
1791
+ }
1792
+ body:not(.adult-mode):not(.reader-mode) .manga-list-item:hover { border-color: #6a2c91; }
1793
+ body:not(.adult-mode):not(.reader-mode) .manga-list-name { color: #e4e6f5; }
1794
+ body:not(.adult-mode):not(.reader-mode) .manga-list-orig { color: #9aa0bf; }
1795
+ body:not(.adult-mode):not(.reader-mode) .manga-list-chip { background: #2a2f4e; color: #b9bede; }
1796
+
1797
+ body:not(.adult-mode):not(.reader-mode) .ln-list-item { background: #1e2136; border-color: #2a2e4a; }
1798
+ body:not(.adult-mode):not(.reader-mode) .ln-list-name { color: #e4e6f5; }
1799
+ body:not(.adult-mode):not(.reader-mode) .ln-list-orig { color: #9aa0bf; }
1800
+ body:not(.adult-mode):not(.reader-mode) .ln-tag { background: #2a2f4e; color: #b9bede; }
1801
+
1802
+ body:not(.adult-mode):not(.reader-mode) .pg-btn {
1803
+ background: #1e2136;
1804
+ border: 1px solid #2a2e4a;
1805
+ color: #cfd3e8;
1806
+ }
1807
+ body:not(.adult-mode):not(.reader-mode) .pg-btn:hover:not(.disabled):not(.active) { background: #242850; border-color: #6a2c91; }
1808
+ body:not(.adult-mode):not(.reader-mode) .pg-btn.active { background: linear-gradient(135deg, #2b1d54, #6a2c91); color: #fff; }
1809
+ body:not(.adult-mode):not(.reader-mode) .pg-btn.disabled { color: #5a5f7e; background: #16182b; }
1810
+
1811
+ body:not(.adult-mode):not(.reader-mode) .settings-box { background: #1e2136; }
1812
+ body:not(.adult-mode):not(.reader-mode) .settings-title { color: #e4e6f5; }
1813
+ body:not(.adult-mode):not(.reader-mode) .settings-field input { background: #16182b; color: #e4e6f5; border-color: #2a2e4a; }
1814
+
1815
+ body:not(.adult-mode):not(.reader-mode) .donate-header,
1816
+ body:not(.adult-mode):not(.reader-mode) .donate-desc,
1817
+ body:not(.adult-mode):not(.reader-mode) .donate-content { color: #cfd3e8; }
1818
+
1819
+ body:not(.adult-mode):not(.reader-mode) .gallery-section { background: #14162a; }
1820
+
1821
+ body:not(.adult-mode):not(.reader-mode) .home-nav { background: #16182b; border-bottom-color: #232640; }
1822
+ body:not(.adult-mode):not(.reader-mode) .home-nav a { color: #b9bede; }
1823
+ body:not(.adult-mode):not(.reader-mode) .home-nav a:hover,
1824
+ body:not(.adult-mode):not(.reader-mode) .home-nav a.active { background: #6a2c91; color: #fff; }
1825
+ body:not(.adult-mode):not(.reader-mode) .stats-bar { background: #14162a; border-bottom-color: #232640; color: #9aa0bf; }
1826
+ body:not(.adult-mode):not(.reader-mode) .stats-bar .stat-item span { color: #ff9aa9; }
1827
+ body:not(.adult-mode):not(.reader-mode) .search-box input { background: #16182b; color: #e4e6f5; }
1828
+
1829
+ /* ===== Stage-1 poster-grid wallpaper (site-wide) ===== */
1830
+ body::before {
1831
+ content: "";
1832
+ position: fixed;
1833
+ inset: 0;
1834
+ z-index: -2;
1835
+ pointer-events: none;
1836
+ background-color: #151515;
1837
+ background-image:
1838
+ url("https://phim.nguonc.com/public/images/Film/76.jpg"),
1839
+ url("https://phim.nguonc.com/public/images/Film/666.jpg"),
1840
+ url("https://phim.nguonc.com/public/images/Film/77.jpg"),
1841
+ url("https://phim.nguonc.com/public/images/Film/667.jpg"),
1842
+ url("https://phim.nguonc.com/public/images/Film/259.jpg"),
1843
+ url("https://phim.nguonc.com/public/images/Film/2738.jpg"),
1844
+ url("https://phim.nguonc.com/public/images/Film/3485.jpg"),
1845
+ url("https://phim.nguonc.com/public/images/Film/141438.jpg"),
1846
+ url("https://phim.nguonc.com/public/images/Film/date-a-bullet-thumb.jpg"),
1847
+ url("https://phim.nguonc.com/public/images/Film/3103.jpg"),
1848
+ url("https://phim.nguonc.com/public/images/Post/5/aishiteru-game-wo-owarasetai.jpg"),
1849
+ url("https://phim.nguonc.com/public/images/Film/541.jpg"),
1850
+ url("https://phim.nguonc.com/public/images/Film/536.jpg"),
1851
+ url("https://phim.nguonc.com/public/images/Film/56167.jpg"),
1852
+ url("https://phim.nguonc.com/public/images/Film/56643.jpg"),
1853
+ url("https://phim.nguonc.com/public/images/Film/538.jpg"),
1854
+ url("https://phim.nguonc.com/public/images/Film/540.jpg"),
1855
+ url("https://phim.nguonc.com/public/images/Film/60351.jpg"),
1856
+ url("https://phim.nguonc.com/public/images/Film/2607.jpg"),
1857
+ url("https://phim.nguonc.com/public/images/Film/2661.jpg"),
1858
+ url("https://phim.nguonc.com/public/images/Film/5MgMCnslrxQfOfaq6T5ndkLlcCk.jpg"),
1859
+ url("https://phim.nguonc.com/public/images/Film/134808.jpg"),
1860
+ url("https://phim.nguonc.com/public/images/Film/138379.jpg"),
1861
+ url("https://phim.nguonc.com/public/images/Post/9/seishun-buta-yarou-wa-santa-claus-no-yume-wo-minai-1.jpg");
1862
+ background-size: 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%, 12.5% 33.333%;
1863
+ background-position:
1864
+ 0% 0%,
1865
+ 14.2857% 0%,
1866
+ 28.5714% 0%,
1867
+ 42.8571% 0%,
1868
+ 57.1429% 0%,
1869
+ 71.4286% 0%,
1870
+ 85.7143% 0%,
1871
+ 100% 0%,
1872
+ 0% 50%,
1873
+ 14.2857% 50%,
1874
+ 28.5714% 50%,
1875
+ 42.8571% 50%,
1876
+ 57.1429% 50%,
1877
+ 71.4286% 50%,
1878
+ 85.7143% 50%,
1879
+ 100% 50%,
1880
+ 0% 100%,
1881
+ 14.2857% 100%,
1882
+ 28.5714% 100%,
1883
+ 42.8571% 100%,
1884
+ 57.1429% 100%,
1885
+ 71.4286% 100%,
1886
+ 85.7143% 100%,
1887
+ 100% 100%;
1888
+ background-repeat: no-repeat;
1889
+ filter: brightness(0.42) saturate(0.65);
1890
+ }
1891
+ body::after {
1892
+ content: "";
1893
+ position: fixed;
1894
+ inset: 0;
1895
+ z-index: -1;
1896
+ pointer-events: none;
1897
+ background: linear-gradient(180deg, rgba(21,21,21,0.55), rgba(21,21,21,0.78) 68%, #151515 100%),
1898
+ radial-gradient(120% 90% at 50% 0%, rgba(21,21,21,0) 42%, rgba(21,21,21,0.5) 100%);
1899
  }