tuhbooh commited on
Commit
2be21af
·
verified ·
1 Parent(s): b58a348

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -98
app.py CHANGED
@@ -1,118 +1,90 @@
1
- from flask import Flask
2
 
3
  app = Flask(__name__)
4
 
5
- # Nhúng HTML với nội dung giải thích chi tiết hơn
6
  html_content = """
7
  <!DOCTYPE html>
8
  <html lang="vi">
9
  <head>
10
  <meta charset="UTF-8">
11
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
- <title>Mini Quiz: Ngôn ngữ TP.HCM</title>
13
  <script src="https://cdn.tailwindcss.com"></script>
14
  <style>
15
- @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap');
16
  body {
17
  font-family: 'Plus Jakarta Sans', sans-serif;
18
- background-color: #020617;
19
  color: #f1f5f9;
20
  margin: 0;
21
  display: flex;
22
  align-items: center;
23
  justify-content: center;
24
  min-height: 100vh;
25
- overflow-y: auto;
26
- padding: 20px 0;
27
  }
28
  .mini-card {
29
  background: #1e293b;
30
- border: 1px solid rgba(255, 255, 255, 0.1);
31
- width: 95%;
32
- max-width: 420px;
33
- max-height: 90vh;
 
 
 
 
 
34
  overflow-y: auto;
35
- border-radius: 28px;
36
- position: relative;
37
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
38
- scrollbar-width: thin;
39
  }
40
- .mini-card::-webkit-scrollbar { width: 4px; }
41
- .mini-card::-webkit-scrollbar-thumb { background-color: #334155; border-radius: 10px; }
42
-
43
- .fade-in { animation: fadeIn 0.5s ease-out forwards; }
44
- @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
45
-
46
- .correct-anim { background: #065f46 !important; border-color: #10b981 !important; transform: scale(1.02); }
47
- .wrong-anim { animation: shake 0.4s ease; background: #7f1d1d !important; border-color: #ef4444 !important; }
48
- @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-6px); } 75% { transform: translateX(6px); } }
49
-
50
  .progress-dot { width: 8px; height: 8px; border-radius: 50%; background: #475569; transition: all 0.4s ease; }
51
  .progress-dot.active { background: #3b82f6; width: 24px; border-radius: 12px; }
 
 
52
  </style>
53
  </head>
54
  <body>
55
- <div id="app" class="mini-card p-6 fade-in">
56
- <!-- Header -->
57
- <div class="flex justify-between items-center mb-8 border-b border-slate-700/50 pb-4">
58
  <div>
59
- <h4 class="text-[10px] font-bold uppercase tracking-widest text-blue-500">Tìm hiểu văn hóa</h4>
60
- <div class="text-[10px] text-slate-500 font-semibold" id="step-label">CÂU 1 / 3</div>
61
  </div>
62
- <div id="dots-container" class="flex gap-2">
63
  <div class="progress-dot active"></div><div class="progress-dot"></div><div class="progress-dot"></div>
64
  </div>
65
  </div>
66
-
67
  <div id="quiz-container">
68
- <h2 id="question-text" class="text-xl font-bold mb-6 leading-snug text-white">Đang tải câu hỏi...</h2>
69
- <div id="options-grid" class="space-y-3"></div>
70
  </div>
71
-
72
- <!-- Bảng giải thích chi tiết -->
73
- <div id="explanation-box" class="hidden mt-6 p-5 bg-blue-900/20 rounded-2xl border border-blue-500/30 fade-in">
74
- <div class="flex items-center gap-2 mb-2">
75
- <span class="text-blue-400 text-lg">📝</span>
76
- <span class="text-blue-400 font-bold text-xs uppercase tracking-tighter">Giải thích chi tiết:</span>
77
  </div>
78
- <p id="explanation-text" class="text-[13px] text-slate-300 leading-relaxed text-justify"></p>
79
- <button id="next-btn" class="w-full mt-5 py-3.5 bg-blue-600 hover:bg-blue-500 text-white text-sm font-bold rounded-xl shadow-lg shadow-blue-900/40 transition-all active:scale-95">
80
- Tiếp tục thử thách
81
- </button>
82
  </div>
83
-
84
- <div id="end-screen" class="hidden text-center py-10">
85
- <div class="text-6xl mb-6">🎯</div>
86
- <h3 class="text-2xl font-bold text-white">Kết quả chung cuộc</h3>
87
- <div id="final-score" class="text-6xl font-black text-transparent bg-clip-text bg-gradient-to-br from-blue-400 to-emerald-400 my-6">0/3</div>
88
- <button onclick="location.reload()" class="w-full py-4 bg-slate-800 hover:bg-slate-700 rounded-2xl text-sm font-bold border border-slate-700">Thực hiện lại</button>
89
  </div>
90
  </div>
91
-
92
  <script>
93
  const quizData = [
94
- {
95
- q: "Yếu tố cốt lõi nào hình thành nên sự đa dạng ngôn ngữ tại TP.HCM?",
96
- options: ["Giao thoa của nhiều vùng miền", "Ảnh hưởng từ tiếng nước ngoài", "Chính sách từ chính quyền", "Quá trình biến đổi tự nhiên"],
97
- correct: 0,
98
- info: "TP.HCM là 'vùng đất hứa' tụ hội cư dân từ khắp mọi miền đất nước. Mỗi nhóm người mang theo phương ngữ đặc trưng (Bắc, Trung, Tây Nam Bộ...), khi hòa quyện với nhau đã tạo nên một hệ thống từ vựng cực kỳ phong phú, biến thành phố thành một 'bảo tàng ngôn ngữ sống' đầy thú vị."
99
- },
100
- {
101
- q: "Đặc điểm nổi bật nhất trong lối nói của người Sài Gòn là gì?",
102
- options: ["Sử dụng nhiều từ hán việt", "Bộc trực, hào sảng và giản dị", "Ưa chuộng sự cầu kỳ, lễ nghi", "Nói giảm nói tránh nhiều"],
103
- correct: 1,
104
- info: "Lối sống mở và phóng khoáng đã hình thành nên phong cách giao tiếp 'nói thẳng, nói thật'. Người Sài Gòn thường lược bỏ sự khách sáo rườm rà, thay vào đó là cách xưng hô thân thiện (anh Hai, chị Ba, chú Tư...) và những từ ngữ bộc lộ sự chân thành ngay lập tức."
105
- },
106
- {
107
- q: "Làm thế nào để bảo tồn bản sắc ngôn ngữ thành phố hiệu quả nhất?",
108
- options: ["Hạn chế sử dụng tiếng lóng", "Chuẩn hóa giọng nói duy nhất", "Kết hợp giáo dục và công nghệ số", "Chỉ sử dụng trong văn bản chính thức"],
109
- correct: 2,
110
- info: "Trong kỷ nguyên số, việc bảo tồn không chỉ là giữ gìn trong sách vở. Chúng ta cần 'số hóa' các phương ngữ, xây dựng từ điển điện tử và lồng ghép vào giáo dục thực tiễn. Điều này giúp thế hệ trẻ vừa tiếp cận được công nghệ, vừa không làm mất đi 'cái gốc' tiếng nói của cha ông."
111
- }
112
  ];
113
-
114
  let current = 0, score = 0, active = true;
115
-
116
  function init() {
117
  active = true;
118
  const data = quizData[current];
@@ -120,51 +92,34 @@ html_content = """
120
  const grid = document.getElementById('options-grid');
