|
149 | 149 | } |
150 | 150 |
|
151 | 151 | .scenarios { |
152 | | - margin: 40px 0; |
| 152 | + margin: 80px 0; |
| 153 | + position: relative; |
153 | 154 | } |
154 | 155 |
|
155 | 156 | .scenarios h2 { |
156 | | - color: #333; |
157 | | - margin-bottom: 25px; |
| 157 | + color: #1d1d1f; |
| 158 | + margin-bottom: 32px; |
158 | 159 | text-align: center; |
| 160 | + font-size: 2.5rem; |
| 161 | + font-weight: 600; |
| 162 | + letter-spacing: -0.03em; |
| 163 | + } |
| 164 | + |
| 165 | + .scenario-wrapper { |
| 166 | + position: relative; |
| 167 | + margin: 0 -20px; |
| 168 | + padding: 0 20px; |
159 | 169 | } |
160 | 170 |
|
161 | 171 | .scenario-list { |
162 | | - display: grid; |
163 | | - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); |
| 172 | + display: flex; |
164 | 173 | gap: 20px; |
| 174 | + overflow-x: auto; |
| 175 | + scroll-snap-type: x mandatory; |
| 176 | + scroll-behavior: smooth; |
| 177 | + -webkit-overflow-scrolling: touch; |
| 178 | + padding: 20px 0; |
| 179 | + margin: -20px 0; |
| 180 | + cursor: grab; |
| 181 | + } |
| 182 | + |
| 183 | + .scenario-list::-webkit-scrollbar { |
| 184 | + height: 0; |
| 185 | + } |
| 186 | + |
| 187 | + @media (min-width: 769px) { |
| 188 | + .scenario-list { |
| 189 | + padding: 20px calc(max((100vw - 1200px) / 2, 20px)); |
| 190 | + margin: -20px calc(min((100vw - 1200px) / -2, -20px)); |
| 191 | + } |
165 | 192 | } |
166 | 193 |
|
167 | 194 | .scenario-item { |
|
174 | 201 | position: relative; |
175 | 202 | overflow: hidden; |
176 | 203 | box-shadow: none; |
| 204 | + flex: 0 0 320px; |
| 205 | + scroll-snap-align: start; |
| 206 | + } |
| 207 | + |
| 208 | + @media (max-width: 768px) { |
| 209 | + .scenario-item { |
| 210 | + flex: 0 0 280px; |
| 211 | + padding: 32px; |
| 212 | + } |
177 | 213 | } |
178 | 214 |
|
179 | 215 | .scenario-item::before { |
|
399 | 435 | font-size: 0.9rem; |
400 | 436 | } |
401 | 437 |
|
| 438 | + /* 滾動指示器 */ |
| 439 | + .scroll-indicator { |
| 440 | + display: flex; |
| 441 | + justify-content: center; |
| 442 | + gap: 8px; |
| 443 | + margin-top: 32px; |
| 444 | + padding: 20px; |
| 445 | + } |
| 446 | + |
| 447 | + .scroll-dot { |
| 448 | + width: 8px; |
| 449 | + height: 8px; |
| 450 | + border-radius: 50%; |
| 451 | + background: rgba(0, 0, 0, 0.2); |
| 452 | + transition: all 0.3s ease; |
| 453 | + cursor: pointer; |
| 454 | + } |
| 455 | + |
| 456 | + .scroll-dot.active { |
| 457 | + background: #1d1d1f; |
| 458 | + transform: scale(1.2); |
| 459 | + } |
| 460 | + |
| 461 | + /* 添加滑動提示漸變 */ |
| 462 | + .scenario-wrapper::after { |
| 463 | + content: ''; |
| 464 | + position: absolute; |
| 465 | + top: 20px; |
| 466 | + right: 0; |
| 467 | + bottom: 20px; |
| 468 | + width: 100px; |
| 469 | + background: linear-gradient(to left, #ffffff 0%, transparent 100%); |
| 470 | + pointer-events: none; |
| 471 | + z-index: 1; |
| 472 | + opacity: 1; |
| 473 | + transition: opacity 0.3s ease; |
| 474 | + } |
| 475 | + |
| 476 | + .scenario-wrapper::before { |
| 477 | + content: ''; |
| 478 | + position: absolute; |
| 479 | + top: 20px; |
| 480 | + left: 0; |
| 481 | + bottom: 20px; |
| 482 | + width: 100px; |
| 483 | + background: linear-gradient(to right, #ffffff 0%, transparent 100%); |
| 484 | + pointer-events: none; |
| 485 | + z-index: 1; |
| 486 | + opacity: 0; |
| 487 | + transition: opacity 0.3s ease; |
| 488 | + } |
| 489 | + |
| 490 | + .scenario-wrapper.scrolled::before { |
| 491 | + opacity: 1; |
| 492 | + } |
| 493 | + |
| 494 | + .scenario-wrapper.scrolled-end::after { |
| 495 | + opacity: 0; |
| 496 | + } |
| 497 | + |
402 | 498 | @media (max-width: 768px) { |
403 | 499 | .header h1 { |
404 | 500 | font-size: 2rem; |
|
411 | 507 | .features { |
412 | 508 | grid-template-columns: 1fr; |
413 | 509 | } |
| 510 | + |
| 511 | + .scenario-wrapper::after { |
| 512 | + width: 50px; |
| 513 | + } |
414 | 514 | } |
415 | 515 | </style> |
416 | 516 | <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
@@ -727,8 +827,9 @@ <h3 style="font-size: 1.25rem; font-weight: 600; color: #1d1d1f;">手機掃描 Q |
727 | 827 | </div> |
728 | 828 |
|
729 | 829 | <div class="scenarios"> |
730 | | - <h2 style="font-size: 2rem; font-weight: 600; margin-bottom: 32px;">8 個學習場景</h2> |
731 | | - <div class="scenario-list"> |
| 830 | + <h2>8 個學習場景</h2> |
| 831 | + <div class="scenario-wrapper"> |
| 832 | + <div class="scenario-list"> |
732 | 833 | <div class="scenario-item"> |
733 | 834 | <div class="scenario-number">01</div> |
734 | 835 | <div class="scenario-title">初次對話體驗</div> |
@@ -820,6 +921,17 @@ <h2 style="font-size: 2rem; font-weight: 600; margin-bottom: 32px;">8 個學習 |
820 | 921 | </div> |
821 | 922 | </div> |
822 | 923 | </div> |
| 924 | + <div class="scroll-indicator"> |
| 925 | + <span class="scroll-dot active" data-index="0"></span> |
| 926 | + <span class="scroll-dot" data-index="1"></span> |
| 927 | + <span class="scroll-dot" data-index="2"></span> |
| 928 | + <span class="scroll-dot" data-index="3"></span> |
| 929 | + <span class="scroll-dot" data-index="4"></span> |
| 930 | + <span class="scroll-dot" data-index="5"></span> |
| 931 | + <span class="scroll-dot" data-index="6"></span> |
| 932 | + <span class="scroll-dot" data-index="7"></span> |
| 933 | + </div> |
| 934 | + </div> |
823 | 935 | </div> |
824 | 936 | </div> |
825 | 937 |
|
@@ -1110,6 +1222,105 @@ <h2>❌ 載入失敗</h2> |
1110 | 1222 | colorLight: "#ffffff", |
1111 | 1223 | correctLevel: QRCode.CorrectLevel.H |
1112 | 1224 | }); |
| 1225 | + |
| 1226 | + // 初始化場景滾動 |
| 1227 | + initScenarioScroll(); |
| 1228 | + } |
| 1229 | + |
| 1230 | + // 場景滾動功能 |
| 1231 | + function initScenarioScroll() { |
| 1232 | + const scenarioList = document.querySelector('.scenario-list'); |
| 1233 | + const scrollDots = document.querySelectorAll('.scroll-dot'); |
| 1234 | + const scenarioItems = document.querySelectorAll('.scenario-item'); |
| 1235 | + const scenarioWrapper = document.querySelector('.scenario-wrapper'); |
| 1236 | + |
| 1237 | + if (!scenarioList || !scrollDots.length || !scenarioItems.length) return; |
| 1238 | + |
| 1239 | + // 計算每個項目的寬度和間距 |
| 1240 | + const itemWidth = scenarioItems[0].offsetWidth + 20; // 包含 gap |
| 1241 | + |
| 1242 | + // 更新滾動指示器 |
| 1243 | + function updateScrollIndicator() { |
| 1244 | + const scrollLeft = scenarioList.scrollLeft; |
| 1245 | + const scrollWidth = scenarioList.scrollWidth; |
| 1246 | + const clientWidth = scenarioList.clientWidth; |
| 1247 | + |
| 1248 | + // 計算當前顯示的場景索引 |
| 1249 | + const currentIndex = Math.round(scrollLeft / itemWidth); |
| 1250 | + |
| 1251 | + // 更新指示器狀態 |
| 1252 | + scrollDots.forEach((dot, index) => { |
| 1253 | + if (index === currentIndex) { |
| 1254 | + dot.classList.add('active'); |
| 1255 | + } else { |
| 1256 | + dot.classList.remove('active'); |
| 1257 | + } |
| 1258 | + }); |
| 1259 | + |
| 1260 | + // 檢查滾動位置,控制左右漸變 |
| 1261 | + if (scrollLeft > 10) { |
| 1262 | + scenarioWrapper.classList.add('scrolled'); |
| 1263 | + } else { |
| 1264 | + scenarioWrapper.classList.remove('scrolled'); |
| 1265 | + } |
| 1266 | + |
| 1267 | + if (scrollLeft + clientWidth >= scrollWidth - 10) { |
| 1268 | + scenarioWrapper.classList.add('scrolled-end'); |
| 1269 | + } else { |
| 1270 | + scenarioWrapper.classList.remove('scrolled-end'); |
| 1271 | + } |
| 1272 | + } |
| 1273 | + |
| 1274 | + // 監聽滾動事件 |
| 1275 | + let scrollTimeout; |
| 1276 | + scenarioList.addEventListener('scroll', () => { |
| 1277 | + clearTimeout(scrollTimeout); |
| 1278 | + scrollTimeout = setTimeout(updateScrollIndicator, 50); |
| 1279 | + }); |
| 1280 | + |
| 1281 | + // 點擊指示器滾動到對應場景 |
| 1282 | + scrollDots.forEach((dot, index) => { |
| 1283 | + dot.addEventListener('click', () => { |
| 1284 | + const targetScroll = index * itemWidth; |
| 1285 | + scenarioList.scrollTo({ |
| 1286 | + left: targetScroll, |
| 1287 | + behavior: 'smooth' |
| 1288 | + }); |
| 1289 | + }); |
| 1290 | + }); |
| 1291 | + |
| 1292 | + // 初始化指示器狀態 |
| 1293 | + updateScrollIndicator(); |
| 1294 | + |
| 1295 | + // 添加觸摸滑動支持 |
| 1296 | + let startX = 0; |
| 1297 | + let scrollLeftStart = 0; |
| 1298 | + let isDragging = false; |
| 1299 | + |
| 1300 | + scenarioList.addEventListener('mousedown', (e) => { |
| 1301 | + isDragging = true; |
| 1302 | + startX = e.pageX - scenarioList.offsetLeft; |
| 1303 | + scrollLeftStart = scenarioList.scrollLeft; |
| 1304 | + scenarioList.style.cursor = 'grabbing'; |
| 1305 | + }); |
| 1306 | + |
| 1307 | + scenarioList.addEventListener('mouseleave', () => { |
| 1308 | + isDragging = false; |
| 1309 | + scenarioList.style.cursor = 'grab'; |
| 1310 | + }); |
| 1311 | + |
| 1312 | + scenarioList.addEventListener('mouseup', () => { |
| 1313 | + isDragging = false; |
| 1314 | + scenarioList.style.cursor = 'grab'; |
| 1315 | + }); |
| 1316 | + |
| 1317 | + scenarioList.addEventListener('mousemove', (e) => { |
| 1318 | + if (!isDragging) return; |
| 1319 | + e.preventDefault(); |
| 1320 | + const x = e.pageX - scenarioList.offsetLeft; |
| 1321 | + const walk = (x - startX) * 1.5; |
| 1322 | + scenarioList.scrollLeft = scrollLeftStart - walk; |
| 1323 | + }); |
1113 | 1324 | } |
1114 | 1325 | </script> |
1115 | 1326 | </body> |
|
0 commit comments