121
  grid.innerHTML = '';
122
  document.getElementById('explanation-box').classList.add('hidden');
123
- document.getElementById('step-label').textContent = `CÂU ${current + 1} / 3`;
124
-
125
- document.querySelectorAll('.progress-dot').forEach((dot, i) => {
126
- dot.className = i === current ? 'progress-dot active' : 'progress-dot';
127
- });
128
-
129
  data.options.forEach((opt, i) => {
130
  const btn = document.createElement('button');
131
- btn.className = 'w-full p-4 text-left text-sm rounded-2xl border border-slate-700 bg-slate-800/40 flex items-start gap-3 transition-all hover:border-blue-500/50';
132
- btn.innerHTML = `<span class="flex-none w-6 h-6 rounded-lg bg-slate-700 flex items-center justify-center text-[10px] font-bold text-blue-400">${String.fromCharCode(65+i)}</span><span class="text-slate-300 leading-tight">${opt}</span>`;
133
-
134
  btn.onclick = () => {
135
  if(!active) return; active = false;
136
  const buttons = grid.querySelectorAll('button');
137
- if(i === data.correct) {
138
- btn.classList.add('correct-anim');
139
- score++;
140
- } else {
141
- btn.classList.add('wrong-anim');
142
- buttons[data.correct].classList.add('correct-anim');
143
- }
144
  document.getElementById('explanation-text').textContent = data.info;
145
  document.getElementById('explanation-box').classList.remove('hidden');
146
- // Tự động cuộn xuống để thấy giải thích
147
- setTimeout(() => { document.querySelector('.mini-card').scrollTo({top: 500, behavior: 'smooth'}); }, 100);
148
  };
149
  grid.appendChild(btn);
150
  });
151
  }
152
-
153
  document.getElementById('next-btn').onclick = () => {
154
  current++;
155
- if(current < 3) {
156
- document.querySelector('.mini-card').scrollTo({top: 0, behavior: 'smooth'});
157
- init();
158
- } else {
159
  document.getElementById('quiz-container').classList.add('hidden');
160
  document.getElementById('explanation-box').classList.add('hidden');
161
  document.getElementById('end-screen').classList.remove('hidden');
162
  document.getElementById('final-score').textContent = `${score}/3`;
163
- document.getElementById('step-label').parentElement.classList.add('hidden');
164
- document.getElementById('dots-container').classList.add('hidden');
165
  }
166
  };
167
-
168
  init();
169
  </script>
170
  </body>
@@ -173,7 +128,12 @@ html_content = """
173
 
174
  @app.route("/")
175
  def index():
176
- return html_content
 
 
 
 
 
177
 
178
  if __name__ == "__main__":
179
  app.run(host="0.0.0.0", port=7860)
 
1
+ from flask import Flask, make_response
2
 
3
  app = Flask(__name__)
4
 
5
+ # Giao diện Quiz đã được tối ưu
6
  html_content = """
7
  <!DOCTYPE html>
8
  <html lang="vi">
9
  <head>
10
  <meta charset="UTF-8">
11
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
+ <title>Sắc màu ngôn ngữ TP.HCM</title>
13
  <script src="https://cdn.tailwindcss.com"></script>
14
  <style>
15
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
16
  body {
17
  font-family: 'Plus Jakarta Sans', sans-serif;
18
+ background-color: #0f172a;
19
  color: #f1f5f9;
20
  margin: 0;
21
  display: flex;
22
  align-items: center;
23
  justify-content: center;
24
  min-height: 100vh;
25
+ padding: 1rem;
 
26
  }
27
  .mini-card {
28
  background: #1e293b;
29
+ border: 1px solid rgba(255, 255, 255, 0.08);
30
+ width: 100%;
31
+ max-width: 450px;
32
+ max-height: 95vh;
33
+ border-radius: 32px;
34
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
35
+ padding: 2rem;
36
+ display: flex;
37
+ flex-direction: column;
38
  overflow-y: auto;
39
+ scrollbar-width: none;
 
 
 
40
  }
41
+ .mini-card::-webkit-scrollbar { display: none; }
42
+ .option-btn { background: rgba(51, 65, 85, 0.4); border: 1px solid rgba(71, 85, 105, 0.5); transition: all 0.3s ease; }
43
+ .correct-anim { background: #064e3b !important; border-color: #10b981 !important; color: #ecfdf5 !important; }
44
+ .wrong-anim { background: #7f1d1d !important; border-color: #ef4444 !important; }
 
 
 
 
 
 
45
  .progress-dot { width: 8px; height: 8px; border-radius: 50%; background: #475569; transition: all 0.4s ease; }
46
  .progress-dot.active { background: #3b82f6; width: 24px; border-radius: 12px; }
47
+ #explanation-box { animation: slideIn 0.4s ease-out forwards; }
48
+ @keyframes slideIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
49
  </style>
50
  </head>
51
  <body>
52
+ <div id="app" class="mini-card">
53
+ <div class="flex justify-between items-center mb-6 pb-4 border-b border-slate-700/50">
 
54
  <div>
55
+ <p class="text-[10px] font-bold uppercase tracking-widest text-blue-500 mb-1">Hành trình ngôn ngữ</p>
56
+ <h4 id="step-label" class="text-xs font-semibold text-slate-400 uppercase">Câu 1 / 3</h4>
57
  </div>
58
+ <div id="dots-container" class="flex gap-1.5">
59
  <div class="progress-dot active"></div><div class="progress-dot"></div><div class="progress-dot"></div>
60
  </div>
61
  </div>
 
62
  <div id="quiz-container">
63
+ <h2 id="question-text" class="text-xl font-bold leading-tight mb-6 text-white">Đang tải...</h2>
64
+ <div id="options-grid" class="space-y-3 mb-6"></div>
65
  </div>
66
+ <div id="explanation-box" class="hidden p-5 bg-blue-600/10 rounded-2xl border border-blue-500/20">
67
+ <div class="flex items-center gap-2 mb-3">
68
+ <span class="text-lg">💡</span>
69
+ <span class="text-[10px] font-black uppercase tracking-widest text-blue-400">Phân tích chuyên sâu</span>
 
 
70
  </div>
71
+ <p id="explanation-text" class="text-[13.5px] leading-relaxed text-slate-300 text-justify italic font-medium"></p>
72
+ <button id="next-btn" class="w-full mt-6 py-4 bg-blue-600 hover:bg-blue-500 text-white text-sm font-bold rounded-xl shadow-lg transition-all active:scale-95">Tiếp tục</button>
 
 
73
  </div>
74
+ <div id="end-screen" class="hidden text-center py-8">
75
+ <div class="text-6xl mb-6">🏆</div>
76
+ <h3 class="text-2xl font-bold text-white mb-2">Hoàn thành!</h3>
77
+ <div id="final-score" class="text-7xl font-black text-blue-400 mb-10">0/3</div>
78
+ <button onclick="location.reload()" class="w-full py-4 bg-slate-800 border border-slate-700 rounded-2xl font-bold text-sm">Làm lại</button>
 
79
  </div>
80
  </div>
 
81
  <script>
82
  const quizData = [
83
+ { q: "Tại sao ngôn ngữ TP.HCM lại có sự pha trộn đặc trưng đến vậy?", options: ["Do vị trí địa lý", "Do chính sách", "Do giao thoa cư dân", "Do phương Tây"], correct: 2, info: "Sài Gòn là vùng đất 'mở', đón nhận di cư từ khắp miền Bắc, Trung, Tây. Sự cộng hưởng này t��o ra hệ thống từ vựng phong phú và bản sắc riêng không trộn lẫn." },
84
+ { q: "Tính cách 'hào sảng' ảnh hưởng thế nào đến cách nói chuyện?", options: ["Cầu kỳ, lễ nghĩa", "Bộc trực, giản dị", "Xa cách, giữ kẽ", "Dùng từ bác học"], correct: 1, info: "Lối nói 'nghĩ sao nói vậy' phản chiếu sự hào sảng. Họ chuộng từ thân mật (anh Hai, chị Ba, cưng...) giúp xóa tan khoảng cách ngay lần đầu gặp mặt." },
85
+ { q: "Hướng đi bền vững nhất để giữ gìn ngôn ngữ Sài Gòn?", options: ["Dùng trong gia đình", "Cấm tiếng lóng", "Giáo dục & Công nghệ", "Cố định phát âm"], correct: 2, info: "Bảo tồn không phải là giữ khư khư, mà là làm cho ngôn ngữ đó tiếp tục sống trên podcast, phim ảnh để thế hệ trẻ luôn yêu mến nó." }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  ];
 
87
  let current = 0, score = 0, active = true;
 
88
  function init() {
89
  active = true;
90
  const data = quizData[current];
 
92
  const grid = document.getElementById('options-grid');
93
  grid.innerHTML = '';
94
  document.getElementById('explanation-box').classList.add('hidden');
95
+ document.getElementById('step-label').textContent = `Câu ${current + 1} / 3`;
96
+ document.querySelectorAll('.progress-dot').forEach((dot, i) => dot.className = i === current ? 'progress-dot active' : 'progress-dot');
 
 
 
 
97
  data.options.forEach((opt, i) => {
98
  const btn = document.createElement('button');
99
+ btn.className = 'option-btn w-full p-4 text-left text-sm rounded-2xl flex gap-3';
100
+ btn.innerHTML = `<span class="flex-none w-6 h-6 rounded-lg bg-slate-700/50 flex items-center justify-center text-[10px] font-bold text-blue-400">${String.fromCharCode(65+i)}</span><span class="text-slate-300 leading-tight">${opt}</span>`;
 
101
  btn.onclick = () => {
102
  if(!active) return; active = false;
103
  const buttons = grid.querySelectorAll('button');
104
+ if(i === data.correct) { btn.classList.add('correct-anim'); score++; }
105
+ else { btn.classList.add('wrong-anim'); buttons[data.correct].classList.add('correct-anim'); }
 
 
 
 
 
106
  document.getElementById('explanation-text').textContent = data.info;
107
  document.getElementById('explanation-box').classList.remove('hidden');
108
+ setTimeout(() => { document.querySelector('.mini-card').scrollTo({top: 1000, behavior: 'smooth'}); }, 100);
 
109
  };
110
  grid.appendChild(btn);
111
  });
112
  }
 
113
  document.getElementById('next-btn').onclick = () => {
114
  current++;
115
+ if(current < 3) { document.querySelector('.mini-card').scrollTo({top: 0, behavior: 'smooth'}); init(); }
116
+ else {
 
 
117
  document.getElementById('quiz-container').classList.add('hidden');
118
  document.getElementById('explanation-box').classList.add('hidden');
119
  document.getElementById('end-screen').classList.remove('hidden');
120
  document.getElementById('final-score').textContent = `${score}/3`;
 
 
121
  }
122
  };
 
123
  init();
124
  </script>
125
  </body>
 
128
 
129
  @app.route("/")
130
  def index():
131
+ # Tạo response và thêm Header để cho phép Canva (hoặc bất kỳ site nào) nhúng vào
132
+ response = make_response(html_content)
133
+ # Loại bỏ các hạn chế về Iframe
134
+ response.headers['X-Frame-Options'] = 'ALLOWALL'
135
+ response.headers['Content-Security-Policy'] = "frame-ancestors *;"
136
+ return response
137
 
138
  if __name__ == "__main__":
139
  app.run(host="0.0.0.0", port=7860)