12716 lines
558 KiB
HTML
12716 lines
558 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Soroban Flashcards</title>
|
||
<style>
|
||
body {
|
||
font-family:
|
||
DejaVu Sans,
|
||
sans-serif;
|
||
margin: 0;
|
||
padding: 0;
|
||
background: #f8fafc;
|
||
line-height: 1.6;
|
||
color: #2d3748;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 0;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* Beautiful Header */
|
||
.main-header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
text-align: center;
|
||
padding: 60px 40px 40px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.main-header::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/><circle cx="50" cy="10" r="0.5" fill="white" opacity="0.15"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
|
||
pointer-events: none;
|
||
}
|
||
|
||
.main-header h1 {
|
||
font-size: 3.5rem;
|
||
font-weight: 700;
|
||
margin: 0 0 15px 0;
|
||
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||
letter-spacing: -0.02em;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.main-header p {
|
||
font-size: 1.3rem;
|
||
margin: 0;
|
||
opacity: 0.95;
|
||
font-weight: 300;
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
line-height: 1.6;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Modern Navigation */
|
||
.page-nav {
|
||
position: sticky;
|
||
top: 0;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(10px);
|
||
border-bottom: 1px solid rgba(102, 126, 234, 0.2);
|
||
z-index: 1000;
|
||
padding: 20px 40px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.nav-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
justify-content: center;
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.nav-btn {
|
||
padding: 12px 24px;
|
||
border: none;
|
||
background: rgba(102, 126, 234, 0.1);
|
||
color: #4a5568;
|
||
border-radius: 50px;
|
||
cursor: pointer;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
text-decoration: none;
|
||
position: relative;
|
||
overflow: hidden;
|
||
min-width: 140px;
|
||
text-align: center;
|
||
}
|
||
|
||
.nav-btn::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
left: -100%;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(
|
||
90deg,
|
||
transparent,
|
||
rgba(255, 255, 255, 0.4),
|
||
transparent
|
||
);
|
||
transition: left 0.6s;
|
||
}
|
||
|
||
.nav-btn:hover {
|
||
background: rgba(102, 126, 234, 0.15);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2);
|
||
}
|
||
|
||
.nav-btn:hover::before {
|
||
left: 100%;
|
||
}
|
||
|
||
.nav-btn.active {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.nav-btn.active:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 12px 35px rgba(102, 126, 234, 0.5);
|
||
}
|
||
|
||
/* Content Area */
|
||
.content-area {
|
||
background: white;
|
||
margin: 0;
|
||
padding: 0;
|
||
min-height: calc(100vh - 200px);
|
||
}
|
||
|
||
/* Section Styles */
|
||
.page-section {
|
||
display: none;
|
||
animation: fadeInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||
padding: 60px 40px 80px;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.page-section.active {
|
||
display: block;
|
||
}
|
||
|
||
@keyframes fadeInUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(30px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.section-header {
|
||
text-align: center;
|
||
margin-bottom: 60px;
|
||
position: relative;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 2.8rem;
|
||
font-weight: 700;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
margin: 0 0 15px 0;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
.section-subtitle {
|
||
color: #718096;
|
||
font-size: 1.2rem;
|
||
margin: 0;
|
||
font-weight: 400;
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.section-header::after {
|
||
content: "";
|
||
display: block;
|
||
width: 60px;
|
||
height: 4px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
margin: 30px auto 0;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.header h1 {
|
||
color: #333;
|
||
font-size: 2.5em;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.header p {
|
||
color: #666;
|
||
font-size: 1.2em;
|
||
}
|
||
|
||
.cards-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||
gap: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.flashcard {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||
transition:
|
||
transform 0.2s ease,
|
||
box-shadow 0.2s ease;
|
||
cursor: pointer;
|
||
position: relative;
|
||
min-height: 220px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.flashcard:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.abacus-container {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin: 10px 0;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.abacus-container svg {
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.numeral {
|
||
font-size: 48pt;
|
||
font-weight: bold;
|
||
color: #333;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
background: rgba(255, 255, 255, 0.95);
|
||
padding: 15px 25px;
|
||
border-radius: 8px;
|
||
border: 2px solid #ddd;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
z-index: 10;
|
||
}
|
||
|
||
.flashcard:hover .numeral {
|
||
opacity: 1;
|
||
}
|
||
|
||
.card-number {
|
||
position: absolute;
|
||
top: 10px;
|
||
left: 10px;
|
||
font-size: 0.8em;
|
||
color: #999;
|
||
background: rgba(255, 255, 255, 0.8);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.instructions {
|
||
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||
padding: 40px;
|
||
border-radius: 20px;
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||
margin: 50px 0;
|
||
text-align: left;
|
||
position: relative;
|
||
border: 1px solid rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.instructions::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(
|
||
135deg,
|
||
rgba(102, 126, 234, 0.03) 0%,
|
||
rgba(118, 75, 162, 0.03) 100%
|
||
);
|
||
border-radius: 20px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.instructions h3 {
|
||
color: #2d3748;
|
||
margin-top: 0;
|
||
margin-bottom: 20px;
|
||
font-size: 1.5rem;
|
||
font-weight: 600;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.instructions h4 {
|
||
color: #4a5568;
|
||
margin-top: 30px;
|
||
margin-bottom: 15px;
|
||
font-size: 1.2rem;
|
||
font-weight: 600;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.instructions p {
|
||
position: relative;
|
||
z-index: 1;
|
||
line-height: 1.7;
|
||
color: #4a5568;
|
||
}
|
||
|
||
.instructions ul {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin: 20px 0;
|
||
padding-left: 0;
|
||
list-style: none;
|
||
}
|
||
|
||
.instructions li {
|
||
padding: 12px 0;
|
||
padding-left: 35px;
|
||
position: relative;
|
||
color: #4a5568;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.instructions li::before {
|
||
content: "✓";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 12px;
|
||
width: 20px;
|
||
height: 20px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.instructions p {
|
||
position: relative;
|
||
z-index: 1;
|
||
line-height: 1.7;
|
||
color: #4a5568;
|
||
}
|
||
|
||
.stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
margin-top: 40px;
|
||
padding: 0;
|
||
}
|
||
|
||
.stats div {
|
||
background: white;
|
||
padding: 25px;
|
||
border-radius: 15px;
|
||
font-size: 14px;
|
||
border: 1px solid rgba(102, 126, 234, 0.1);
|
||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition:
|
||
transform 0.3s ease,
|
||
box-shadow 0.3s ease;
|
||
text-align: left;
|
||
}
|
||
|
||
.stats div::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 4px;
|
||
height: 100%;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
}
|
||
|
||
.stats div:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.15);
|
||
}
|
||
|
||
.stats div strong {
|
||
color: #2d3748;
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Tutorial Styles */
|
||
.tutorial-content {
|
||
max-width: 1000px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.tutorial-step {
|
||
background: white;
|
||
margin-bottom: 40px;
|
||
border-radius: 20px;
|
||
overflow: hidden;
|
||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.08);
|
||
border: 1px solid rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.step-header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
padding: 25px 40px;
|
||
color: white;
|
||
}
|
||
|
||
.step-header h3 {
|
||
margin: 0;
|
||
font-size: 1.5rem;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.step-number {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
width: 35px;
|
||
height: 35px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 700;
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.step-content {
|
||
padding: 40px;
|
||
}
|
||
|
||
.tutorial-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 400px;
|
||
gap: 40px;
|
||
align-items: center;
|
||
}
|
||
|
||
.tutorial-text h4 {
|
||
color: #2d3748;
|
||
margin-top: 25px;
|
||
margin-bottom: 15px;
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.tutorial-text ol,
|
||
.tutorial-text ul {
|
||
color: #4a5568;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.tutorial-text li {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.tutorial-visual {
|
||
text-align: center;
|
||
}
|
||
|
||
.example-abacus {
|
||
background: #f8fafc;
|
||
padding: 20px;
|
||
border-radius: 12px;
|
||
margin-bottom: 15px;
|
||
border: 2px solid rgba(102, 126, 234, 0.1);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 120px;
|
||
}
|
||
|
||
.example-abacus svg {
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.example-label {
|
||
font-weight: 600;
|
||
color: #2d3748;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.example-breakdown {
|
||
font-size: 0.85rem;
|
||
color: #718096;
|
||
margin-top: 5px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.examples-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 25px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.example-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.example-item .example-abacus {
|
||
margin-bottom: 12px;
|
||
min-height: 100px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.practice-hint {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 10px 15px;
|
||
border-radius: 8px;
|
||
font-size: 0.9rem;
|
||
margin-top: 10px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.reveal-btn {
|
||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 24px;
|
||
border-radius: 8px;
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
margin-top: 15px;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
|
||
}
|
||
|
||
.reveal-btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 25px rgba(40, 167, 69, 0.4);
|
||
}
|
||
|
||
.reveal-btn:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.revealed {
|
||
animation: revealAnimation 0.5s ease-out;
|
||
}
|
||
|
||
@keyframes revealAnimation {
|
||
0% {
|
||
background: #fff3cd;
|
||
transform: scale(1.05);
|
||
}
|
||
100% {
|
||
background: #f8fafc;
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.tutorial-grid {
|
||
grid-template-columns: 1fr;
|
||
gap: 25px;
|
||
}
|
||
|
||
.step-content {
|
||
padding: 25px;
|
||
}
|
||
|
||
.step-header {
|
||
padding: 20px 25px;
|
||
}
|
||
|
||
.examples-grid {
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.cards-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
.flashcard {
|
||
min-height: 120px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.numeral {
|
||
font-size: calc(48pt * 0.8);
|
||
padding: 10px 20px;
|
||
}
|
||
}
|
||
|
||
/* Quiz Styling */
|
||
.quiz-section {
|
||
background: #f8f9fa;
|
||
border-radius: 12px;
|
||
padding: 30px;
|
||
margin: 30px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.quiz-section h2 {
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.quiz-controls {
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.control-group {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.control-group label {
|
||
display: block;
|
||
font-weight: bold;
|
||
margin-bottom: 10px;
|
||
color: #555;
|
||
}
|
||
|
||
.count-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.count-btn {
|
||
background: white;
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.count-btn:hover {
|
||
border-color: #4a90e2;
|
||
background: #f0f7ff;
|
||
}
|
||
|
||
.count-btn.active {
|
||
background: #4a90e2;
|
||
color: white;
|
||
border-color: #4a90e2;
|
||
}
|
||
|
||
.slider-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.slider-container input[type="range"] {
|
||
width: 200px;
|
||
height: 6px;
|
||
border-radius: 3px;
|
||
background: #ddd;
|
||
outline: none;
|
||
-webkit-appearance: none;
|
||
}
|
||
|
||
.slider-container input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: #4a90e2;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.slider-value {
|
||
font-weight: bold;
|
||
color: #4a90e2;
|
||
min-width: 50px;
|
||
}
|
||
|
||
.quiz-start-btn {
|
||
background: #28a745;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 15px 30px;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.quiz-start-btn:hover {
|
||
background: #218838;
|
||
}
|
||
|
||
/* Quiz Game Area */
|
||
.quiz-game {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.quiz-progress {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
height: 8px;
|
||
background: #ddd;
|
||
border-radius: 4px;
|
||
margin: 0 auto 10px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: #4a90e2;
|
||
transition: width 0.3s ease;
|
||
width: 0%;
|
||
}
|
||
|
||
.progress-text {
|
||
color: #666;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.quiz-display {
|
||
position: relative;
|
||
min-height: 300px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.quiz-flashcard {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||
width: min(85vw, 700px);
|
||
height: min(50vh, 400px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 0 auto;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
/* Ensure quiz game section fits in viewport */
|
||
#quiz-game {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* Responsive adjustments for smaller screens */
|
||
@media (max-height: 600px) {
|
||
.quiz-flashcard {
|
||
height: min(40vh, 300px);
|
||
padding: 15px;
|
||
}
|
||
}
|
||
|
||
@media (max-height: 500px) {
|
||
.quiz-flashcard {
|
||
height: min(35vh, 250px);
|
||
padding: 10px;
|
||
}
|
||
|
||
#quiz-game {
|
||
min-height: auto;
|
||
padding: 10px;
|
||
}
|
||
}
|
||
|
||
.quiz-flashcard svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
}
|
||
|
||
.quiz-flashcard.pulse {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.quiz-flashcard.card-exit-warning {
|
||
border-color: #dc3545;
|
||
box-shadow: 0 0 15px rgba(220, 53, 69, 0.4);
|
||
}
|
||
|
||
.quiz-flashcard.card-fade-out {
|
||
opacity: 0.3;
|
||
transform: scale(0.95);
|
||
transition: all 0.1s ease;
|
||
}
|
||
|
||
.countdown {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
font-size: 48px;
|
||
font-weight: bold;
|
||
color: #4a90e2;
|
||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||
z-index: 10;
|
||
}
|
||
|
||
.countdown.ready {
|
||
color: #28a745;
|
||
}
|
||
|
||
.countdown.go {
|
||
color: #ffc107;
|
||
}
|
||
|
||
.countdown.new-card-flash {
|
||
color: #17a2b8;
|
||
font-size: 24px;
|
||
animation: flashIn 0.15s ease;
|
||
}
|
||
|
||
@keyframes flashIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) scale(0.8);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translate(-50%, -50%) scale(1);
|
||
}
|
||
}
|
||
|
||
/* Quiz Input */
|
||
.quiz-input {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
max-width: 700px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.quiz-stats {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 30px;
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
background: #f8f9fa;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.stats-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.stats-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.stats-item span:last-child {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #2c5f76;
|
||
}
|
||
|
||
.smart-input-container {
|
||
position: relative;
|
||
margin: 40px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.smart-input-prompt {
|
||
font-size: 16px;
|
||
color: #7a8695;
|
||
margin-bottom: 15px;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.number-display {
|
||
min-height: 60px;
|
||
padding: 20px;
|
||
font-size: 32px;
|
||
font-family: "Courier New", "Monaco", monospace;
|
||
text-align: center;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
letter-spacing: 3px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.current-typing {
|
||
display: inline-block;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.number-display.correct .current-typing {
|
||
color: #28a745;
|
||
animation: successPulse 0.5s ease;
|
||
}
|
||
|
||
.number-display.incorrect .current-typing {
|
||
color: #dc3545;
|
||
animation: errorShake 0.5s ease;
|
||
}
|
||
|
||
@keyframes successPulse {
|
||
0%,
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
transform: scale(1.05);
|
||
}
|
||
}
|
||
|
||
@keyframes errorShake {
|
||
0%,
|
||
100% {
|
||
transform: translateX(0);
|
||
}
|
||
25% {
|
||
transform: translateX(-10px);
|
||
}
|
||
75% {
|
||
transform: translateX(10px);
|
||
}
|
||
}
|
||
|
||
.input-feedback {
|
||
margin-top: 10px;
|
||
font-weight: bold;
|
||
min-height: 20px;
|
||
}
|
||
|
||
.input-feedback.success {
|
||
color: #28a745;
|
||
}
|
||
|
||
.input-feedback.error {
|
||
color: #dc3545;
|
||
}
|
||
|
||
.found-numbers {
|
||
margin: 30px 0;
|
||
min-height: 60px;
|
||
}
|
||
|
||
.found-number {
|
||
display: inline-block;
|
||
margin: 5px;
|
||
padding: 10px 15px;
|
||
background: #28a745;
|
||
color: white;
|
||
border-radius: 8px;
|
||
font-weight: bold;
|
||
font-size: 18px;
|
||
animation: slideIn 0.3s ease;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.finish-btn {
|
||
background: #2c5f76;
|
||
color: white;
|
||
border: none;
|
||
padding: 15px 30px;
|
||
font-size: 18px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.finish-btn:hover {
|
||
background: #1e4a5c;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.quiz-finish-buttons {
|
||
display: flex;
|
||
gap: 15px;
|
||
justify-content: center;
|
||
margin-top: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.give-up-btn {
|
||
background: #17a2b8;
|
||
color: white;
|
||
border: none;
|
||
padding: 15px 30px;
|
||
font-size: 18px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.give-up-btn:hover {
|
||
background: #138496;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.input-container {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.quiz-input textarea {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
height: 120px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
font-size: 16px;
|
||
resize: vertical;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.quiz-input textarea:focus {
|
||
outline: none;
|
||
border-color: #4a90e2;
|
||
}
|
||
|
||
.quiz-input button {
|
||
background: #4a90e2;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 12px 24px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.quiz-input button:hover {
|
||
background: #357abd;
|
||
}
|
||
|
||
/* Quiz Results */
|
||
.quiz-results {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
max-width: 700px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.score-display {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 40px;
|
||
margin: 30px 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.score-circle {
|
||
width: 120px;
|
||
height: 120px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #4a90e2, #357abd);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.score-details {
|
||
text-align: left;
|
||
}
|
||
|
||
.score-details p {
|
||
margin: 8px 0;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.results-breakdown {
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 30px 0;
|
||
text-align: left;
|
||
}
|
||
|
||
.result-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 10px 0;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.result-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.result-correct {
|
||
color: #28a745;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.result-incorrect {
|
||
color: #dc3545;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.quiz-actions {
|
||
margin-top: 30px;
|
||
display: flex;
|
||
gap: 15px;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.quiz-actions button {
|
||
padding: 12px 24px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
}
|
||
|
||
#retry-quiz {
|
||
background: #ffc107;
|
||
color: #333;
|
||
}
|
||
|
||
#retry-quiz:hover {
|
||
background: #e0a800;
|
||
}
|
||
|
||
#back-to-cards {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
#back-to-cards:hover {
|
||
background: #545b62;
|
||
}
|
||
|
||
/* End Game Button */
|
||
.end-game-btn {
|
||
background: #dc3545;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 8px 16px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
}
|
||
|
||
.end-game-btn:hover {
|
||
background: #c82333;
|
||
}
|
||
|
||
/* Quiz Header for Game Mode */
|
||
.quiz-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding: 15px 20px;
|
||
background: rgba(74, 144, 226, 0.1);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.quiz-header .quiz-progress {
|
||
flex: 1;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.quiz-section {
|
||
padding: 20px;
|
||
}
|
||
|
||
.count-buttons {
|
||
gap: 8px;
|
||
}
|
||
|
||
.count-btn {
|
||
padding: 8px 16px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.score-display {
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
.score-details {
|
||
text-align: center;
|
||
}
|
||
|
||
.countdown {
|
||
font-size: 36px;
|
||
}
|
||
|
||
.sorting-section {
|
||
padding: 20px;
|
||
}
|
||
|
||
.sorting-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||
gap: 10px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.sort-card {
|
||
padding: 10px;
|
||
}
|
||
|
||
.sort-count-btn {
|
||
padding: 8px 16px;
|
||
font-size: 14px;
|
||
margin: 2px;
|
||
}
|
||
|
||
.sort-start-btn,
|
||
.sort-check-btn,
|
||
.sort-reveal-btn,
|
||
.sort-new-btn {
|
||
padding: 10px 16px;
|
||
font-size: 14px;
|
||
margin: 5px;
|
||
}
|
||
|
||
.sorting-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.challenges-grid {
|
||
grid-template-columns: 1fr;
|
||
gap: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.challenge-category {
|
||
padding: 20px;
|
||
}
|
||
|
||
.challenge-card {
|
||
min-height: 80px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.challenge-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 95%;
|
||
max-height: 95vh;
|
||
}
|
||
|
||
.modal-header {
|
||
padding: 15px 20px;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.modal-header h2 {
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
|
||
/* Card Sorting Styling */
|
||
.sorting-section {
|
||
background: #e8f4f8;
|
||
border-radius: 12px;
|
||
padding: 30px;
|
||
margin: 30px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.sorting-section h2 {
|
||
color: #2c5f76;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.sorting-controls {
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.sort-count-btn {
|
||
background: #ffffff;
|
||
color: #2c5f76;
|
||
border: 2px solid #2c5f76;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
margin: 0 5px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.sort-count-btn:hover {
|
||
background: #2c5f76;
|
||
color: white;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.sort-count-btn.active {
|
||
background: #2c5f76;
|
||
color: white;
|
||
}
|
||
|
||
.sorting-actions {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
/* Setup modal body for sticky positioning */
|
||
.modal-body {
|
||
position: relative;
|
||
}
|
||
|
||
/* Sticky header within modal body */
|
||
.sorting-header {
|
||
position: sticky;
|
||
top: 0;
|
||
background: #f8f9fa;
|
||
border-bottom: 2px solid #e1e5e9;
|
||
border-radius: 8px 8px 0 0;
|
||
z-index: 50;
|
||
padding: 12px 20px;
|
||
margin: 0 -20px 15px -20px; /* Extend to edges of modal body */
|
||
}
|
||
|
||
.sorting-header-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
}
|
||
|
||
.sorting-status-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
|
||
.sorting-timer {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #2c5f76;
|
||
}
|
||
|
||
.sorting-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.header-btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.check-btn {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
|
||
.check-btn:hover {
|
||
background: #218838;
|
||
}
|
||
|
||
.reveal-btn {
|
||
background: #ffc107;
|
||
color: #212529;
|
||
}
|
||
|
||
.reveal-btn:hover {
|
||
background: #e0a800;
|
||
}
|
||
|
||
.new-btn {
|
||
background: #007bff;
|
||
color: white;
|
||
}
|
||
|
||
.new-btn:hover {
|
||
background: #0056b3;
|
||
}
|
||
|
||
.end-btn {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
.end-btn:hover {
|
||
background: #c82333;
|
||
}
|
||
|
||
.sorting-game-actions {
|
||
display: none; /* Hide old action buttons */
|
||
}
|
||
|
||
/* Score Modal Styles */
|
||
.score-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
z-index: 2000;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
animation: fadeIn 0.3s ease;
|
||
}
|
||
|
||
.score-modal-content {
|
||
background: white;
|
||
border-radius: 12px;
|
||
max-width: 600px;
|
||
width: 90%;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||
animation: slideIn 0.3s ease;
|
||
}
|
||
|
||
.score-modal-header {
|
||
padding: 20px 30px;
|
||
border-bottom: 1px solid #e1e5e9;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.score-modal-header h3 {
|
||
margin: 0;
|
||
color: #333;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.score-modal-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 32px;
|
||
cursor: pointer;
|
||
color: #666;
|
||
padding: 0;
|
||
width: 40px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 20px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.score-modal-close:hover {
|
||
background: #f8f9fa;
|
||
color: #333;
|
||
}
|
||
|
||
.score-modal-body {
|
||
padding: 30px;
|
||
}
|
||
|
||
.score-modal-body .feedback-score {
|
||
font-size: 48px;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
margin-bottom: 20px;
|
||
color: #28a745;
|
||
}
|
||
|
||
.score-modal-body .feedback-message {
|
||
font-size: 20px;
|
||
text-align: center;
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
}
|
||
|
||
.score-modal-body .feedback-time {
|
||
font-size: 18px;
|
||
text-align: center;
|
||
margin-bottom: 25px;
|
||
color: #666;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.score-modal-body .feedback-breakdown {
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.score-modal-body .score-component {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #e9ecef;
|
||
}
|
||
|
||
.score-modal-body .score-component:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.score-modal-body .component-label {
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.score-modal-body .component-score {
|
||
font-weight: bold;
|
||
color: #007bff;
|
||
}
|
||
|
||
.score-modal-body .component-weight {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.score-modal-body .feedback-details {
|
||
background: #e9ecef;
|
||
padding: 15px;
|
||
border-radius: 6px;
|
||
text-align: center;
|
||
font-size: 16px;
|
||
color: #495057;
|
||
}
|
||
|
||
.score-modal-footer {
|
||
padding: 20px 30px;
|
||
border-top: 1px solid #e1e5e9;
|
||
display: flex;
|
||
gap: 15px;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.modal-btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.new-game-btn {
|
||
background: #007bff;
|
||
color: white;
|
||
}
|
||
|
||
.new-game-btn:hover {
|
||
background: #0056b3;
|
||
}
|
||
|
||
.close-btn {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.close-btn:hover {
|
||
background: #545b62;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateY(-50px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateY(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.sort-start-btn,
|
||
.sort-check-btn,
|
||
.sort-reveal-btn,
|
||
.sort-new-btn {
|
||
background: #2c5f76;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 12px 24px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
margin: 0 10px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.sort-start-btn:hover,
|
||
.sort-check-btn:hover,
|
||
.sort-reveal-btn:hover,
|
||
.sort-new-btn:hover {
|
||
background: #1e4a61;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.sort-check-btn {
|
||
background: #28a745;
|
||
}
|
||
|
||
.sort-check-btn:hover {
|
||
background: #218838;
|
||
}
|
||
|
||
.sort-reveal-btn {
|
||
background: #ffc107;
|
||
color: #333;
|
||
}
|
||
|
||
.sort-reveal-btn:hover {
|
||
background: #e0a800;
|
||
}
|
||
|
||
.sort-new-btn {
|
||
background: #6f42c1;
|
||
}
|
||
|
||
.sort-new-btn:hover {
|
||
background: #5a32a3;
|
||
}
|
||
|
||
.sorting-instructions {
|
||
margin: 20px 0;
|
||
color: #2c5f76;
|
||
}
|
||
|
||
.sorting-progress {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
margin: 10px 0;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* Horizontal layout for sorting game */
|
||
.sorting-game-layout {
|
||
display: flex;
|
||
gap: 30px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.available-cards-section {
|
||
flex: 1;
|
||
}
|
||
|
||
.sorting-slots-section {
|
||
flex: 2;
|
||
}
|
||
|
||
.available-cards-section h4,
|
||
.sorting-slots-section h4 {
|
||
margin: 0 0 15px 0;
|
||
color: #2c5f76;
|
||
font-size: 18px;
|
||
text-align: center;
|
||
}
|
||
|
||
.sorting-area {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
justify-content: center;
|
||
padding: 15px;
|
||
background: rgba(255, 255, 255, 0.5);
|
||
border-radius: 8px;
|
||
min-height: 120px;
|
||
border: 2px dashed #2c5f76;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.sorting-game-layout {
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
.available-cards-section,
|
||
.sorting-slots-section {
|
||
flex: none;
|
||
}
|
||
}
|
||
|
||
.position-slots {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 15px;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
border-radius: 8px;
|
||
border: 2px dashed #2c5f76;
|
||
}
|
||
|
||
.insert-button {
|
||
width: 32px;
|
||
height: 50px;
|
||
background: #2c5f76;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 20px;
|
||
cursor: pointer;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.insert-button:hover {
|
||
opacity: 1;
|
||
background: #1976d2;
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.insert-button.active {
|
||
opacity: 1;
|
||
background: #1976d2;
|
||
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.3);
|
||
}
|
||
|
||
.insert-button.disabled {
|
||
opacity: 0.1;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.position-slot {
|
||
width: 90px;
|
||
height: 110px;
|
||
border: 2px solid #2c5f76;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
}
|
||
|
||
.position-slot.gradient-bg {
|
||
color: white;
|
||
border-color: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.position-slot:hover {
|
||
background: #f0f8ff;
|
||
border-color: #1a4a5c;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.position-slot.active {
|
||
background: #e3f2fd;
|
||
border-color: #1976d2;
|
||
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.3);
|
||
}
|
||
|
||
.position-slot .slot-label {
|
||
font-size: 12px;
|
||
text-align: center;
|
||
font-weight: 500;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||
/* Color will be set dynamically based on background */
|
||
}
|
||
|
||
.position-slot.filled .slot-label {
|
||
position: absolute;
|
||
bottom: 8px;
|
||
left: 8px;
|
||
right: 8px;
|
||
color: #2c3e50;
|
||
text-shadow: none;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border-radius: 4px;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
.position-slot.filled:hover .slot-label {
|
||
opacity: 1;
|
||
}
|
||
|
||
.position-slot.filled {
|
||
background: #e8f5e8;
|
||
border-color: #28a745;
|
||
}
|
||
|
||
.position-slot.filled .slot-card {
|
||
position: absolute;
|
||
top: 5px;
|
||
left: 5px;
|
||
right: 5px;
|
||
bottom: 5px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.position-slot.correct {
|
||
border-color: #28a745;
|
||
background: #f8fff9;
|
||
}
|
||
|
||
.position-slot.incorrect {
|
||
border-color: #dc3545;
|
||
background: #fff8f8;
|
||
animation: shake 0.5s ease-in-out;
|
||
}
|
||
|
||
.sorting-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 15px;
|
||
margin: 30px 0;
|
||
padding: 20px;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
border-radius: 12px;
|
||
min-height: 300px;
|
||
}
|
||
|
||
.sort-card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 8px;
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||
cursor: pointer;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
border: 2px solid transparent;
|
||
position: relative;
|
||
user-select: none;
|
||
width: 90px;
|
||
height: 90px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.sort-card .card-svg svg {
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.sort-card:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||
border-color: #2c5f76;
|
||
}
|
||
|
||
.sort-card.selected {
|
||
border-color: #1976d2;
|
||
background: #e3f2fd;
|
||
transform: scale(1.1);
|
||
box-shadow: 0 6px 20px rgba(25, 118, 210, 0.3);
|
||
}
|
||
|
||
.sort-card.placed {
|
||
opacity: 0.7;
|
||
transform: scale(0.9);
|
||
cursor: default;
|
||
}
|
||
|
||
.sort-card.placed:hover {
|
||
transform: scale(0.95);
|
||
border-color: #28a745;
|
||
}
|
||
|
||
.sort-card.correct {
|
||
border-color: #28a745;
|
||
background: #f8fff9;
|
||
}
|
||
|
||
.sort-card.incorrect {
|
||
border-color: #dc3545;
|
||
background: #fff8f8;
|
||
animation: shake 0.5s ease-in-out;
|
||
}
|
||
|
||
.sort-card .card-position {
|
||
position: absolute;
|
||
top: 5px;
|
||
left: 5px;
|
||
background: rgba(44, 95, 118, 0.8);
|
||
color: white;
|
||
border-radius: 50%;
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.sort-card .revealed-number {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 5px;
|
||
background: #ffc107;
|
||
color: #333;
|
||
border-radius: 4px;
|
||
padding: 2px 8px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
display: none;
|
||
}
|
||
|
||
.sort-card.revealed .revealed-number {
|
||
display: block;
|
||
}
|
||
|
||
.sorting-feedback {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.feedback-perfect {
|
||
background: linear-gradient(135deg, #d4edda, #c3e6cb);
|
||
color: #155724;
|
||
border: 2px solid #28a745;
|
||
}
|
||
|
||
.feedback-good {
|
||
background: linear-gradient(135deg, #fff3cd, #ffeaa7);
|
||
color: #856404;
|
||
border: 2px solid #ffc107;
|
||
}
|
||
|
||
.feedback-needs-work {
|
||
background: linear-gradient(135deg, #f8d7da, #f5c6cb);
|
||
color: #721c24;
|
||
border: 2px solid #dc3545;
|
||
}
|
||
|
||
.feedback-score {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.feedback-message {
|
||
font-size: 18px;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.feedback-details {
|
||
margin-top: 15px;
|
||
font-size: 14px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.feedback-breakdown {
|
||
margin: 20px 0;
|
||
padding: 15px;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.score-component {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin: 8px 0;
|
||
padding: 5px 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.score-component:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.component-label {
|
||
font-weight: bold;
|
||
flex: 1;
|
||
}
|
||
|
||
.component-score {
|
||
font-weight: bold;
|
||
margin: 0 10px;
|
||
min-width: 40px;
|
||
text-align: right;
|
||
}
|
||
|
||
.component-weight {
|
||
font-size: 12px;
|
||
opacity: 0.7;
|
||
min-width: 80px;
|
||
text-align: right;
|
||
}
|
||
|
||
@keyframes shake {
|
||
0%,
|
||
100% {
|
||
transform: translateX(0);
|
||
}
|
||
25% {
|
||
transform: translateX(-5px);
|
||
}
|
||
75% {
|
||
transform: translateX(5px);
|
||
}
|
||
}
|
||
|
||
@keyframes success-pulse {
|
||
0% {
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
transform: scale(1.05);
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
.sort-card.success {
|
||
animation: success-pulse 0.6s ease-in-out;
|
||
}
|
||
|
||
/* Challenge Buttons */
|
||
.challenges-section {
|
||
margin: 40px 0;
|
||
max-width: 1200px;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
.section-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.section-header h2 {
|
||
margin: 0 0 10px 0;
|
||
color: #2c3e50;
|
||
font-size: 28px;
|
||
}
|
||
|
||
.section-header p {
|
||
margin: 0;
|
||
color: #7a8695;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.challenges-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||
gap: 30px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.challenge-category {
|
||
background: white;
|
||
border-radius: 16px;
|
||
padding: 25px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid #f0f2f5;
|
||
}
|
||
|
||
.category-title {
|
||
margin: 0 0 20px 0;
|
||
color: #2c3e50;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
padding-bottom: 15px;
|
||
border-bottom: 2px solid #f0f2f5;
|
||
}
|
||
|
||
.challenge-cards {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.challenge-card {
|
||
background: linear-gradient(135deg, #4a90e2, #357abd);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
text-align: left;
|
||
min-height: 100px;
|
||
}
|
||
|
||
.challenge-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 25px rgba(74, 144, 226, 0.4);
|
||
}
|
||
|
||
.challenge-icon {
|
||
font-size: 32px;
|
||
flex-shrink: 0;
|
||
width: 50px;
|
||
height: 50px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.challenge-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.challenge-content h4 {
|
||
margin: 0 0 8px 0;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.challenge-content p {
|
||
margin: 0 0 12px 0;
|
||
opacity: 0.9;
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.challenge-stats {
|
||
display: flex;
|
||
gap: 15px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.stat {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
padding: 4px 8px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.sorting-card {
|
||
background: linear-gradient(135deg, #2c5f76, #1e4a61);
|
||
}
|
||
|
||
.sorting-card:hover {
|
||
box-shadow: 0 8px 25px rgba(44, 95, 118, 0.4);
|
||
}
|
||
|
||
.matching-card {
|
||
background: linear-gradient(135deg, #7b4397, #dc2430);
|
||
}
|
||
|
||
.matching-card:hover {
|
||
box-shadow: 0 8px 25px rgba(123, 67, 151, 0.4);
|
||
}
|
||
|
||
.start-game-btn {
|
||
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
|
||
color: white;
|
||
border: none;
|
||
padding: 15px 20px;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-align: center;
|
||
min-height: 70px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.start-game-btn:hover {
|
||
background: linear-gradient(135deg, #5f4ed6, #9085f5);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 20px rgba(108, 92, 231, 0.4);
|
||
}
|
||
|
||
.start-game-btn .btn-main {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.start-game-btn .btn-sub {
|
||
font-size: 13px;
|
||
opacity: 0.9;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.mode-buttons,
|
||
.timer-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.mode-btn,
|
||
.timer-btn {
|
||
background: #f8f9fa;
|
||
border: 2px solid #e9ecef;
|
||
padding: 10px 15px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 120px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.mode-btn.active,
|
||
.timer-btn.active {
|
||
background: #007bff;
|
||
border-color: #007bff;
|
||
color: white;
|
||
}
|
||
|
||
.mode-btn:hover,
|
||
.timer-btn:hover {
|
||
border-color: #007bff;
|
||
background: #e3f2fd;
|
||
}
|
||
|
||
.mode-btn.active:hover,
|
||
.timer-btn.active:hover {
|
||
background: #0056b3;
|
||
}
|
||
|
||
.mode-icon {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.mode-text {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.timer-btn {
|
||
min-width: 80px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Matching Game Styles */
|
||
.matching-header {
|
||
position: sticky;
|
||
top: 0;
|
||
background: #f8f9fa;
|
||
border-bottom: 2px solid #e9ecef;
|
||
padding: 15px 20px;
|
||
z-index: 100;
|
||
margin: 0 -20px 15px -20px;
|
||
}
|
||
|
||
.matching-header-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.matching-status-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
|
||
.matching-stats {
|
||
display: flex;
|
||
gap: 15px;
|
||
font-size: 14px;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.matching-timer {
|
||
font-weight: 600;
|
||
color: #495057;
|
||
}
|
||
|
||
.player-stats {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.player-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 8px 12px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
min-width: 80px;
|
||
}
|
||
|
||
.player-info.active {
|
||
color: white;
|
||
}
|
||
|
||
.player-info.player1 {
|
||
border: 2px solid #007bff;
|
||
}
|
||
|
||
.player-info.player1.active {
|
||
background: #007bff;
|
||
}
|
||
|
||
.player-info.player2 {
|
||
border: 2px solid #fd7e14;
|
||
}
|
||
|
||
.player-info.player2.active {
|
||
background: #fd7e14;
|
||
}
|
||
|
||
.player-name {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.player-score {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.current-turn {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #007bff;
|
||
text-align: center;
|
||
}
|
||
|
||
.turn-timer {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: #495057;
|
||
background: #e9ecef;
|
||
padding: 8px 12px;
|
||
border-radius: 8px;
|
||
min-width: 60px;
|
||
text-align: center;
|
||
border: 2px solid #dee2e6;
|
||
}
|
||
|
||
.turn-timer.warning {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
animation: timerPulse 1s infinite;
|
||
}
|
||
|
||
.turn-timer.critical {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
animation: timerPulse 0.5s infinite;
|
||
}
|
||
|
||
.turn-timer.waiting {
|
||
background: #d1ecf1;
|
||
color: #0c5460;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
@keyframes timerPulse {
|
||
0%,
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
transform: scale(1.1);
|
||
}
|
||
}
|
||
|
||
.two-player-results {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 30px;
|
||
margin: 20px 0;
|
||
padding: 20px;
|
||
background: #f8f9fa;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.player-result {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.player-name {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #495057;
|
||
}
|
||
|
||
.player-final-score {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #007bff;
|
||
}
|
||
|
||
.vs-divider {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #6c757d;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.game-summary {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background: #e3f2fd;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.summary-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.summary-label {
|
||
font-size: 12px;
|
||
color: #6c757d;
|
||
text-transform: uppercase;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.summary-value {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #495057;
|
||
}
|
||
|
||
.matching-grid {
|
||
display: grid;
|
||
gap: 8px;
|
||
padding: 10px;
|
||
justify-content: center;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.matching-grid.grid-3x4 {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
aspect-ratio: 4/3;
|
||
width: min(100%, calc(100vh - 280px) * 4/3);
|
||
height: min(calc(100vw - 40px) * 3/4, calc(100vh - 280px));
|
||
}
|
||
|
||
.matching-grid.grid-4x4 {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
aspect-ratio: 1;
|
||
width: min(100%, calc(100vh - 280px));
|
||
height: min(calc(100vw - 40px), calc(100vh - 280px));
|
||
}
|
||
|
||
.matching-grid.grid-4x6 {
|
||
grid-template-columns: repeat(6, 1fr);
|
||
aspect-ratio: 6/4;
|
||
width: min(100%, calc(100vh - 280px) * 6/4);
|
||
height: min(calc(100vw - 40px) * 4/6, calc(100vh - 280px));
|
||
}
|
||
|
||
.matching-grid.grid-5x6 {
|
||
grid-template-columns: repeat(6, 1fr);
|
||
aspect-ratio: 6/5;
|
||
width: min(100%, calc(100vh - 280px) * 6/5);
|
||
height: min(calc(100vw - 40px) * 5/6, calc(100vh - 280px));
|
||
}
|
||
|
||
.match-card {
|
||
aspect-ratio: 1;
|
||
background: #ffffff;
|
||
border: 2px solid #e9ecef;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
transition: all 0.3s ease;
|
||
user-select: none;
|
||
width: 100%;
|
||
height: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* Cards automatically size to fill grid cells while maintaining aspect ratio */
|
||
.match-card {
|
||
/* Let the card fill the grid cell */
|
||
width: 100%;
|
||
height: 100%;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
}
|
||
|
||
.match-card:hover {
|
||
border-color: #007bff;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2);
|
||
}
|
||
|
||
.match-card.flipped {
|
||
background: #f8f9fa;
|
||
border-color: #007bff;
|
||
}
|
||
|
||
.match-card.matched {
|
||
background: #d4edda;
|
||
border-color: #28a745;
|
||
cursor: default;
|
||
position: relative;
|
||
opacity: 0.7;
|
||
filter: grayscale(20%);
|
||
transform: scale(0.95);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.match-card.matched:hover {
|
||
transform: scale(0.95);
|
||
box-shadow: none;
|
||
border-color: #28a745;
|
||
}
|
||
|
||
.match-card.matched-player1 {
|
||
background: #cce7ff;
|
||
border-color: #007bff;
|
||
box-shadow: none;
|
||
opacity: 0.7;
|
||
filter: grayscale(20%);
|
||
}
|
||
|
||
.match-card.matched-player1:hover {
|
||
border-color: #007bff;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.match-card.matched-player1::before {
|
||
border-color: #007bff !important;
|
||
background: linear-gradient(
|
||
45deg,
|
||
rgba(0, 123, 255, 0.1),
|
||
rgba(0, 123, 255, 0.05)
|
||
) !important;
|
||
}
|
||
|
||
.match-card.matched-player1::after {
|
||
background: #007bff !important;
|
||
box-shadow:
|
||
0 0 0 1px #007bff,
|
||
0 0 8px rgba(0, 123, 255, 0.4) !important;
|
||
}
|
||
|
||
.match-card.matched-player2 {
|
||
background: #ffe6cc;
|
||
border-color: #fd7e14;
|
||
box-shadow: none;
|
||
opacity: 0.7;
|
||
filter: grayscale(20%);
|
||
}
|
||
|
||
.match-card.matched-player2:hover {
|
||
border-color: #fd7e14;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.match-card.matched-player2::before {
|
||
border-color: #fd7e14 !important;
|
||
background: linear-gradient(
|
||
45deg,
|
||
rgba(253, 126, 20, 0.1),
|
||
rgba(253, 126, 20, 0.05)
|
||
) !important;
|
||
}
|
||
|
||
.match-card.matched-player2::after {
|
||
background: #fd7e14 !important;
|
||
box-shadow:
|
||
0 0 0 1px #fd7e14,
|
||
0 0 8px rgba(253, 126, 20, 0.4) !important;
|
||
}
|
||
|
||
.player-badge {
|
||
position: absolute;
|
||
top: -8px;
|
||
right: -8px;
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: white;
|
||
border: 2px solid white;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||
z-index: 20;
|
||
}
|
||
|
||
.player-badge.player1 {
|
||
background: #007bff;
|
||
}
|
||
|
||
.player-badge.player2 {
|
||
background: #fd7e14;
|
||
}
|
||
|
||
.match-card.matched::after {
|
||
content: "";
|
||
position: absolute;
|
||
bottom: 3px;
|
||
left: 3px;
|
||
width: 12px;
|
||
height: 12px;
|
||
background: #28a745;
|
||
border: 2px solid white;
|
||
border-radius: 50%;
|
||
box-shadow:
|
||
0 0 0 1px #28a745,
|
||
0 0 8px rgba(40, 167, 69, 0.4);
|
||
pointer-events: none;
|
||
z-index: 10;
|
||
}
|
||
|
||
.match-card.matched::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: -2px;
|
||
left: -2px;
|
||
right: -2px;
|
||
bottom: -2px;
|
||
border: 2px solid #28a745;
|
||
border-radius: 10px;
|
||
background: linear-gradient(
|
||
45deg,
|
||
rgba(40, 167, 69, 0.1),
|
||
rgba(40, 167, 69, 0.05)
|
||
);
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
}
|
||
|
||
.match-card.matched .match-card-content {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.match-card.invalid-move {
|
||
animation: invalidMoveShake 0.6s ease-in-out;
|
||
border-color: #dc3545 !important;
|
||
}
|
||
|
||
@keyframes invalidMoveShake {
|
||
0%,
|
||
100% {
|
||
transform: translateX(0);
|
||
}
|
||
10%,
|
||
30%,
|
||
50%,
|
||
70%,
|
||
90% {
|
||
transform: translateX(-3px);
|
||
}
|
||
20%,
|
||
40%,
|
||
60%,
|
||
80% {
|
||
transform: translateX(3px);
|
||
}
|
||
}
|
||
|
||
.match-card.valid-choice {
|
||
border-color: #28a745 !important;
|
||
box-shadow: 0 0 8px rgba(40, 167, 69, 0.4);
|
||
animation: validChoicePulse 2s infinite;
|
||
}
|
||
|
||
.match-card.invalid-choice {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.match-card.invalid-choice:hover {
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
@keyframes validChoicePulse {
|
||
0%,
|
||
100% {
|
||
box-shadow: 0 0 8px rgba(40, 167, 69, 0.4);
|
||
}
|
||
50% {
|
||
box-shadow: 0 0 12px rgba(40, 167, 69, 0.7);
|
||
}
|
||
}
|
||
|
||
@keyframes matchedCard {
|
||
0% {
|
||
transform: scale(1) rotate(0deg);
|
||
opacity: 1;
|
||
}
|
||
50% {
|
||
transform: scale(1.1) rotate(2deg);
|
||
opacity: 0.9;
|
||
}
|
||
100% {
|
||
transform: scale(0.95) rotate(0deg);
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
|
||
.match-card.matched {
|
||
animation: matchedCard 0.6s ease-out forwards;
|
||
}
|
||
|
||
.match-card-content {
|
||
display: none;
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: 8px;
|
||
}
|
||
|
||
.match-card.flipped .match-card-content,
|
||
.match-card.matched .match-card-content {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.match-card-number {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #495057;
|
||
}
|
||
|
||
.match-card-abacus {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.match-card-abacus svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.match-card-back {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
gap: 4px;
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.match-card-back.abacus-type {
|
||
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
|
||
}
|
||
|
||
.match-card-back.number-type {
|
||
background: linear-gradient(135deg, #00b894, #00cec9);
|
||
}
|
||
|
||
.match-card-back .card-type-icon {
|
||
font-size: 40px;
|
||
}
|
||
|
||
.match-card.flipped .match-card-back,
|
||
.match-card.matched .match-card-back {
|
||
display: none;
|
||
}
|
||
|
||
.matching-instructions {
|
||
background: #e3f2fd;
|
||
border: 1px solid #bbdefb;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
margin: 0 0 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.matching-feedback {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.matching-grid {
|
||
gap: 6px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.matching-grid.grid-3x4 {
|
||
width: min(100%, calc(100vh - 260px) * 4/3);
|
||
height: min(calc(100vw - 30px) * 3/4, calc(100vh - 260px));
|
||
}
|
||
|
||
.matching-grid.grid-4x4 {
|
||
width: min(100%, calc(100vh - 260px));
|
||
height: min(calc(100vw - 30px), calc(100vh - 260px));
|
||
}
|
||
|
||
.matching-grid.grid-4x6 {
|
||
width: min(100%, calc(100vh - 260px) * 6/4);
|
||
height: min(calc(100vw - 30px) * 4/6, calc(100vh - 260px));
|
||
}
|
||
|
||
.matching-grid.grid-5x6 {
|
||
width: min(100%, calc(100vh - 260px) * 6/5);
|
||
height: min(calc(100vw - 30px) * 5/6, calc(100vh - 260px));
|
||
}
|
||
|
||
.match-card-number {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.match-card-back .card-type-icon {
|
||
font-size: 28px;
|
||
}
|
||
|
||
.matching-header {
|
||
padding: 10px 15px;
|
||
margin: 0 -20px 10px -20px;
|
||
}
|
||
|
||
.matching-instructions {
|
||
padding: 12px;
|
||
margin: 0 0 15px 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.start-game-btn {
|
||
min-height: 60px;
|
||
padding: 12px 16px;
|
||
}
|
||
|
||
.start-game-btn .btn-main {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.start-game-btn .btn-sub {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.matching-grid {
|
||
gap: 4px;
|
||
padding: 5px;
|
||
}
|
||
|
||
.matching-grid.grid-3x4 {
|
||
width: min(100%, calc(100vh - 240px) * 4/3);
|
||
height: min(calc(100vw - 20px) * 3/4, calc(100vh - 240px));
|
||
}
|
||
|
||
.matching-grid.grid-4x4 {
|
||
width: min(100%, calc(100vh - 240px));
|
||
height: min(calc(100vw - 20px), calc(100vh - 240px));
|
||
}
|
||
|
||
.matching-grid.grid-4x6 {
|
||
width: min(100%, calc(100vh - 240px) * 6/4);
|
||
height: min(calc(100vw - 20px) * 4/6, calc(100vh - 240px));
|
||
}
|
||
|
||
.matching-grid.grid-5x6 {
|
||
width: min(100%, calc(100vh - 240px) * 6/5);
|
||
height: min(calc(100vw - 20px) * 5/6, calc(100vh - 240px));
|
||
}
|
||
|
||
.match-card-number {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.match-card-back .card-type-icon {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.start-game-btn {
|
||
min-height: 50px;
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
.start-game-btn .btn-main {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.start-game-btn .btn-sub {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.match-card.matched::after {
|
||
width: 10px;
|
||
height: 10px;
|
||
bottom: 2px;
|
||
left: 2px;
|
||
}
|
||
}
|
||
|
||
/* Modal Styling */
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
z-index: 1000;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
|
||
.modal.show {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-content {
|
||
background: white;
|
||
border-radius: 12px;
|
||
width: 90%;
|
||
max-width: 900px;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
animation: slideIn 0.3s ease-out;
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20px 30px;
|
||
border-bottom: 1px solid #eee;
|
||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||
border-radius: 12px 12px 0 0;
|
||
}
|
||
|
||
.modal-header h2 {
|
||
margin: 0;
|
||
color: #333;
|
||
}
|
||
|
||
.modal-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.fullscreen-btn,
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
padding: 5px 10px;
|
||
border-radius: 6px;
|
||
transition: background 0.2s ease;
|
||
}
|
||
|
||
.fullscreen-btn:hover,
|
||
.close-btn:hover {
|
||
background: rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.close-btn {
|
||
color: #666;
|
||
}
|
||
|
||
.close-btn:hover {
|
||
color: #333;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 30px;
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* Fullscreen modal styling */
|
||
.modal.fullscreen {
|
||
background: rgba(0, 0, 0, 0.95);
|
||
}
|
||
|
||
.modal.fullscreen .modal-content {
|
||
width: 100%;
|
||
height: 100%;
|
||
max-width: none;
|
||
max-height: none;
|
||
border-radius: 0;
|
||
}
|
||
|
||
.modal.fullscreen .modal-header {
|
||
border-radius: 0;
|
||
}
|
||
|
||
/* Animations */
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: scale(0.9) translateY(-20px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: scale(1) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<!-- Beautiful Main Header -->
|
||
<header class="main-header">
|
||
<h1>Soroban Flashcards</h1>
|
||
<p>Interactive learning tools for mastering the Japanese abacus</p>
|
||
</header>
|
||
|
||
<!-- Navigation -->
|
||
<nav class="page-nav">
|
||
<div class="nav-buttons">
|
||
<button class="nav-btn active" data-section="introduction">
|
||
📚 Introduction
|
||
</button>
|
||
<button class="nav-btn" data-section="configuration">
|
||
⚙️ Configuration
|
||
</button>
|
||
<button class="nav-btn" data-section="challenges">
|
||
🎯 Challenges
|
||
</button>
|
||
<button class="nav-btn" data-section="flashcards">
|
||
🧮 Flashcards
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Content Area -->
|
||
<div class="content-area">
|
||
<!-- Section 1: Introduction -->
|
||
<section id="introduction" class="page-section active">
|
||
<div class="section-header">
|
||
<h2 class="section-title">📚 Master the Soroban Abacus</h2>
|
||
<p class="section-subtitle">
|
||
Complete visual guide to reading and understanding Japanese abacus
|
||
patterns
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Tutorial Content -->
|
||
<div class="tutorial-content">
|
||
<!-- Step 1: Basic Structure -->
|
||
<div class="tutorial-step">
|
||
<div class="step-header">
|
||
<h3>
|
||
<span class="step-number">1</span>Understanding the Structure
|
||
</h3>
|
||
</div>
|
||
<div class="step-content">
|
||
<div class="tutorial-grid">
|
||
<div class="tutorial-text">
|
||
<p>
|
||
The soroban has <strong>heaven beads</strong> (top) and
|
||
<strong>earth beads</strong> (bottom) separated by a
|
||
horizontal bar.
|
||
</p>
|
||
<ul>
|
||
<li><strong>Heaven bead:</strong> Worth 5 units</li>
|
||
<li><strong>Earth beads:</strong> Worth 1 unit each</li>
|
||
<li>
|
||
<strong>Active position:</strong> Beads pushed toward
|
||
the bar
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div class="tutorial-visual">
|
||
<div class="example-abacus" id="tutorial-structure">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(36.1 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 10)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 79.5 L 3 79.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 40)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 52.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 77.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 25 2 L 25 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">
|
||
Zero (0) - All beads away from bar
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 2: Single Digits -->
|
||
<div class="tutorial-step">
|
||
<div class="step-header">
|
||
<h3>
|
||
<span class="step-number">2</span>Reading Single Digits (1-9)
|
||
</h3>
|
||
</div>
|
||
<div class="step-content">
|
||
<div class="examples-grid">
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(36.1 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 10)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 25 2 L 25 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">1 = One earth bead up</div>
|
||
</div>
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(36.1 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 10)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 25 2 L 25 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">3 = Three earth beads up</div>
|
||
</div>
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(36.1 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 72.5 L 3 72.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 40)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 52.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 77.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 25 2 L 25 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">5 = Heaven bead down</div>
|
||
</div>
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(36.1 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 25 2 L 25 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">
|
||
7 = Heaven bead + 2 earth beads
|
||
</div>
|
||
</div>
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(36.1 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 65.5 L 3 65.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 70.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 25 2 L 25 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">
|
||
9 = Heaven bead + 4 earth beads
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 3: Multi-digit Numbers -->
|
||
<div class="tutorial-step">
|
||
<div class="step-header">
|
||
<h3><span class="step-number">3</span>Multi-Digit Numbers</h3>
|
||
</div>
|
||
<div class="step-content">
|
||
<div class="tutorial-text">
|
||
<p>
|
||
Each column represents a place value. Read from right to
|
||
left: <strong>ones, tens, hundreds, thousands</strong>.
|
||
</p>
|
||
</div>
|
||
<div class="examples-grid">
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 10)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(36 10)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">23 = 2 tens + 3 ones</div>
|
||
<div class="example-breakdown">Tens: 2 | Ones: 3</div>
|
||
</div>
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(36 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">67 = 6 tens + 7 ones</div>
|
||
<div class="example-breakdown">Tens: 6 | Ones: 7</div>
|
||
</div>
|
||
<div class="example-item">
|
||
<div class="example-abacus">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11.099999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 10)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(36 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 72.5 L 3 72.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 40)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 52.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 77.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(61 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 75 2 L 75 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label">
|
||
158 = 1 hundred + 5 tens + 8 ones
|
||
</div>
|
||
<div class="example-breakdown">
|
||
Hundreds: 1 | Tens: 5 | Ones: 8
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 4: Practice Tips -->
|
||
<div class="tutorial-step">
|
||
<div class="step-header">
|
||
<h3><span class="step-number">4</span>Practice Strategy</h3>
|
||
</div>
|
||
<div class="step-content">
|
||
<div class="tutorial-grid">
|
||
<div class="tutorial-text">
|
||
<h4>Start Simple, Build Complexity</h4>
|
||
<ol>
|
||
<li>Master single digits (0-9) first</li>
|
||
<li>Practice two-digit numbers (10-99)</li>
|
||
<li>Advance to three-digit numbers (100-999)</li>
|
||
<li>Challenge yourself with larger numbers</li>
|
||
</ol>
|
||
|
||
<h4>Reading Strategy</h4>
|
||
<p>For each column, count the active beads:</p>
|
||
<ul>
|
||
<li>Heaven bead down = add 5</li>
|
||
<li>Earth beads up = add 1 each</li>
|
||
<li>Total the values for each column</li>
|
||
</ul>
|
||
</div>
|
||
<div class="tutorial-visual">
|
||
<div class="example-abacus" id="practice-example">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11.099999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 -11.400000000000002)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(11 10)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 72.5 L 3 72.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 70.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(36 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 72.5 L 3 72.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 40)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 52.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 77.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(61 17)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(54.1 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g
|
||
transform="translate(0 0)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(0 30)"
|
||
>
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 75 2 L 75 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="example-label" id="practice-label">
|
||
Practice Number
|
||
</div>
|
||
<div class="practice-hint">
|
||
Try to read this number before revealing!
|
||
</div>
|
||
<button
|
||
class="reveal-btn"
|
||
id="reveal-answer"
|
||
onclick="revealAnswer()"
|
||
>
|
||
Reveal Answer
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Section 2: Configuration -->
|
||
<section id="configuration" class="page-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">⚙️ Current Configuration</h2>
|
||
<p class="section-subtitle">Settings used for this flashcard set</p>
|
||
</div>
|
||
|
||
<div class="stats">
|
||
<div><strong>Cards:</strong> 11</div>
|
||
<div><strong>Range:</strong> 10 - 20</div>
|
||
<div>
|
||
<strong>Color Scheme:</strong> All beads are the same color
|
||
</div>
|
||
<div><strong>Bead Shape:</strong> Diamond</div>
|
||
</div>
|
||
|
||
<div class="instructions">
|
||
<h3>Configuration Details:</h3>
|
||
<p>
|
||
This flashcard set has been generated with specific settings to
|
||
help you practice. You can generate new sets with different
|
||
ranges, color schemes, and visual styles using the command-line
|
||
tools.
|
||
</p>
|
||
|
||
<h4>Available Options:</h4>
|
||
<ul style="text-align: left; display: inline-block; margin: 20px 0">
|
||
<li>
|
||
<strong>Number Ranges:</strong> Practice with any range from
|
||
0-99999
|
||
</li>
|
||
<li>
|
||
<strong>Color Schemes:</strong> Monochrome, place-value,
|
||
heaven-earth, alternating
|
||
</li>
|
||
<li><strong>Bead Shapes:</strong> Diamond, circle, square</li>
|
||
<li>
|
||
<strong>Visual Styles:</strong> Multiple colorblind-friendly
|
||
palettes
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Section 3: Challenges -->
|
||
<section id="challenges" class="page-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">🎯 Interactive Challenges</h2>
|
||
<p class="section-subtitle">
|
||
Test your soroban skills with engaging games and quizzes
|
||
</p>
|
||
</div>
|
||
|
||
<div class="challenges-section">
|
||
<div class="challenges-grid">
|
||
<div class="challenge-category">
|
||
<h3 class="category-title">🧠 Memory & Speed</h3>
|
||
<div class="challenge-cards">
|
||
<button id="open-quiz-modal" class="challenge-card quiz-card">
|
||
<div class="challenge-icon">⚡</div>
|
||
<div class="challenge-content">
|
||
<h4>Speed Memory Quiz</h4>
|
||
<p>Quick card displays test your reading skills</p>
|
||
<div class="challenge-stats">
|
||
<span class="stat">🎯 Accuracy</span>
|
||
<span class="stat">⏱️ Speed</span>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="challenge-category">
|
||
<h3 class="category-title">🎲 Logic & Patterns</h3>
|
||
<div class="challenge-cards">
|
||
<button
|
||
id="open-sorting-modal"
|
||
class="challenge-card sorting-card"
|
||
>
|
||
<div class="challenge-icon">🔢</div>
|
||
<div class="challenge-content">
|
||
<h4>Card Sorting Challenge</h4>
|
||
<p>Arrange cards using only abacus patterns</p>
|
||
<div class="challenge-stats">
|
||
<span class="stat">🧩 Logic</span>
|
||
<span class="stat">👀 Pattern Recognition</span>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
id="open-matching-modal"
|
||
class="challenge-card matching-card"
|
||
>
|
||
<div class="challenge-icon">🧩</div>
|
||
<div class="challenge-content">
|
||
<h4>Matching Pairs</h4>
|
||
<p>Match abacus patterns with their numerals</p>
|
||
<div class="challenge-stats">
|
||
<span class="stat">🔍 Memory</span>
|
||
<span class="stat">⚡ Speed</span>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quiz Modal -->
|
||
<div id="quiz-modal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>Speed Memory Quiz</h2>
|
||
<div class="modal-controls">
|
||
<button
|
||
id="quiz-fullscreen-btn"
|
||
class="fullscreen-btn"
|
||
title="Toggle Fullscreen"
|
||
>
|
||
⛶
|
||
</button>
|
||
<button id="close-quiz-modal" class="close-btn">
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>
|
||
Test your soroban reading skills! Cards will be shown briefly,
|
||
then you'll enter the numbers you remember.
|
||
</p>
|
||
|
||
<div class="quiz-controls">
|
||
<div class="control-group">
|
||
<label for="quiz-count">Cards to Quiz:</label>
|
||
<div class="count-buttons">
|
||
<button type="button" class="count-btn" data-count="5">
|
||
5
|
||
</button>
|
||
<button type="button" class="count-btn" data-count="10">
|
||
10
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="count-btn active"
|
||
data-count="15"
|
||
>
|
||
15
|
||
</button>
|
||
<button type="button" class="count-btn" data-count="25">
|
||
25
|
||
</button>
|
||
<button type="button" class="count-btn" data-count="all">
|
||
All (11)
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="display-time">Display Time per Card:</label>
|
||
<div class="slider-container">
|
||
<input
|
||
type="range"
|
||
id="display-time"
|
||
min="0.5"
|
||
max="10"
|
||
step="0.5"
|
||
value="2"
|
||
/>
|
||
<span class="slider-value">2.0s</span>
|
||
</div>
|
||
</div>
|
||
|
||
<button id="start-quiz" class="quiz-start-btn">
|
||
Start Quiz
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Quiz Game Area (hidden initially) -->
|
||
<div id="quiz-game" class="quiz-game" style="display: none">
|
||
<div class="quiz-header">
|
||
<div class="quiz-progress">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill"></div>
|
||
</div>
|
||
<span class="progress-text"
|
||
>Card <span id="current-card">1</span> of
|
||
<span id="total-cards">10</span></span
|
||
>
|
||
</div>
|
||
<button id="end-quiz" class="end-game-btn">End Quiz</button>
|
||
</div>
|
||
|
||
<div class="quiz-display">
|
||
<div id="quiz-card" class="quiz-flashcard">
|
||
<!-- Card content will be inserted here -->
|
||
</div>
|
||
<div id="quiz-countdown" class="countdown">
|
||
Get Ready...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quiz Input Phase (hidden initially) -->
|
||
<div id="quiz-input" class="quiz-input" style="display: none">
|
||
<h3>Enter the Numbers You Remember</h3>
|
||
<div class="quiz-stats">
|
||
<div class="stats-item">
|
||
<span class="stats-label">Cards shown:</span>
|
||
<span id="cards-shown-count">0</span>
|
||
</div>
|
||
<div class="stats-item">
|
||
<span class="stats-label">Guesses left:</span>
|
||
<span id="guesses-remaining">0</span>
|
||
</div>
|
||
<div class="stats-item">
|
||
<span class="stats-label">Found:</span>
|
||
<span id="numbers-found">0</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="smart-input-container">
|
||
<div class="smart-input-prompt">
|
||
Type the numbers you remember:
|
||
</div>
|
||
<div class="number-display" id="number-display">
|
||
<span class="current-typing" id="current-typing"></span>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
id="smart-input"
|
||
style="
|
||
position: absolute;
|
||
left: -9999px;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
"
|
||
autocomplete="off"
|
||
/>
|
||
</div>
|
||
|
||
<div class="found-numbers" id="found-numbers">
|
||
<!-- Accepted numbers will appear here -->
|
||
</div>
|
||
|
||
<div class="quiz-finish-buttons">
|
||
<button
|
||
id="finish-quiz"
|
||
class="finish-btn"
|
||
style="display: none"
|
||
>
|
||
Finish Quiz
|
||
</button>
|
||
<button
|
||
id="give-up-quiz"
|
||
class="give-up-btn"
|
||
style="display: none"
|
||
>
|
||
Can't Remember More
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quiz Results (hidden initially) -->
|
||
<div
|
||
id="quiz-results"
|
||
class="quiz-results"
|
||
style="display: none"
|
||
>
|
||
<h3>Quiz Results</h3>
|
||
<div class="score-display">
|
||
<div class="score-circle">
|
||
<span id="score-percentage">0%</span>
|
||
</div>
|
||
<div class="score-details">
|
||
<p>
|
||
<strong>Score:</strong>
|
||
<span id="score-correct">0</span> /
|
||
<span id="score-total">0</span> correct
|
||
</p>
|
||
<p>
|
||
<strong>Time per card:</strong>
|
||
<span id="result-timing">2.0s</span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="results-breakdown">
|
||
<h4>Detailed Results:</h4>
|
||
<div id="results-list">
|
||
<!-- Results will be inserted here -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quiz-actions">
|
||
<button id="retry-quiz">Try Again</button>
|
||
<button id="back-to-cards">Back to Cards</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sorting Modal -->
|
||
<div id="sorting-modal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>Card Sorting Challenge</h2>
|
||
<div class="modal-controls">
|
||
<button
|
||
id="sorting-fullscreen-btn"
|
||
class="fullscreen-btn"
|
||
title="Toggle Fullscreen"
|
||
>
|
||
⛶
|
||
</button>
|
||
<button id="close-sorting-modal" class="close-btn">
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>
|
||
Click cards and positions to arrange them in ascending order
|
||
(smallest to largest). No numerals shown - rely on reading the
|
||
abacus!
|
||
</p>
|
||
|
||
<div class="sorting-controls">
|
||
<div class="control-group">
|
||
<label for="sort-count">Cards to Sort:</label>
|
||
<div class="count-buttons">
|
||
<button
|
||
type="button"
|
||
class="sort-count-btn active"
|
||
data-count="5"
|
||
>
|
||
5
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="sort-count-btn"
|
||
data-count="8"
|
||
>
|
||
8
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="sort-count-btn"
|
||
data-count="12"
|
||
>
|
||
12
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="sort-count-btn"
|
||
data-count="15"
|
||
>
|
||
15
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sorting-actions">
|
||
<button id="start-sorting" class="sort-start-btn">
|
||
Start Sorting Challenge
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Game Action Buttons (now moved to sticky header) -->
|
||
<div class="sorting-game-actions" style="display: none">
|
||
<!-- Buttons moved to sticky header -->
|
||
</div>
|
||
|
||
<!-- Sorting Game Header (Sticky) -->
|
||
<div
|
||
id="sorting-header"
|
||
class="sorting-header"
|
||
style="display: none"
|
||
>
|
||
<div class="sorting-header-content">
|
||
<div class="sorting-status-group">
|
||
<span id="sorting-status">Ready to start</span>
|
||
<div class="sorting-timer" id="sorting-timer">0:00</div>
|
||
</div>
|
||
<div class="sorting-controls">
|
||
<button id="check-sorting" class="header-btn check-btn">
|
||
Check Solution
|
||
</button>
|
||
<button id="reveal-numbers" class="header-btn reveal-btn">
|
||
Show Numbers
|
||
</button>
|
||
<button id="new-sorting" class="header-btn new-btn">
|
||
New Challenge
|
||
</button>
|
||
<button id="end-sorting" class="header-btn end-btn">
|
||
End Game
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sorting Game Area -->
|
||
<div id="sorting-game" style="display: none">
|
||
<div class="sorting-instructions">
|
||
<p>
|
||
<strong>Instructions:</strong> Click a card → Click
|
||
position or + button to place. Click placed cards to move
|
||
back.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="sorting-game-layout">
|
||
<div class="available-cards-section">
|
||
<h4>Available Cards</h4>
|
||
<div id="sorting-area" class="sorting-area">
|
||
<!-- Available cards will be shown here -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sorting-slots-section">
|
||
<h4>Sorting Positions (Smallest → Largest)</h4>
|
||
<div id="position-slots" class="position-slots">
|
||
<!-- Position slots will be created here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="sorting-feedback"
|
||
id="sorting-feedback"
|
||
style="display: none"
|
||
>
|
||
<!-- Feedback will be shown here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Score Report Modal -->
|
||
<div id="score-modal" class="score-modal" style="display: none">
|
||
<div class="score-modal-content">
|
||
<div class="score-modal-header">
|
||
<h3>🎯 Sorting Challenge Results</h3>
|
||
<button class="score-modal-close">×</button>
|
||
</div>
|
||
<div id="score-modal-body" class="score-modal-body">
|
||
<!-- Score content will be inserted here -->
|
||
</div>
|
||
<div class="score-modal-footer">
|
||
<button
|
||
id="score-modal-new-game"
|
||
class="modal-btn new-game-btn"
|
||
>
|
||
New Challenge
|
||
</button>
|
||
<button
|
||
id="score-modal-close-btn"
|
||
class="modal-btn close-btn"
|
||
>
|
||
Close
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Matching Pairs Modal -->
|
||
<div id="matching-modal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>Matching Pairs Challenge</h2>
|
||
<div class="modal-controls">
|
||
<button
|
||
id="matching-fullscreen-btn"
|
||
class="fullscreen-btn"
|
||
title="Toggle Fullscreen"
|
||
>
|
||
⛶
|
||
</button>
|
||
<button id="close-matching-modal" class="close-btn">
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>
|
||
Match abacus patterns with their corresponding numerals. Find
|
||
all pairs in minimum moves!
|
||
</p>
|
||
|
||
<div class="matching-controls">
|
||
<div class="control-group">
|
||
<label>Game Mode:</label>
|
||
<div class="mode-buttons">
|
||
<button
|
||
type="button"
|
||
class="mode-btn active"
|
||
data-mode="single"
|
||
>
|
||
<div class="mode-icon">👤</div>
|
||
<div class="mode-text">Single Player</div>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="mode-btn"
|
||
data-mode="two-player"
|
||
>
|
||
<div class="mode-icon">👥</div>
|
||
<div class="mode-text">Two Player</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="control-group"
|
||
id="timer-controls"
|
||
style="display: none"
|
||
>
|
||
<label>Turn Timer:</label>
|
||
<div class="timer-buttons">
|
||
<button
|
||
type="button"
|
||
class="timer-btn active"
|
||
data-timer="0"
|
||
>
|
||
No Timer
|
||
</button>
|
||
<button type="button" class="timer-btn" data-timer="15">
|
||
15 sec
|
||
</button>
|
||
<button type="button" class="timer-btn" data-timer="30">
|
||
30 sec
|
||
</button>
|
||
<button type="button" class="timer-btn" data-timer="60">
|
||
60 sec
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="match-grid-size"
|
||
>Choose Grid Size to Start:</label
|
||
>
|
||
<div class="count-buttons">
|
||
<button
|
||
type="button"
|
||
class="match-size-btn start-game-btn"
|
||
data-pairs="6"
|
||
>
|
||
<div class="btn-main">3×4 Grid</div>
|
||
<div class="btn-sub">6 pairs</div>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="match-size-btn start-game-btn"
|
||
data-pairs="8"
|
||
>
|
||
<div class="btn-main">4×4 Grid</div>
|
||
<div class="btn-sub">8 pairs</div>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="match-size-btn start-game-btn"
|
||
data-pairs="12"
|
||
>
|
||
<div class="btn-main">4×6 Grid</div>
|
||
<div class="btn-sub">12 pairs</div>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="match-size-btn start-game-btn"
|
||
data-pairs="15"
|
||
>
|
||
<div class="btn-main">5×6 Grid</div>
|
||
<div class="btn-sub">15 pairs</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Matching Game Header (Sticky) -->
|
||
<div
|
||
id="matching-header"
|
||
class="matching-header"
|
||
style="display: none"
|
||
>
|
||
<div class="matching-header-content">
|
||
<div class="matching-status-group">
|
||
<span id="matching-status">Ready to start</span>
|
||
<div class="matching-stats">
|
||
<span id="moves-counter">Moves: 0</span>
|
||
<span id="pairs-found">Pairs: 0/0</span>
|
||
<div class="matching-timer" id="matching-timer">
|
||
0:00
|
||
</div>
|
||
</div>
|
||
<!-- Two-player specific stats -->
|
||
<div
|
||
class="player-stats"
|
||
id="player-stats"
|
||
style="display: none"
|
||
>
|
||
<div class="player-info player1" id="player1-info">
|
||
<span class="player-name">🔵 Player 1</span>
|
||
<span class="player-score">0 pairs</span>
|
||
</div>
|
||
<div class="current-turn" id="current-turn">
|
||
🔵 Player 1's Turn
|
||
</div>
|
||
<div
|
||
class="turn-timer"
|
||
id="turn-timer"
|
||
style="display: none"
|
||
>
|
||
30s
|
||
</div>
|
||
<div class="player-info player2" id="player2-info">
|
||
<span class="player-name">🟠 Player 2</span>
|
||
<span class="player-score">0 pairs</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="matching-controls">
|
||
<button id="new-matching" class="header-btn new-btn">
|
||
New Game
|
||
</button>
|
||
<button id="end-matching" class="header-btn end-btn">
|
||
End Game
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Matching Game Area -->
|
||
<div id="matching-game" style="display: none">
|
||
<div class="matching-instructions">
|
||
<p>
|
||
<strong>Instructions:</strong> Click two cards to flip
|
||
them. Purple cards (🧮) contain abacus patterns, green
|
||
cards (🔢) contain numbers. Match abacus patterns with
|
||
their corresponding numbers. Complete all pairs with
|
||
minimum moves!
|
||
</p>
|
||
</div>
|
||
|
||
<div id="matching-grid" class="matching-grid">
|
||
<!-- Matching cards will be generated here -->
|
||
</div>
|
||
|
||
<div
|
||
class="matching-feedback"
|
||
id="matching-feedback"
|
||
style="display: none"
|
||
>
|
||
<!-- Feedback will be shown here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Matching Score Modal -->
|
||
<div
|
||
id="matching-score-modal"
|
||
class="score-modal"
|
||
style="display: none"
|
||
>
|
||
<div class="score-modal-content">
|
||
<div class="score-modal-header">
|
||
<h3>🧩 Matching Pairs Results</h3>
|
||
<button class="matching-score-modal-close">
|
||
×
|
||
</button>
|
||
</div>
|
||
<div
|
||
id="matching-score-modal-body"
|
||
class="score-modal-body"
|
||
>
|
||
<!-- Score content will be inserted here -->
|
||
</div>
|
||
<div class="score-modal-footer">
|
||
<button
|
||
id="matching-score-modal-new-game"
|
||
class="modal-btn new-game-btn"
|
||
>
|
||
New Challenge
|
||
</button>
|
||
<button
|
||
id="matching-score-modal-close-btn"
|
||
class="modal-btn close-btn"
|
||
>
|
||
Close
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Section 4: Flashcards -->
|
||
<section id="flashcards" class="page-section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">🧮 Practice Flashcards</h2>
|
||
<p class="section-subtitle">
|
||
Interactive cards for hands-on learning
|
||
</p>
|
||
</div>
|
||
|
||
<div class="cards-grid" id="cards-grid">
|
||
<div class="flashcard" data-number="10">
|
||
<div class="card-number">#1</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 79.5 L 3 79.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 40)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 52.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 77.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">10</div>
|
||
</div>
|
||
<div class="flashcard" data-number="11">
|
||
<div class="card-number">#2</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">11</div>
|
||
</div>
|
||
<div class="flashcard" data-number="12">
|
||
<div class="card-number">#3</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">12</div>
|
||
</div>
|
||
<div class="flashcard" data-number="13">
|
||
<div class="card-number">#4</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">13</div>
|
||
</div>
|
||
<div class="flashcard" data-number="14">
|
||
<div class="card-number">#5</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 72.5 L 3 72.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 70.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">14</div>
|
||
</div>
|
||
<div class="flashcard" data-number="15">
|
||
<div class="card-number">#6</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 17)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 72.5 L 3 72.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 40)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 52.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 77.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">15</div>
|
||
</div>
|
||
<div class="flashcard" data-number="16">
|
||
<div class="card-number">#7</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 17)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">16</div>
|
||
</div>
|
||
<div class="flashcard" data-number="17">
|
||
<div class="card-number">#8</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 17)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">17</div>
|
||
</div>
|
||
<div class="flashcard" data-number="18">
|
||
<div class="card-number">#9</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 17)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 73 L 3 73 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">18</div>
|
||
</div>
|
||
<div class="flashcard" data-number="19">
|
||
<div class="card-number">#10</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 53)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 17)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 65.5 L 3 65.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 17)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 58)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 70.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">19</div>
|
||
</div>
|
||
<div class="flashcard" data-number="20">
|
||
<div class="card-number">#11</div>
|
||
<div class="abacus-container">
|
||
<svg
|
||
class="typst-doc"
|
||
viewBox="0 0 108 108"
|
||
width="108pt"
|
||
height="108pt"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||
xmlns:h5="http://www.w3.org/1999/xhtml"
|
||
>
|
||
<g>
|
||
<g
|
||
transform="translate(5.400000000000001 5.400000000000001)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(23.599999999999998 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 -11.400000000000002)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(11 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 80 L 3 80 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 33)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 45.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 65.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(4.1000000000000005 78)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(36 10)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#eeeeee"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 79.5 L 3 79.5 L 3 0 Z "
|
||
/>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 10)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 40)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 52.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 65)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g
|
||
transform="translate(29.099999999999998 77.5)"
|
||
>
|
||
<g class="typst-group">
|
||
<g>
|
||
<g transform="translate(0 0)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#e6e6e6"
|
||
fill-rule="nonzero"
|
||
stroke="#000000"
|
||
stroke-width="0.5"
|
||
stroke-linecap="butt"
|
||
stroke-linejoin="miter"
|
||
stroke-miterlimit="4"
|
||
d="M 8.4 0 L 16.8 6 L 8.4 12 L 0 6 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(0 30)">
|
||
<path
|
||
class="typst-shape"
|
||
fill="#000000"
|
||
fill-rule="nonzero"
|
||
d="M 0 0 L 0 2 L 50 2 L 50 0 Z "
|
||
/>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="numeral">20</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="instructions">
|
||
<p>
|
||
<em
|
||
>Interactive flashcards for digital learning. Use the left/right
|
||
arrow keys or click the cards to flip them.</em
|
||
>
|
||
</p>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<!-- End Content Area -->
|
||
</div>
|
||
|
||
<script>
|
||
// Modal Manager - Handles modal dialogs and fullscreen functionality
|
||
class ModalManager {
|
||
constructor() {
|
||
this.isFullscreen = false;
|
||
this.bindEvents();
|
||
}
|
||
|
||
bindEvents() {
|
||
// Challenge button events
|
||
document
|
||
.getElementById("open-quiz-modal")
|
||
.addEventListener("click", () => {
|
||
this.openModal("quiz-modal");
|
||
});
|
||
|
||
document
|
||
.getElementById("open-sorting-modal")
|
||
.addEventListener("click", () => {
|
||
this.openModal("sorting-modal");
|
||
});
|
||
|
||
document
|
||
.getElementById("open-matching-modal")
|
||
.addEventListener("click", () => {
|
||
this.openModal("matching-modal");
|
||
});
|
||
|
||
// Close button events
|
||
document
|
||
.getElementById("close-quiz-modal")
|
||
.addEventListener("click", () => {
|
||
this.closeModal("quiz-modal");
|
||
});
|
||
|
||
document
|
||
.getElementById("close-sorting-modal")
|
||
.addEventListener("click", () => {
|
||
this.closeModal("sorting-modal");
|
||
});
|
||
|
||
document
|
||
.getElementById("close-matching-modal")
|
||
.addEventListener("click", () => {
|
||
this.closeModal("matching-modal");
|
||
});
|
||
|
||
// Fullscreen button events
|
||
document
|
||
.getElementById("quiz-fullscreen-btn")
|
||
.addEventListener("click", () => {
|
||
this.toggleFullscreen("quiz-modal");
|
||
});
|
||
|
||
document
|
||
.getElementById("sorting-fullscreen-btn")
|
||
.addEventListener("click", () => {
|
||
this.toggleFullscreen("sorting-modal");
|
||
});
|
||
|
||
document
|
||
.getElementById("matching-fullscreen-btn")
|
||
.addEventListener("click", () => {
|
||
this.toggleFullscreen("matching-modal");
|
||
});
|
||
|
||
// Close modal when clicking outside
|
||
document.addEventListener("click", (e) => {
|
||
if (
|
||
e.target.classList.contains("modal") &&
|
||
e.target.classList.contains("show")
|
||
) {
|
||
this.closeModal(e.target.id);
|
||
}
|
||
});
|
||
|
||
// ESC key to close modal
|
||
document.addEventListener("keydown", (e) => {
|
||
if (e.key === "Escape") {
|
||
const openModal = document.querySelector(".modal.show");
|
||
if (openModal) {
|
||
this.closeModal(openModal.id);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Fullscreen change events
|
||
document.addEventListener("fullscreenchange", () => {
|
||
this.handleFullscreenChange();
|
||
});
|
||
|
||
document.addEventListener("webkitfullscreenchange", () => {
|
||
this.handleFullscreenChange();
|
||
});
|
||
|
||
document.addEventListener("mozfullscreenchange", () => {
|
||
this.handleFullscreenChange();
|
||
});
|
||
|
||
document.addEventListener("MSFullscreenChange", () => {
|
||
this.handleFullscreenChange();
|
||
});
|
||
}
|
||
|
||
openModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
modal.classList.add("show");
|
||
document.body.style.overflow = "hidden";
|
||
}
|
||
|
||
closeModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
if (this.isFullscreen) {
|
||
this.exitFullscreen();
|
||
}
|
||
modal.classList.remove("show", "fullscreen");
|
||
document.body.style.overflow = "";
|
||
}
|
||
|
||
async toggleFullscreen(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
|
||
if (!this.isFullscreen) {
|
||
try {
|
||
if (modal.requestFullscreen) {
|
||
await modal.requestFullscreen();
|
||
} else if (modal.webkitRequestFullscreen) {
|
||
await modal.webkitRequestFullscreen();
|
||
} else if (modal.mozRequestFullScreen) {
|
||
await modal.mozRequestFullScreen();
|
||
} else if (modal.msRequestFullscreen) {
|
||
await modal.msRequestFullscreen();
|
||
}
|
||
modal.classList.add("fullscreen");
|
||
} catch (error) {
|
||
console.warn("Fullscreen not supported or failed:", error);
|
||
// Fallback to CSS fullscreen
|
||
modal.classList.add("fullscreen");
|
||
}
|
||
} else {
|
||
this.exitFullscreen();
|
||
}
|
||
}
|
||
|
||
exitFullscreen() {
|
||
if (document.exitFullscreen) {
|
||
document.exitFullscreen();
|
||
} else if (document.webkitExitFullscreen) {
|
||
document.webkitExitFullscreen();
|
||
} else if (document.mozCancelFullScreen) {
|
||
document.mozCancelFullScreen();
|
||
} else if (document.msExitFullscreen) {
|
||
document.msExitFullscreen();
|
||
}
|
||
}
|
||
|
||
handleFullscreenChange() {
|
||
const isFullscreen = !!(
|
||
document.fullscreenElement ||
|
||
document.webkitFullscreenElement ||
|
||
document.mozFullScreenElement ||
|
||
document.msFullscreenElement
|
||
);
|
||
|
||
this.isFullscreen = isFullscreen;
|
||
|
||
if (!isFullscreen) {
|
||
// Remove fullscreen class from all modals when exiting fullscreen
|
||
document.querySelectorAll(".modal").forEach((modal) => {
|
||
modal.classList.remove("fullscreen");
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// Quiz functionality - No dependencies, pure JavaScript
|
||
class SorobanQuiz {
|
||
constructor() {
|
||
this.cards = [];
|
||
this.quizCards = [];
|
||
this.currentCardIndex = 0;
|
||
this.displayTime = 2.0;
|
||
this.selectedCount = 15;
|
||
this.foundNumbers = [];
|
||
this.correctAnswers = [];
|
||
this.guessesRemaining = 0;
|
||
this.currentInput = "";
|
||
this.incorrectGuesses = 0;
|
||
this.finishButtonsBound = false;
|
||
|
||
this.initializeCards();
|
||
this.bindEvents();
|
||
}
|
||
|
||
initializeCards() {
|
||
// Extract card data from the DOM
|
||
const cardElements = document.querySelectorAll(".flashcard");
|
||
this.cards = Array.from(cardElements).map((card) => ({
|
||
number: parseInt(card.dataset.number),
|
||
svg: card.querySelector(".abacus-container").innerHTML,
|
||
element: card,
|
||
}));
|
||
}
|
||
|
||
bindEvents() {
|
||
// Count buttons
|
||
document.querySelectorAll(".count-btn").forEach((btn) => {
|
||
btn.addEventListener("click", (e) => {
|
||
document
|
||
.querySelectorAll(".count-btn")
|
||
.forEach((b) => b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
this.selectedCount =
|
||
btn.dataset.count === "all"
|
||
? this.cards.length
|
||
: parseInt(btn.dataset.count);
|
||
});
|
||
});
|
||
|
||
// Display time slider
|
||
const slider = document.getElementById("display-time");
|
||
const valueDisplay = document.querySelector(".slider-value");
|
||
slider.addEventListener("input", (e) => {
|
||
this.displayTime = parseFloat(e.target.value);
|
||
valueDisplay.textContent = this.displayTime.toFixed(1) + "s";
|
||
});
|
||
|
||
// Start quiz button
|
||
document
|
||
.getElementById("start-quiz")
|
||
.addEventListener("click", () => {
|
||
this.startQuiz();
|
||
});
|
||
|
||
// Note: Submit answers button replaced by smart input system
|
||
|
||
// Note: Finish quiz buttons are bound later in showInputPhase() when they become visible
|
||
|
||
// End quiz button
|
||
document.getElementById("end-quiz").addEventListener("click", () => {
|
||
this.endQuiz();
|
||
});
|
||
|
||
// Retry and back buttons
|
||
document
|
||
.getElementById("retry-quiz")
|
||
.addEventListener("click", () => {
|
||
this.resetQuiz();
|
||
this.startQuiz();
|
||
});
|
||
|
||
document
|
||
.getElementById("back-to-cards")
|
||
.addEventListener("click", () => {
|
||
this.resetQuiz();
|
||
});
|
||
}
|
||
|
||
async startQuiz() {
|
||
// Select random cards
|
||
this.quizCards = this.getRandomCards(this.selectedCount);
|
||
this.correctAnswers = this.quizCards.map((card) => card.number);
|
||
this.currentCardIndex = 0;
|
||
|
||
// Hide configuration controls, show only game
|
||
document.querySelector(".quiz-controls").style.display = "none";
|
||
|
||
// Show quiz game section within modal
|
||
this.hideQuizSections();
|
||
document.getElementById("quiz-game").style.display = "block";
|
||
document.getElementById("total-cards").textContent =
|
||
this.quizCards.length;
|
||
|
||
// Start with the first card
|
||
this.showNextCard();
|
||
}
|
||
|
||
getRandomCards(count) {
|
||
const shuffled = [...this.cards].sort(() => 0.5 - Math.random());
|
||
return shuffled.slice(0, Math.min(count, this.cards.length));
|
||
}
|
||
|
||
async showNextCard() {
|
||
if (this.currentCardIndex >= this.quizCards.length) {
|
||
this.showInputPhase();
|
||
return;
|
||
}
|
||
|
||
const card = this.quizCards[this.currentCardIndex];
|
||
const progress =
|
||
(this.currentCardIndex / this.quizCards.length) * 100;
|
||
|
||
// Update progress
|
||
document.querySelector(".progress-fill").style.width = progress + "%";
|
||
document.getElementById("current-card").textContent =
|
||
this.currentCardIndex + 1;
|
||
|
||
// Only show countdown for the very first card
|
||
if (this.currentCardIndex === 0) {
|
||
await this.showCountdown();
|
||
} else {
|
||
// Subtle "new card" indicator for subsequent cards
|
||
await this.showNewCardIndicator();
|
||
}
|
||
|
||
// Show card
|
||
await this.displayCard(card);
|
||
|
||
this.currentCardIndex++;
|
||
|
||
// Minimal delay before next card (just enough for the exit animation)
|
||
setTimeout(() => {
|
||
this.showNextCard();
|
||
}, 100);
|
||
}
|
||
|
||
async showNewCardIndicator() {
|
||
return new Promise((resolve) => {
|
||
const countdownEl = document.getElementById("quiz-countdown");
|
||
const cardEl = document.getElementById("quiz-card");
|
||
|
||
// Hide card temporarily
|
||
cardEl.style.display = "none";
|
||
countdownEl.style.display = "block";
|
||
|
||
// Brief flash to indicate new card
|
||
countdownEl.textContent = "Next";
|
||
countdownEl.className = "quiz-countdown new-card-flash";
|
||
|
||
setTimeout(() => {
|
||
countdownEl.style.display = "none";
|
||
resolve();
|
||
}, 150); // Very brief indication
|
||
});
|
||
}
|
||
|
||
async showCountdown() {
|
||
const countdownEl = document.getElementById("quiz-countdown");
|
||
const cardEl = document.getElementById("quiz-card");
|
||
|
||
cardEl.style.visibility = "hidden";
|
||
countdownEl.style.display = "block";
|
||
|
||
// 3, 2, 1 countdown
|
||
const counts = ["3", "2", "1", "GO!"];
|
||
|
||
for (let i = 0; i < counts.length; i++) {
|
||
countdownEl.textContent = counts[i];
|
||
countdownEl.className = "countdown";
|
||
if (i === counts.length - 1) countdownEl.classList.add("go");
|
||
|
||
await this.delay(400);
|
||
}
|
||
|
||
countdownEl.style.display = "none";
|
||
}
|
||
|
||
async displayCard(card) {
|
||
const cardEl = document.getElementById("quiz-card");
|
||
const countdownEl = document.getElementById("quiz-countdown");
|
||
|
||
// Show card content with entry animation
|
||
cardEl.innerHTML = card.svg;
|
||
cardEl.style.display = "block";
|
||
cardEl.style.visibility = "visible";
|
||
cardEl.classList.add("pulse");
|
||
|
||
// Display for most of the time
|
||
await this.delay(this.displayTime * 1000 - 300);
|
||
|
||
// Subtle exit signal - brief red border flash
|
||
cardEl.classList.add("card-exit-warning");
|
||
await this.delay(200);
|
||
|
||
// Quick fade out
|
||
cardEl.classList.add("card-fade-out");
|
||
await this.delay(100);
|
||
|
||
// Hide card and reset classes
|
||
cardEl.classList.remove(
|
||
"pulse",
|
||
"card-exit-warning",
|
||
"card-fade-out",
|
||
);
|
||
cardEl.style.visibility = "hidden";
|
||
}
|
||
|
||
showInputPhase() {
|
||
// Complete progress bar
|
||
document.querySelector(".progress-fill").style.width = "100%";
|
||
|
||
// Initialize smart input system
|
||
this.correctAnswers = this.quizCards.map((card) => card.number);
|
||
this.foundNumbers = [];
|
||
this.guessesRemaining =
|
||
this.selectedCount + Math.floor(this.selectedCount / 2); // Allow 50% extra guesses
|
||
|
||
// Update stats display
|
||
document.getElementById("cards-shown-count").textContent =
|
||
this.quizCards.length;
|
||
document.getElementById("guesses-remaining").textContent =
|
||
this.guessesRemaining;
|
||
document.getElementById("numbers-found").textContent = "0";
|
||
|
||
// Hide quiz game, show input
|
||
this.hideQuizSections();
|
||
document.getElementById("quiz-input").style.display = "block";
|
||
|
||
// Setup smart input
|
||
const smartInput = document.getElementById("smart-input");
|
||
const display = document.getElementById("number-display");
|
||
smartInput.value = "";
|
||
document.getElementById("current-typing").textContent = "";
|
||
|
||
// Focus the hidden input and make sure it captures keyboard events
|
||
smartInput.focus();
|
||
|
||
// Remove any existing event listeners to prevent duplicates
|
||
const newSmartInput = smartInput.cloneNode(true);
|
||
smartInput.parentNode.replaceChild(newSmartInput, smartInput);
|
||
|
||
// Add input event listener for real-time validation
|
||
newSmartInput.addEventListener("input", (e) =>
|
||
this.handleSmartInput(e),
|
||
);
|
||
|
||
// Make the display area clickable to maintain focus
|
||
display.addEventListener("click", () => {
|
||
newSmartInput.focus();
|
||
});
|
||
|
||
// Keep focus on the hidden input
|
||
newSmartInput.focus();
|
||
|
||
// Bind finish buttons (they exist now that quiz-input is shown)
|
||
this.bindFinishButtons();
|
||
|
||
// Show finish button when all numbers found or guesses exhausted
|
||
this.updateFinishButtonVisibility();
|
||
}
|
||
|
||
bindFinishButtons() {
|
||
// Bind finish quiz buttons - called when input phase starts and buttons are visible
|
||
// Only bind once to prevent duplicate listeners
|
||
if (this.finishButtonsBound) return;
|
||
|
||
const finishBtn = document.getElementById("finish-quiz");
|
||
const giveUpBtn = document.getElementById("give-up-quiz");
|
||
|
||
if (finishBtn) {
|
||
finishBtn.addEventListener("click", () => {
|
||
console.log("Finish quiz button clicked");
|
||
this.finishQuiz();
|
||
});
|
||
console.log("Finish button event listener added");
|
||
} else {
|
||
console.error("finish-quiz button not found in DOM");
|
||
}
|
||
|
||
if (giveUpBtn) {
|
||
giveUpBtn.addEventListener("click", () => {
|
||
console.log("Give up button clicked");
|
||
this.finishQuiz();
|
||
});
|
||
console.log("Give up button event listener added");
|
||
} else {
|
||
console.error("give-up-quiz button not found in DOM");
|
||
}
|
||
|
||
this.finishButtonsBound = true;
|
||
}
|
||
|
||
handleSmartInput(event) {
|
||
const input = event.target;
|
||
const value = input.value.trim();
|
||
const display = document.getElementById("number-display");
|
||
const typingSpan = document.getElementById("current-typing");
|
||
|
||
// Reset visual feedback
|
||
display.classList.remove("correct", "incorrect");
|
||
|
||
// Update the visual display
|
||
typingSpan.textContent = value;
|
||
|
||
// Check if input is empty
|
||
if (!value) {
|
||
this.currentInput = "";
|
||
return;
|
||
}
|
||
|
||
// Check if it's a valid number
|
||
const number = parseInt(value);
|
||
if (isNaN(number)) {
|
||
return; // Wait for more input
|
||
}
|
||
|
||
this.currentInput = value;
|
||
|
||
// Check if this number is in our correct answers and not already found
|
||
if (
|
||
this.correctAnswers.includes(number) &&
|
||
!this.foundNumbers.includes(number)
|
||
) {
|
||
// Correct number found!
|
||
this.acceptCorrectNumber(number, input, display);
|
||
} else if (
|
||
value.length >= 2 &&
|
||
!this.correctAnswers.includes(number)
|
||
) {
|
||
// Wrong number (only trigger after at least 2 digits to avoid false positives)
|
||
this.handleIncorrectGuess(input, display);
|
||
}
|
||
}
|
||
|
||
acceptCorrectNumber(number, input, display) {
|
||
// Add to found numbers
|
||
this.foundNumbers.push(number);
|
||
|
||
// Visual success feedback
|
||
display.classList.add("correct");
|
||
|
||
// Update stats
|
||
document.getElementById("numbers-found").textContent =
|
||
this.foundNumbers.length;
|
||
|
||
// Add to found numbers display
|
||
this.addFoundNumberDisplay(number);
|
||
|
||
// Clear input immediately for fast entry
|
||
setTimeout(() => {
|
||
input.value = "";
|
||
document.getElementById("current-typing").textContent = "";
|
||
this.currentInput = "";
|
||
|
||
// Check if we're done
|
||
this.updateFinishButtonVisibility();
|
||
|
||
// If all numbers found, auto-finish
|
||
if (this.foundNumbers.length === this.correctAnswers.length) {
|
||
setTimeout(() => this.finishQuiz(), 1000);
|
||
}
|
||
}, 150); // Much shorter delay - just enough to show the success feedback
|
||
|
||
// Remove success visual feedback after animation completes
|
||
setTimeout(() => {
|
||
display.classList.remove("correct");
|
||
}, 500);
|
||
}
|
||
|
||
handleIncorrectGuess(input, display) {
|
||
// Only penalize if we have guesses remaining
|
||
if (this.guessesRemaining > 0) {
|
||
this.guessesRemaining--;
|
||
this.incorrectGuesses++; // Track incorrect guesses for scoring
|
||
document.getElementById("guesses-remaining").textContent =
|
||
this.guessesRemaining;
|
||
|
||
// Visual error feedback
|
||
display.classList.add("incorrect");
|
||
|
||
// Clear input quickly for rapid entry
|
||
setTimeout(() => {
|
||
input.value = "";
|
||
document.getElementById("current-typing").textContent = "";
|
||
this.currentInput = "";
|
||
|
||
// Check if we're out of guesses
|
||
this.updateFinishButtonVisibility();
|
||
if (this.guessesRemaining === 0) {
|
||
setTimeout(() => this.finishQuiz(), 1000);
|
||
}
|
||
}, 150); // Same fast clearing as correct numbers
|
||
|
||
// Remove error visual feedback after animation completes
|
||
setTimeout(() => {
|
||
display.classList.remove("incorrect");
|
||
}, 500);
|
||
}
|
||
}
|
||
|
||
addFoundNumberDisplay(number) {
|
||
const foundContainer = document.getElementById("found-numbers");
|
||
const numberElement = document.createElement("span");
|
||
numberElement.className = "found-number";
|
||
numberElement.textContent = number;
|
||
foundContainer.appendChild(numberElement);
|
||
}
|
||
|
||
updateFinishButtonVisibility() {
|
||
const finishBtn = document.getElementById("finish-quiz");
|
||
const giveUpBtn = document.getElementById("give-up-quiz");
|
||
|
||
const hasFoundSome = this.foundNumbers.length > 0;
|
||
const hasFoundAll =
|
||
this.foundNumbers.length === this.correctAnswers.length;
|
||
const outOfGuesses = this.guessesRemaining === 0;
|
||
const hasGuessesLeft = this.guessesRemaining > 0;
|
||
|
||
if (hasFoundAll || outOfGuesses) {
|
||
// Show finish button when all found or no guesses left
|
||
finishBtn.style.display = "block";
|
||
giveUpBtn.style.display = "none";
|
||
finishBtn.textContent = hasFoundAll
|
||
? "Finish Quiz"
|
||
: "Show Results";
|
||
} else if (hasFoundSome && hasGuessesLeft) {
|
||
// Show both buttons when user has found some but could find more
|
||
finishBtn.style.display = "block";
|
||
giveUpBtn.style.display = "block";
|
||
finishBtn.textContent = "Show Results";
|
||
} else {
|
||
// No buttons when user hasn't found any yet
|
||
finishBtn.style.display = "none";
|
||
giveUpBtn.style.display = "none";
|
||
}
|
||
}
|
||
|
||
finishQuiz() {
|
||
console.log("finishQuiz called, foundNumbers:", this.foundNumbers);
|
||
// Use found numbers as answers and show results
|
||
this.answers = [...this.foundNumbers];
|
||
console.log("About to call showResults with answers:", this.answers);
|
||
this.showResults();
|
||
}
|
||
|
||
submitAnswers() {
|
||
const input = document.getElementById("answer-input").value;
|
||
this.answers = this.parseAnswers(input);
|
||
this.showResults();
|
||
}
|
||
|
||
parseAnswers(input) {
|
||
// Parse comma or space separated numbers
|
||
return input
|
||
.split(/[,\s]+/)
|
||
.map((s) => s.trim())
|
||
.filter((s) => s.length > 0)
|
||
.map((s) => parseInt(s))
|
||
.filter((n) => !isNaN(n));
|
||
}
|
||
|
||
showResults() {
|
||
const scoreData = this.calculateScore();
|
||
const correct = scoreData.correct;
|
||
const finalScore = scoreData.finalScore;
|
||
const percentage = Math.round(finalScore);
|
||
|
||
// Update score display
|
||
document.getElementById("score-percentage").textContent =
|
||
percentage + "%";
|
||
document.getElementById("score-correct").textContent = correct.length;
|
||
document.getElementById("score-total").textContent =
|
||
this.correctAnswers.length;
|
||
document.getElementById("result-timing").textContent =
|
||
this.displayTime.toFixed(1) + "s";
|
||
|
||
// Show detailed results with penalty info
|
||
this.showDetailedResults(correct, scoreData);
|
||
|
||
// Hide input, show results
|
||
this.hideQuizSections();
|
||
document.getElementById("quiz-results").style.display = "block";
|
||
}
|
||
|
||
calculateScore() {
|
||
const correct = [];
|
||
const correctSet = new Set(this.correctAnswers);
|
||
const answerSet = new Set(this.answers);
|
||
|
||
// Find correct answers
|
||
this.answers.forEach((answer) => {
|
||
if (correctSet.has(answer)) {
|
||
correct.push(answer);
|
||
}
|
||
});
|
||
|
||
// Calculate base score as percentage of correct answers
|
||
const baseScore = (correct.length / this.correctAnswers.length) * 100;
|
||
|
||
// Calculate penalty: lose 5 points per incorrect guess, minimum 0%
|
||
const penalty = this.incorrectGuesses * 5;
|
||
const finalScore = Math.max(0, baseScore - penalty);
|
||
|
||
return {
|
||
correct: correct,
|
||
baseScore: baseScore,
|
||
penalty: penalty,
|
||
incorrectGuesses: this.incorrectGuesses,
|
||
finalScore: finalScore,
|
||
};
|
||
}
|
||
|
||
showDetailedResults(correct, scoreData) {
|
||
const resultsEl = document.getElementById("results-list");
|
||
const correctSet = new Set(correct);
|
||
const answerSet = new Set(this.answers);
|
||
|
||
let html = "";
|
||
|
||
// Show scoring breakdown if there were penalties
|
||
if (scoreData && scoreData.incorrectGuesses > 0) {
|
||
html += `<div class="result-item score-breakdown">
|
||
<div style="margin-bottom: 10px; font-weight: bold; color: #2c5f76;">Score Breakdown:</div>
|
||
<div style="font-size: 0.9em; color: #666;">
|
||
Base Score: ${Math.round(scoreData.baseScore)}% (${correct.length} of ${this.correctAnswers.length} correct)<br>
|
||
Penalty: -${scoreData.penalty}% (${scoreData.incorrectGuesses} wrong guess${scoreData.incorrectGuesses > 1 ? "es" : ""} × 5 points each)<br>
|
||
<strong style="color: #2c5f76;">Final Score: ${Math.round(scoreData.finalScore)}%</strong>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// Show all correct answers and whether user got them
|
||
this.correctAnswers.forEach((num) => {
|
||
const wasCorrect = correctSet.has(num);
|
||
const className = wasCorrect
|
||
? "result-correct"
|
||
: "result-incorrect";
|
||
const status = wasCorrect ? "✓ Correct" : "✗ Missed";
|
||
html += `<div class="result-item">
|
||
<span>Card: ${num}</span>
|
||
<span class="${className}">${status}</span>
|
||
</div>`;
|
||
});
|
||
|
||
// Show any extra incorrect answers
|
||
const extraAnswers = this.answers.filter(
|
||
(a) =>
|
||
!correctSet.has(a) && this.correctAnswers.includes(a) === false,
|
||
);
|
||
extraAnswers.forEach((num) => {
|
||
html += `<div class="result-item">
|
||
<span>Wrong guess: ${num}</span>
|
||
<span class="result-incorrect">✗ Not in quiz (-5 points)</span>
|
||
</div>`;
|
||
});
|
||
|
||
resultsEl.innerHTML = html;
|
||
}
|
||
|
||
endQuiz() {
|
||
// Stop the current quiz and return to configuration
|
||
this.resetQuiz();
|
||
}
|
||
|
||
resetQuiz() {
|
||
// Reset state
|
||
this.currentCardIndex = 0;
|
||
this.answers = [];
|
||
this.correctAnswers = [];
|
||
this.quizCards = [];
|
||
this.foundNumbers = [];
|
||
this.guessesRemaining = 0;
|
||
this.currentInput = "";
|
||
this.incorrectGuesses = 0;
|
||
this.finishButtonsBound = false;
|
||
|
||
// Clear smart input
|
||
const smartInput = document.getElementById("smart-input");
|
||
if (smartInput) {
|
||
smartInput.value = "";
|
||
smartInput.classList.remove("correct", "incorrect");
|
||
}
|
||
|
||
// Clear found numbers display
|
||
const foundContainer = document.getElementById("found-numbers");
|
||
if (foundContainer) {
|
||
foundContainer.innerHTML = "";
|
||
}
|
||
|
||
// Reset stats display
|
||
document.getElementById("cards-shown-count").textContent = "0";
|
||
document.getElementById("guesses-remaining").textContent = "0";
|
||
document.getElementById("numbers-found").textContent = "0";
|
||
|
||
// Hide finish buttons
|
||
document.getElementById("finish-quiz").style.display = "none";
|
||
document.getElementById("give-up-quiz").style.display = "none";
|
||
|
||
// Reset to initial quiz state (hide all sections, show controls)
|
||
this.hideQuizSections();
|
||
document.querySelector(".quiz-controls").style.display = "block";
|
||
}
|
||
|
||
hideQuizSections() {
|
||
document.getElementById("quiz-game").style.display = "none";
|
||
document.getElementById("quiz-input").style.display = "none";
|
||
document.getElementById("quiz-results").style.display = "none";
|
||
}
|
||
|
||
delay(ms) {
|
||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||
}
|
||
}
|
||
|
||
// Card Sorting Challenge - Drag and drop functionality
|
||
class SortingChallenge {
|
||
constructor() {
|
||
this.cards = [];
|
||
this.sortingCards = [];
|
||
this.selectedCount = 5;
|
||
this.currentOrder = [];
|
||
this.correctOrder = [];
|
||
this.isDragging = false;
|
||
this.draggedElement = null;
|
||
this.draggedIndex = -1;
|
||
this.previewOrder = [];
|
||
this.lastInsertIndex = -1;
|
||
this.touchStartElement = null;
|
||
this.touchStartIndex = -1;
|
||
this.touchStartX = 0;
|
||
this.touchStartY = 0;
|
||
|
||
this.initializeSorting();
|
||
this.bindSortingEvents();
|
||
}
|
||
|
||
initializeSorting() {
|
||
// Get available cards (same as quiz cards)
|
||
const cardElements = document.querySelectorAll(".flashcard");
|
||
this.cards = Array.from(cardElements).map((card) => ({
|
||
number: parseInt(card.dataset.number),
|
||
svg: card.querySelector(".abacus-container").outerHTML,
|
||
}));
|
||
}
|
||
|
||
bindSortingEvents() {
|
||
// Card count selection
|
||
document.querySelectorAll(".sort-count-btn").forEach((btn) => {
|
||
btn.addEventListener("click", (e) => {
|
||
document
|
||
.querySelectorAll(".sort-count-btn")
|
||
.forEach((b) => b.classList.remove("active"));
|
||
e.target.classList.add("active");
|
||
this.selectedCount = parseInt(e.target.dataset.count);
|
||
});
|
||
});
|
||
|
||
// Action buttons
|
||
document
|
||
.getElementById("start-sorting")
|
||
.addEventListener("click", () => this.startSorting());
|
||
document
|
||
.getElementById("check-sorting")
|
||
.addEventListener("click", () => this.checkSolution());
|
||
document
|
||
.getElementById("reveal-numbers")
|
||
.addEventListener("click", () => this.revealNumbers());
|
||
document
|
||
.getElementById("new-sorting")
|
||
.addEventListener("click", () => this.newChallenge());
|
||
document
|
||
.getElementById("end-sorting")
|
||
.addEventListener("click", () => this.endSorting());
|
||
|
||
// Score modal event listeners
|
||
document
|
||
.querySelector(".score-modal-close")
|
||
.addEventListener("click", () => this.hideScoreModal());
|
||
document
|
||
.getElementById("score-modal-close-btn")
|
||
.addEventListener("click", () => this.hideScoreModal());
|
||
document
|
||
.getElementById("score-modal-new-game")
|
||
.addEventListener("click", () => {
|
||
this.hideScoreModal();
|
||
this.newChallenge();
|
||
});
|
||
|
||
// Close modal when clicking outside
|
||
document
|
||
.getElementById("score-modal")
|
||
.addEventListener("click", (e) => {
|
||
if (e.target.id === "score-modal") {
|
||
this.hideScoreModal();
|
||
}
|
||
});
|
||
}
|
||
|
||
startSorting() {
|
||
// Select random cards for sorting
|
||
const shuffledCards = [...this.cards].sort(() => Math.random() - 0.5);
|
||
this.sortingCards = shuffledCards.slice(0, this.selectedCount);
|
||
this.correctOrder = [...this.sortingCards].sort(
|
||
(a, b) => a.number - b.number,
|
||
);
|
||
|
||
// Shuffle the display order
|
||
this.currentOrder = [...this.sortingCards].sort(
|
||
() => Math.random() - 0.5,
|
||
);
|
||
|
||
// Initialize revealed state
|
||
this.numbersRevealed = false;
|
||
|
||
// Hide configuration controls, show only game
|
||
document.querySelector(".sorting-controls").style.display = "none";
|
||
|
||
// Show sorting game and sticky header
|
||
document.getElementById("sorting-game").style.display = "block";
|
||
document.getElementById("sorting-header").style.display = "block";
|
||
|
||
this.renderSortingCards();
|
||
this.updateSortingStatus(
|
||
`Arrange the ${this.selectedCount} cards in ascending order (smallest to largest)`,
|
||
);
|
||
|
||
// Start timer
|
||
this.startTimer();
|
||
|
||
// Reset reveal numbers button for new game
|
||
document.getElementById("reveal-numbers").style.display =
|
||
"inline-block";
|
||
|
||
// Update buttons - hide old controls, sticky header is now visible
|
||
document.getElementById("start-sorting").style.display = "none";
|
||
document.querySelector(".sorting-game-actions").style.display =
|
||
"none";
|
||
}
|
||
|
||
getSequenceStyle(position, totalSlots) {
|
||
// Generate neutral warm gray gradient that won't clash with abacus colors
|
||
// Position 0 (first) = darkest, last position = lightest
|
||
const intensity = position / (totalSlots - 1); // 0 to 1
|
||
const lightness = 30 + intensity * 45; // 30% to 75% lightness for better contrast
|
||
return {
|
||
background: `hsl(220, 8%, ${lightness}%)`, // Very subtle blue-gray, low saturation
|
||
color: lightness > 60 ? "#2c3e50" : "#ffffff", // High contrast text
|
||
borderColor: lightness > 60 ? "#2c5f76" : "rgba(255,255,255,0.4)",
|
||
};
|
||
}
|
||
|
||
renderSortingCards() {
|
||
this.createPositionSlots();
|
||
this.renderAvailableCards();
|
||
}
|
||
|
||
createPositionSlots() {
|
||
const slotsContainer = document.getElementById("position-slots");
|
||
if (!slotsContainer) return;
|
||
|
||
slotsContainer.innerHTML = "";
|
||
this.placedCards = new Array(this.selectedCount).fill(null);
|
||
|
||
// Add insert button before the very first position
|
||
const firstInsertBtn = document.createElement("button");
|
||
firstInsertBtn.className = "insert-button";
|
||
firstInsertBtn.innerHTML = "+";
|
||
firstInsertBtn.dataset.insertAt = 0;
|
||
firstInsertBtn.addEventListener("click", (e) =>
|
||
this.handleInsertClick(0),
|
||
);
|
||
slotsContainer.appendChild(firstInsertBtn);
|
||
|
||
for (let i = 0; i < this.selectedCount; i++) {
|
||
// Add the position slot
|
||
const slot = document.createElement("div");
|
||
slot.className = "position-slot";
|
||
slot.dataset.position = i;
|
||
|
||
// Apply gradient to entire slot background
|
||
const style = this.getSequenceStyle(i, this.selectedCount);
|
||
slot.style.background = style.background;
|
||
slot.style.color = style.color;
|
||
slot.style.borderColor = style.borderColor;
|
||
|
||
slot.innerHTML = `
|
||
<div class="slot-label" style="color: ${style.color}">${i === 0 ? "Smallest" : i === this.selectedCount - 1 ? "Largest" : ""}</div>
|
||
`;
|
||
|
||
slot.addEventListener("click", (e) => this.handleSlotClick(i));
|
||
slotsContainer.appendChild(slot);
|
||
|
||
// Add insert button after each position
|
||
const insertBtn = document.createElement("button");
|
||
insertBtn.className = "insert-button";
|
||
insertBtn.innerHTML = "+";
|
||
insertBtn.dataset.insertAt = i + 1;
|
||
insertBtn.addEventListener("click", (e) =>
|
||
this.handleInsertClick(i + 1),
|
||
);
|
||
slotsContainer.appendChild(insertBtn);
|
||
}
|
||
}
|
||
|
||
renderAvailableCards() {
|
||
const sortingArea = document.getElementById("sorting-area");
|
||
if (!sortingArea) return;
|
||
|
||
sortingArea.innerHTML = "";
|
||
|
||
// Remove duplicates and filter out placed cards
|
||
const uniqueAvailable = this.currentOrder.filter(
|
||
(card, index, arr) => {
|
||
// Only keep first occurrence of each card number
|
||
const firstIndex = arr.findIndex((c) => c.number === card.number);
|
||
if (firstIndex !== index) {
|
||
console.warn(
|
||
`Duplicate card found: ${card.number}, removing duplicate`,
|
||
);
|
||
return false;
|
||
}
|
||
|
||
// Skip if already placed
|
||
if (
|
||
this.placedCards.some(
|
||
(placed) => placed && placed.number === card.number,
|
||
)
|
||
) {
|
||
console.warn(
|
||
`Card ${card.number} is both available and placed, removing from available`,
|
||
);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
},
|
||
);
|
||
|
||
// Update currentOrder to clean version
|
||
this.currentOrder = uniqueAvailable;
|
||
|
||
this.currentOrder.forEach((card, index) => {
|
||
const cardEl = document.createElement("div");
|
||
cardEl.className = "sort-card";
|
||
cardEl.dataset.number = card.number;
|
||
|
||
// Apply revealed state if numbers were previously revealed
|
||
if (this.numbersRevealed) {
|
||
cardEl.classList.add("revealed");
|
||
}
|
||
|
||
cardEl.innerHTML = `
|
||
<div class="revealed-number">${card.number}</div>
|
||
<div class="card-svg">${card.svg}</div>
|
||
`;
|
||
|
||
cardEl.addEventListener("click", (e) =>
|
||
this.handleCardClick(card, cardEl),
|
||
);
|
||
sortingArea.appendChild(cardEl);
|
||
});
|
||
}
|
||
|
||
handleCardClick(card, cardElement) {
|
||
// Clear any previously selected cards
|
||
document.querySelectorAll(".sort-card.selected").forEach((el) => {
|
||
el.classList.remove("selected");
|
||
});
|
||
|
||
// Select this card
|
||
cardElement.classList.add("selected");
|
||
this.selectedCard = card;
|
||
this.selectedCardElement = cardElement;
|
||
|
||
// Highlight available positions and insert buttons
|
||
document.querySelectorAll(".position-slot").forEach((slot) => {
|
||
if (!slot.classList.contains("filled")) {
|
||
slot.classList.add("active");
|
||
}
|
||
});
|
||
|
||
document.querySelectorAll(".insert-button").forEach((btn) => {
|
||
btn.classList.add("active");
|
||
});
|
||
|
||
this.updateSortingStatus(
|
||
`Selected card with value ${card.number}. Click a position or + button to place it.`,
|
||
);
|
||
}
|
||
|
||
handleInsertClick(insertPosition) {
|
||
if (!this.selectedCard) {
|
||
this.updateSortingStatus(
|
||
"Please select a card first, then click where to insert it.",
|
||
);
|
||
return;
|
||
}
|
||
|
||
// Handle insertion at the rightmost position (beyond current array bounds)
|
||
if (insertPosition >= this.selectedCount) {
|
||
// Find the rightmost empty position
|
||
let rightmostEmptyPos = -1;
|
||
for (let i = this.selectedCount - 1; i >= 0; i--) {
|
||
if (this.placedCards[i] === null) {
|
||
rightmostEmptyPos = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (rightmostEmptyPos === -1) {
|
||
this.updateSortingStatus(
|
||
"All positions are filled. Move a card first to make room.",
|
||
);
|
||
return;
|
||
}
|
||
|
||
// Place card in the rightmost empty position
|
||
this.placedCards[rightmostEmptyPos] = this.selectedCard;
|
||
} else {
|
||
// Create a new array for placed cards
|
||
const newPlacedCards = new Array(this.selectedCount).fill(null);
|
||
|
||
// Copy existing cards, shifting them as needed
|
||
for (let i = 0; i < this.placedCards.length; i++) {
|
||
if (this.placedCards[i] !== null) {
|
||
if (i < insertPosition) {
|
||
// Cards before insert position stay in same place
|
||
newPlacedCards[i] = this.placedCards[i];
|
||
} else {
|
||
// Cards at or after insert position shift right by 1
|
||
if (i + 1 < this.selectedCount) {
|
||
newPlacedCards[i + 1] = this.placedCards[i];
|
||
} else {
|
||
// Card would fall off - store temporarily, will handle below
|
||
newPlacedCards[i + 1] = this.placedCards[i];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Place the selected card at the insert position
|
||
newPlacedCards[insertPosition] = this.selectedCard;
|
||
|
||
// Now apply gap-filling logic: shift cards left to compress and eliminate gaps
|
||
const compactedCards = [];
|
||
for (let i = 0; i < newPlacedCards.length; i++) {
|
||
if (newPlacedCards[i] !== null) {
|
||
compactedCards.push(newPlacedCards[i]);
|
||
}
|
||
}
|
||
|
||
// If we have more cards than positions, put excess back in available
|
||
if (compactedCards.length > this.selectedCount) {
|
||
const excessCards = compactedCards.slice(this.selectedCount);
|
||
this.currentOrder.push(...excessCards);
|
||
compactedCards.splice(this.selectedCount);
|
||
}
|
||
|
||
// Fill the final array with compacted cards (no gaps)
|
||
const finalPlacedCards = new Array(this.selectedCount).fill(null);
|
||
for (let i = 0; i < compactedCards.length; i++) {
|
||
finalPlacedCards[i] = compactedCards[i];
|
||
}
|
||
|
||
this.placedCards = finalPlacedCards;
|
||
}
|
||
|
||
// Remove card from available cards
|
||
this.currentOrder = this.currentOrder.filter(
|
||
(c) => c.number !== this.selectedCard.number,
|
||
);
|
||
|
||
// Debug: Check total card count
|
||
this.debugCardCount("after insert");
|
||
|
||
// Clear selection and re-render
|
||
this.clearSelection();
|
||
this.updatePositionSlots();
|
||
this.renderAvailableCards();
|
||
|
||
// Update status
|
||
const placedCount = this.placedCards.filter((c) => c !== null).length;
|
||
if (placedCount === this.selectedCount) {
|
||
this.updateSortingStatus(
|
||
'All cards placed! Click "Check My Solution" to see how you did.',
|
||
);
|
||
} else {
|
||
this.updateSortingStatus(
|
||
`${placedCount}/${this.selectedCount} cards placed. Select another card to continue.`,
|
||
);
|
||
}
|
||
}
|
||
|
||
debugCardCount(context) {
|
||
const placedCount = this.placedCards.filter((c) => c !== null).length;
|
||
const availableCount = this.currentOrder.length;
|
||
const totalCount = placedCount + availableCount;
|
||
|
||
console.log(
|
||
`DEBUG ${context}: Placed=${placedCount}, Available=${availableCount}, Total=${totalCount}, Expected=${this.selectedCount}`,
|
||
);
|
||
|
||
if (totalCount !== this.selectedCount) {
|
||
console.error(
|
||
"Card count mismatch! Some cards are missing or duplicated.",
|
||
);
|
||
console.log(
|
||
"Placed cards:",
|
||
this.placedCards.map((c) => (c ? c.number : "empty")),
|
||
);
|
||
console.log(
|
||
"Available cards:",
|
||
this.currentOrder.map((c) => c.number),
|
||
);
|
||
}
|
||
}
|
||
|
||
updatePositionSlots() {
|
||
this.placedCards.forEach((card, position) => {
|
||
const slot = document.querySelector(
|
||
`[data-position="${position}"]`,
|
||
);
|
||
if (!slot) return;
|
||
|
||
if (card) {
|
||
slot.classList.add("filled");
|
||
// Reset to white background when filled
|
||
slot.style.background = "#fff";
|
||
slot.style.color = "#333";
|
||
slot.style.borderColor = "#2c5f76";
|
||
slot.innerHTML = `
|
||
<div class="slot-card">
|
||
<div class="card-svg">${card.svg}</div>
|
||
</div>
|
||
<div class="slot-label">← Click to move back</div>
|
||
`;
|
||
} else {
|
||
slot.classList.remove("filled");
|
||
// Apply gradient to empty slot
|
||
const style = this.getSequenceStyle(position, this.selectedCount);
|
||
slot.style.background = style.background;
|
||
slot.style.color = style.color;
|
||
slot.style.borderColor = style.borderColor;
|
||
slot.innerHTML = `
|
||
<div class="slot-label" style="color: ${style.color}">${position === 0 ? "Smallest" : position === this.selectedCount - 1 ? "Largest" : ""}</div>
|
||
`;
|
||
}
|
||
});
|
||
}
|
||
|
||
clearSelection() {
|
||
this.selectedCard = null;
|
||
this.selectedCardElement = null;
|
||
|
||
// Remove active states
|
||
document.querySelectorAll(".sort-card.selected").forEach((el) => {
|
||
el.classList.remove("selected");
|
||
});
|
||
document.querySelectorAll(".position-slot.active").forEach((el) => {
|
||
el.classList.remove("active");
|
||
});
|
||
document.querySelectorAll(".insert-button.active").forEach((el) => {
|
||
el.classList.remove("active");
|
||
});
|
||
}
|
||
|
||
handleSlotClick(position) {
|
||
const slot = document.querySelector(`[data-position="${position}"]`);
|
||
|
||
// If no card is selected but slot has a card, move it back to available
|
||
if (!this.selectedCard && this.placedCards[position]) {
|
||
const cardToMove = this.placedCards[position];
|
||
|
||
// Remove card from this position
|
||
this.placedCards[position] = null;
|
||
|
||
// Add card back to available cards
|
||
this.currentOrder.push(cardToMove);
|
||
|
||
// Debug: Check total card count
|
||
this.debugCardCount("after moving card back");
|
||
|
||
// Update slot appearance
|
||
this.updatePositionSlots();
|
||
this.renderAvailableCards();
|
||
|
||
// Auto-select the moved card so user can immediately place it elsewhere
|
||
this.selectedCard = cardToMove;
|
||
this.selectedCardElement = document.querySelector(
|
||
`[data-number="${cardToMove.number}"]`,
|
||
);
|
||
if (this.selectedCardElement) {
|
||
this.selectedCardElement.classList.add("selected");
|
||
|
||
// Highlight available positions
|
||
document.querySelectorAll(".position-slot").forEach((slot) => {
|
||
if (!slot.classList.contains("filled")) {
|
||
slot.classList.add("active");
|
||
}
|
||
});
|
||
document.querySelectorAll(".insert-button").forEach((btn) => {
|
||
btn.classList.add("active");
|
||
btn.classList.remove("disabled");
|
||
});
|
||
}
|
||
|
||
const placedCount = this.placedCards.filter(
|
||
(c) => c !== null,
|
||
).length;
|
||
this.updateSortingStatus(
|
||
`Moved card ${cardToMove.number} back and selected it. ${placedCount}/${this.selectedCount} cards placed.`,
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (!this.selectedCard) {
|
||
this.updateSortingStatus(
|
||
"Select a card first, or click a placed card to move it back.",
|
||
);
|
||
return;
|
||
}
|
||
|
||
// If slot is already filled, replace the card
|
||
if (this.placedCards[position]) {
|
||
// Move the previous card back to available area
|
||
this.currentOrder.push(this.placedCards[position]);
|
||
}
|
||
|
||
// Place the selected card in this position
|
||
this.placedCards[position] = this.selectedCard;
|
||
|
||
// Remove card from current order (available cards)
|
||
this.currentOrder = this.currentOrder.filter(
|
||
(c) => c.number !== this.selectedCard.number,
|
||
);
|
||
|
||
// Debug: Check total card count
|
||
this.debugCardCount("after regular placement");
|
||
|
||
// Update slot appearance
|
||
slot.classList.add("filled");
|
||
// Reset to white background when filled
|
||
slot.style.background = "#fff";
|
||
slot.style.color = "#333";
|
||
slot.style.borderColor = "#2c5f76";
|
||
slot.innerHTML = `
|
||
<div class="slot-card">
|
||
<div class="card-svg">${this.selectedCard.svg}</div>
|
||
</div>
|
||
<div class="slot-label">← Click to move back</div>
|
||
`;
|
||
|
||
// Clear selection and re-render
|
||
this.clearSelection();
|
||
this.renderAvailableCards();
|
||
|
||
// Update status
|
||
const placedCount = this.placedCards.filter((c) => c !== null).length;
|
||
if (placedCount === this.selectedCount) {
|
||
this.updateSortingStatus(
|
||
'All cards placed! Click "Check My Solution" to see how you did.',
|
||
);
|
||
} else {
|
||
this.updateSortingStatus(
|
||
`${placedCount}/${this.selectedCount} cards placed. Select another card to continue.`,
|
||
);
|
||
}
|
||
}
|
||
|
||
handleDragEnd(e) {
|
||
if (this.previewOrder.length > 0) {
|
||
// Commit the preview order to the actual order
|
||
this.currentOrder = [...this.previewOrder];
|
||
}
|
||
|
||
// Clean up
|
||
this.resetDragState();
|
||
|
||
// Re-render with final positions
|
||
this.renderSortingCards();
|
||
|
||
// Add success animation
|
||
setTimeout(() => {
|
||
document.querySelectorAll(".sort-card").forEach((card) => {
|
||
card.classList.add("success");
|
||
setTimeout(() => card.classList.remove("success"), 600);
|
||
});
|
||
}, 100);
|
||
}
|
||
|
||
handleDragOver(e) {
|
||
if (e.preventDefault) e.preventDefault();
|
||
e.dataTransfer.dropEffect = "move";
|
||
|
||
// Calculate where to insert based on mouse position
|
||
const insertIndex = this.calculateInsertIndex(e.clientX, e.clientY);
|
||
console.log(
|
||
"DragOver - insertIndex:",
|
||
insertIndex,
|
||
"lastInsertIndex:",
|
||
this.lastInsertIndex,
|
||
);
|
||
if (insertIndex !== -1 && insertIndex !== this.lastInsertIndex) {
|
||
console.log(
|
||
"Calling updatePreviewOrder with insertIndex:",
|
||
insertIndex,
|
||
);
|
||
this.updatePreviewOrder(insertIndex);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
handleDragEnter(e) {
|
||
// Handle drag enter for the sorting area
|
||
}
|
||
|
||
handleDragLeave(e) {
|
||
// Handle drag leave
|
||
}
|
||
|
||
handleDrop(e) {
|
||
if (e.stopPropagation) e.stopPropagation();
|
||
// The preview order is already set, just let dragEnd handle the commit
|
||
return false;
|
||
}
|
||
|
||
calculateInsertIndex(clientX, clientY) {
|
||
const sortingArea = document.getElementById("sorting-area");
|
||
const cards = Array.from(
|
||
sortingArea.querySelectorAll(".sort-card:not(.dragging)"),
|
||
);
|
||
|
||
if (cards.length === 0) return 0;
|
||
|
||
// Simple approach: find which card the mouse is over
|
||
for (let i = 0; i < cards.length; i++) {
|
||
const card = cards[i];
|
||
const rect = card.getBoundingClientRect();
|
||
|
||
// Check if mouse is over this card
|
||
if (
|
||
clientX >= rect.left &&
|
||
clientX <= rect.right &&
|
||
clientY >= rect.top &&
|
||
clientY <= rect.bottom
|
||
) {
|
||
const cardIndex = parseInt(card.dataset.index);
|
||
const cardCenterX = rect.left + rect.width / 2;
|
||
|
||
const insertIndex =
|
||
clientX < cardCenterX ? cardIndex : cardIndex + 1;
|
||
return insertIndex;
|
||
}
|
||
}
|
||
|
||
return this.currentOrder.length;
|
||
}
|
||
|
||
updatePreviewOrder(insertIndex) {
|
||
if (this.lastInsertIndex === insertIndex) return; // No change needed
|
||
this.lastInsertIndex = insertIndex;
|
||
|
||
console.log(
|
||
"updatePreviewOrder called with insertIndex:",
|
||
insertIndex,
|
||
);
|
||
console.log(
|
||
"currentOrder:",
|
||
this.currentOrder.map((c) => c.number),
|
||
);
|
||
console.log("draggedIndex:", this.draggedIndex);
|
||
|
||
// Create new preview order
|
||
const newOrder = [...this.currentOrder];
|
||
const draggedCard = newOrder.splice(this.draggedIndex, 1)[0];
|
||
|
||
// Adjust insert index if we removed an item before it
|
||
let adjustedInsertIndex = insertIndex;
|
||
if (this.draggedIndex < insertIndex) {
|
||
adjustedInsertIndex--;
|
||
}
|
||
|
||
// Insert at new position
|
||
newOrder.splice(adjustedInsertIndex, 0, draggedCard);
|
||
this.previewOrder = newOrder;
|
||
|
||
console.log(
|
||
"New preview order:",
|
||
this.previewOrder.map((c) => c.number),
|
||
);
|
||
|
||
// Update the visual layout immediately
|
||
this.renderPreview();
|
||
}
|
||
|
||
renderPreview() {
|
||
console.log("renderPreview called");
|
||
const sortingArea = document.getElementById("sorting-area");
|
||
|
||
// Get all cards except the dragged one
|
||
const cards = Array.from(
|
||
sortingArea.querySelectorAll(".sort-card:not(.dragging)"),
|
||
);
|
||
console.log("Found cards (excluding dragged):", cards.length);
|
||
|
||
const draggedNumber = parseInt(this.draggedElement.dataset.number);
|
||
|
||
// Simply compare the full orders - if they're different, reorder
|
||
const previewNumbers = this.previewOrder.map((c) => c.number);
|
||
const currentNumbers = this.currentOrder.map((c) => c.number);
|
||
|
||
console.log("Preview order:", previewNumbers);
|
||
console.log("Current order:", currentNumbers);
|
||
|
||
// Check if the orders are different
|
||
const needsReorder = !previewNumbers.every(
|
||
(num, index) => currentNumbers[index] === num,
|
||
);
|
||
|
||
console.log("Needs reorder:", needsReorder);
|
||
|
||
if (needsReorder) {
|
||
console.log("Performing reorder...");
|
||
|
||
// Update currentOrder to match preview (this is key!)
|
||
this.currentOrder = [...this.previewOrder];
|
||
|
||
// Create a simple reordering without removing cards from DOM
|
||
// Just change their visual order using CSS flexbox order or reinsert in correct order
|
||
|
||
const previewWithoutDragged = this.previewOrder.filter(
|
||
(card) => card.number !== draggedNumber,
|
||
);
|
||
console.log(
|
||
"Cards to reorder:",
|
||
previewWithoutDragged.map((c) => c.number),
|
||
);
|
||
console.log(
|
||
"Available DOM cards:",
|
||
cards.map((c) => parseInt(c.dataset.number)),
|
||
);
|
||
|
||
// Store references to all current cards
|
||
const cardElements = new Map();
|
||
cards.forEach((card) => {
|
||
cardElements.set(parseInt(card.dataset.number), card);
|
||
});
|
||
|
||
// Reorder by moving each card to the correct position
|
||
previewWithoutDragged.forEach((card, targetIndex) => {
|
||
const cardEl = cardElements.get(card.number);
|
||
console.log(
|
||
"Finding card",
|
||
card.number,
|
||
"found element:",
|
||
!!cardEl,
|
||
);
|
||
if (cardEl) {
|
||
cardEl.dataset.index = targetIndex;
|
||
cardEl.querySelector(".card-position").textContent =
|
||
targetIndex + 1;
|
||
|
||
// Move to correct position in DOM
|
||
const currentIndex = Array.from(sortingArea.children).indexOf(
|
||
cardEl,
|
||
);
|
||
const targetPosition = targetIndex;
|
||
|
||
if (currentIndex !== targetPosition) {
|
||
if (targetPosition >= sortingArea.children.length - 1) {
|
||
sortingArea.appendChild(cardEl);
|
||
} else {
|
||
const nextSibling = sortingArea.children[targetPosition];
|
||
sortingArea.insertBefore(cardEl, nextSibling);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Ensure dragged element stays at the end for z-index
|
||
if (
|
||
this.draggedElement &&
|
||
this.draggedElement.parentNode === sortingArea
|
||
) {
|
||
sortingArea.appendChild(this.draggedElement);
|
||
}
|
||
}
|
||
}
|
||
|
||
resetDragState() {
|
||
if (this.draggedElement) {
|
||
this.draggedElement.classList.remove("dragging");
|
||
this.draggedElement.style.transform = "";
|
||
this.draggedElement.style.position = "";
|
||
this.draggedElement.style.pointerEvents = "";
|
||
}
|
||
this.isDragging = false;
|
||
this.draggedElement = null;
|
||
this.draggedIndex = -1;
|
||
this.previewOrder = [];
|
||
this.lastInsertIndex = -1;
|
||
}
|
||
|
||
// Enhanced touch support for mobile with real-time reordering
|
||
handleTouchStart(e) {
|
||
const touch = e.touches[0];
|
||
this.touchStartX = touch.clientX;
|
||
this.touchStartY = touch.clientY;
|
||
this.touchStartElement = e.target.closest(".sort-card");
|
||
this.touchStartIndex = this.touchStartElement
|
||
? parseInt(this.touchStartElement.dataset.index)
|
||
: -1;
|
||
|
||
if (this.touchStartElement) {
|
||
this.isDragging = true;
|
||
this.draggedElement = this.touchStartElement;
|
||
this.draggedIndex = this.touchStartIndex;
|
||
this.previewOrder = [...this.currentOrder];
|
||
this.lastInsertIndex = -1;
|
||
|
||
this.touchStartElement.classList.add("dragging");
|
||
this.createPlaceholder();
|
||
}
|
||
}
|
||
|
||
handleTouchMove(e) {
|
||
e.preventDefault();
|
||
|
||
if (this.touchStartElement && this.isDragging) {
|
||
const touch = e.touches[0];
|
||
|
||
// Update dragged element position
|
||
const deltaX = touch.clientX - this.touchStartX;
|
||
const deltaY = touch.clientY - this.touchStartY;
|
||
this.touchStartElement.style.transform = `translate(${deltaX}px, ${deltaY}px) rotate(5deg) scale(1.05)`;
|
||
|
||
// Calculate insert position and update preview
|
||
const insertIndex = this.calculateInsertIndex(
|
||
touch.clientX,
|
||
touch.clientY,
|
||
);
|
||
if (insertIndex !== -1) {
|
||
this.updatePreviewOrder(insertIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
handleTouchEnd(e) {
|
||
if (!this.touchStartElement || !this.isDragging) return;
|
||
|
||
// Commit the preview order
|
||
this.currentOrder = [...this.previewOrder];
|
||
|
||
// Clean up
|
||
this.touchStartElement.classList.remove("dragging");
|
||
this.touchStartElement.style.transform = "";
|
||
this.removePlaceholder();
|
||
|
||
this.isDragging = false;
|
||
this.draggedElement = null;
|
||
this.draggedIndex = -1;
|
||
this.previewOrder = [];
|
||
this.lastInsertIndex = -1;
|
||
|
||
// Re-render final positions
|
||
this.renderSortingCards();
|
||
|
||
// Success animation
|
||
setTimeout(() => {
|
||
document.querySelectorAll(".sort-card").forEach((card) => {
|
||
card.classList.add("success");
|
||
setTimeout(() => card.classList.remove("success"), 600);
|
||
});
|
||
}, 100);
|
||
|
||
this.touchStartElement = null;
|
||
this.touchStartIndex = -1;
|
||
}
|
||
|
||
checkSolution() {
|
||
if (this.placedCards.some((card) => card === null)) {
|
||
this.updateSortingStatus(
|
||
"Please place all cards before checking your solution.",
|
||
);
|
||
return;
|
||
}
|
||
|
||
// Get the sequences for comparison
|
||
const userSequence = this.placedCards.map((card) => card.number);
|
||
const correctSequence = this.correctOrder.map((card) => card.number);
|
||
|
||
// Calculate fair score using sequence alignment
|
||
const scoreResult = this.calculateSequenceScore(
|
||
userSequence,
|
||
correctSequence,
|
||
);
|
||
|
||
// Update visual feedback for each position
|
||
this.placedCards.forEach((card, index) => {
|
||
const slot = document.querySelector(`[data-position="${index}"]`);
|
||
const isExactMatch =
|
||
card.number === this.correctOrder[index].number;
|
||
|
||
if (isExactMatch) {
|
||
slot.classList.add("correct");
|
||
slot.classList.remove("incorrect");
|
||
} else {
|
||
slot.classList.add("incorrect");
|
||
slot.classList.remove("correct");
|
||
}
|
||
});
|
||
|
||
this.showAdvancedFeedback(userSequence, correctSequence);
|
||
}
|
||
|
||
calculateSequenceScore(userSeq, correctSeq) {
|
||
// Calculate Longest Common Subsequence (LCS) score
|
||
const lcsLength = this.longestCommonSubsequence(userSeq, correctSeq);
|
||
|
||
// Calculate how many cards are in correct relative order
|
||
const relativeOrderScore = (lcsLength / correctSeq.length) * 100;
|
||
|
||
// Calculate exact position matches
|
||
let exactMatches = 0;
|
||
for (let i = 0; i < userSeq.length; i++) {
|
||
if (userSeq[i] === correctSeq[i]) {
|
||
exactMatches++;
|
||
}
|
||
}
|
||
const exactScore = (exactMatches / correctSeq.length) * 100;
|
||
|
||
// Calculate inversion count (how "scrambled" the sequence is)
|
||
const inversions = this.countInversions(userSeq, correctSeq);
|
||
const maxInversions =
|
||
(correctSeq.length * (correctSeq.length - 1)) / 2;
|
||
const inversionScore = Math.max(
|
||
0,
|
||
((maxInversions - inversions) / maxInversions) * 100,
|
||
);
|
||
|
||
// Weighted final score:
|
||
// - 50% for having cards in correct relative order (LCS)
|
||
// - 30% for exact position matches
|
||
// - 20% for overall sequence organization (inversions)
|
||
const finalScore = Math.round(
|
||
relativeOrderScore * 0.5 + exactScore * 0.3 + inversionScore * 0.2,
|
||
);
|
||
|
||
return {
|
||
percentage: finalScore,
|
||
exactMatches: exactMatches,
|
||
correctRelativeOrder: lcsLength,
|
||
totalCards: correctSeq.length,
|
||
details: {
|
||
relativeOrderScore: Math.round(relativeOrderScore),
|
||
exactScore: Math.round(exactScore),
|
||
inversionScore: Math.round(inversionScore),
|
||
inversions: inversions,
|
||
},
|
||
};
|
||
}
|
||
|
||
longestCommonSubsequence(seq1, seq2) {
|
||
const m = seq1.length;
|
||
const n = seq2.length;
|
||
const dp = Array(m + 1)
|
||
.fill()
|
||
.map(() => Array(n + 1).fill(0));
|
||
|
||
for (let i = 1; i <= m; i++) {
|
||
for (let j = 1; j <= n; j++) {
|
||
if (seq1[i - 1] === seq2[j - 1]) {
|
||
dp[i][j] = dp[i - 1][j - 1] + 1;
|
||
} else {
|
||
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
||
}
|
||
}
|
||
}
|
||
|
||
return dp[m][n];
|
||
}
|
||
|
||
countInversions(userSeq, correctSeq) {
|
||
// Create a mapping from value to correct position
|
||
const correctPositions = {};
|
||
correctSeq.forEach((val, idx) => {
|
||
correctPositions[val] = idx;
|
||
});
|
||
|
||
// Convert user sequence to "correct position" sequence
|
||
const userCorrectPositions = userSeq.map(
|
||
(val) => correctPositions[val],
|
||
);
|
||
|
||
// Count inversions in this position sequence
|
||
let inversions = 0;
|
||
for (let i = 0; i < userCorrectPositions.length; i++) {
|
||
for (let j = i + 1; j < userCorrectPositions.length; j++) {
|
||
if (userCorrectPositions[i] > userCorrectPositions[j]) {
|
||
inversions++;
|
||
}
|
||
}
|
||
}
|
||
|
||
return inversions;
|
||
}
|
||
|
||
showAdvancedFeedback(userSeq, correctSeq) {
|
||
const feedbackEl = document.getElementById("sorting-feedback");
|
||
|
||
// Calculate advanced scoring metrics
|
||
const lcsLength = this.longestCommonSubsequence(userSeq, correctSeq);
|
||
const relativeOrderScore = (lcsLength / correctSeq.length) * 100;
|
||
|
||
// Calculate exact position matches
|
||
let exactMatches = 0;
|
||
for (
|
||
let i = 0;
|
||
i < Math.min(userSeq.length, correctSeq.length);
|
||
i++
|
||
) {
|
||
if (userSeq[i] === correctSeq[i]) {
|
||
exactMatches++;
|
||
}
|
||
}
|
||
const exactScore = (exactMatches / correctSeq.length) * 100;
|
||
|
||
// Calculate inversion score
|
||
const inversions = this.countInversions(userSeq, correctSeq);
|
||
const maxInversions =
|
||
(correctSeq.length * (correctSeq.length - 1)) / 2;
|
||
const inversionScore = Math.max(
|
||
0,
|
||
((maxInversions - inversions) / maxInversions) * 100,
|
||
);
|
||
|
||
// Weighted final score
|
||
const finalScore = Math.round(
|
||
relativeOrderScore * 0.5 + exactScore * 0.3 + inversionScore * 0.2,
|
||
);
|
||
|
||
const isPerfect = finalScore === 100;
|
||
|
||
let feedbackClass, message;
|
||
|
||
if (isPerfect) {
|
||
feedbackClass = "feedback-perfect";
|
||
message = "🎉 Perfect! All cards in correct order!";
|
||
} else if (finalScore >= 80) {
|
||
feedbackClass = "feedback-good";
|
||
message = "👍 Excellent! Very close to perfect!";
|
||
} else if (finalScore >= 60) {
|
||
feedbackClass = "feedback-good";
|
||
message = "👍 Good job! You understand the pattern!";
|
||
} else {
|
||
feedbackClass = "feedback-needs-work";
|
||
message =
|
||
"💪 Keep practicing! Focus on reading each abacus carefully.";
|
||
}
|
||
|
||
// Show score in modal instead of inline
|
||
this.showScoreModal({
|
||
finalScore,
|
||
message,
|
||
relativeOrderScore,
|
||
exactScore,
|
||
inversionScore,
|
||
lcsLength,
|
||
correctSeq,
|
||
exactMatches,
|
||
feedbackClass,
|
||
elapsedTime: this.getElapsedTime(),
|
||
});
|
||
|
||
// Keep inline feedback hidden
|
||
feedbackEl.style.display = "none";
|
||
|
||
this.updateSortingStatus(
|
||
isPerfect ? "Perfect solution!" : `${finalScore}% score`,
|
||
);
|
||
}
|
||
|
||
revealNumbers() {
|
||
// Track revealed state
|
||
this.numbersRevealed = true;
|
||
|
||
document.querySelectorAll(".sort-card").forEach((card) => {
|
||
card.classList.add("revealed");
|
||
});
|
||
|
||
document.getElementById("reveal-numbers").style.display = "none";
|
||
this.updateSortingStatus(
|
||
"Numbers revealed - now you can see the correct order!",
|
||
);
|
||
}
|
||
|
||
startTimer() {
|
||
this.startTime = Date.now();
|
||
this.timerInterval = setInterval(() => {
|
||
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
|
||
const minutes = Math.floor(elapsed / 60);
|
||
const seconds = elapsed % 60;
|
||
document.getElementById("sorting-timer").textContent =
|
||
`${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||
}, 1000);
|
||
}
|
||
|
||
stopTimer() {
|
||
if (this.timerInterval) {
|
||
clearInterval(this.timerInterval);
|
||
this.timerInterval = null;
|
||
}
|
||
}
|
||
|
||
getElapsedTime() {
|
||
if (this.startTime) {
|
||
return Math.floor((Date.now() - this.startTime) / 1000);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
showScoreModal(scoreData) {
|
||
const {
|
||
finalScore,
|
||
message,
|
||
relativeOrderScore,
|
||
exactScore,
|
||
inversionScore,
|
||
lcsLength,
|
||
correctSeq,
|
||
exactMatches,
|
||
feedbackClass,
|
||
elapsedTime,
|
||
} = scoreData;
|
||
|
||
const minutes = Math.floor(elapsedTime / 60);
|
||
const seconds = elapsedTime % 60;
|
||
const timeDisplay = `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||
|
||
document.getElementById("score-modal-body").innerHTML = `
|
||
<div class="feedback-score">${finalScore}%</div>
|
||
<div class="feedback-message">${message}</div>
|
||
<div class="feedback-time">⏱️ Time: ${timeDisplay}</div>
|
||
<div class="feedback-breakdown">
|
||
<div class="score-component">
|
||
<span class="component-label">Sequence Order:</span>
|
||
<span class="component-score">${Math.round(relativeOrderScore)}%</span>
|
||
<span class="component-weight">(50% weight)</span>
|
||
</div>
|
||
<div class="score-component">
|
||
<span class="component-label">Exact Positions:</span>
|
||
<span class="component-score">${Math.round(exactScore)}%</span>
|
||
<span class="component-weight">(30% weight)</span>
|
||
</div>
|
||
<div class="score-component">
|
||
<span class="component-label">Organization:</span>
|
||
<span class="component-score">${Math.round(inversionScore)}%</span>
|
||
<span class="component-weight">(20% weight)</span>
|
||
</div>
|
||
</div>
|
||
<div class="feedback-details">
|
||
Cards in correct order: ${lcsLength}/${correctSeq.length} •
|
||
Exact position matches: ${exactMatches}/${correctSeq.length}
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById("score-modal").style.display = "flex";
|
||
}
|
||
|
||
hideScoreModal() {
|
||
document.getElementById("score-modal").style.display = "none";
|
||
}
|
||
|
||
endSorting() {
|
||
// End the current sorting and return to configuration
|
||
this.resetSorting();
|
||
}
|
||
|
||
resetSorting() {
|
||
// Reset state
|
||
this.currentOrder = [];
|
||
this.correctOrder = [];
|
||
this.placedCards = [];
|
||
this.selectedCard = null;
|
||
this.selectedCardElement = null;
|
||
|
||
// Clear feedback
|
||
document.getElementById("sorting-feedback").style.display = "none";
|
||
|
||
// Hide game and sticky header, show configuration controls
|
||
document.getElementById("sorting-game").style.display = "none";
|
||
document.getElementById("sorting-header").style.display = "none";
|
||
document.querySelector(".sorting-controls").style.display = "block";
|
||
|
||
// Stop timer
|
||
this.stopTimer();
|
||
|
||
// Reset buttons
|
||
document.getElementById("start-sorting").style.display =
|
||
"inline-block";
|
||
document.querySelector(".sorting-game-actions").style.display =
|
||
"none";
|
||
}
|
||
|
||
newChallenge() {
|
||
// Reset state but stay in game mode
|
||
this.currentOrder = [];
|
||
this.correctOrder = [];
|
||
this.placedCards = [];
|
||
this.selectedCard = null;
|
||
this.selectedCardElement = null;
|
||
this.numbersRevealed = false;
|
||
|
||
// Clear feedback
|
||
document.getElementById("sorting-feedback").style.display = "none";
|
||
|
||
// Clear card states
|
||
document.querySelectorAll(".sort-card").forEach((card) => {
|
||
card.classList.remove("correct", "incorrect", "revealed");
|
||
});
|
||
|
||
// Start a new challenge (keeping game mode active)
|
||
this.startSorting();
|
||
}
|
||
|
||
updateSortingStatus(message) {
|
||
document.getElementById("sorting-status").textContent = message;
|
||
}
|
||
}
|
||
|
||
// Matching Pairs Challenge
|
||
class MatchingChallenge {
|
||
constructor() {
|
||
this.cards = [];
|
||
this.gameCards = [];
|
||
this.flippedCards = [];
|
||
this.matchedPairs = 0;
|
||
this.totalPairs = 0;
|
||
this.moves = 0;
|
||
this.selectedPairs = 6;
|
||
this.gameStartTime = null;
|
||
this.timerInterval = null;
|
||
|
||
// Two-player mode variables
|
||
this.gameMode = "single";
|
||
this.turnTimer = 0; // 0 = no timer
|
||
this.currentPlayer = 1;
|
||
this.player1Score = 0;
|
||
this.player2Score = 0;
|
||
this.turnTimeLeft = 0;
|
||
this.turnTimerInterval = null;
|
||
this.playerTurnActive = true;
|
||
this.isFirstMove = true;
|
||
|
||
this.initializeMatching();
|
||
this.bindMatchingEvents();
|
||
}
|
||
|
||
initializeMatching() {
|
||
// Get available cards (same as quiz cards)
|
||
const cardElements = document.querySelectorAll(".flashcard");
|
||
this.cards = Array.from(cardElements).map((card) => ({
|
||
number: parseInt(card.dataset.number),
|
||
svg: card.querySelector(".abacus-container").outerHTML,
|
||
}));
|
||
}
|
||
|
||
bindMatchingEvents() {
|
||
// Remove existing event listeners to prevent duplicates
|
||
document.querySelectorAll(".mode-btn").forEach((btn) => {
|
||
// Clone the button to remove all event listeners
|
||
const newBtn = btn.cloneNode(true);
|
||
btn.parentNode.replaceChild(newBtn, btn);
|
||
});
|
||
|
||
document.querySelectorAll(".timer-btn").forEach((btn) => {
|
||
// Clone the button to remove all event listeners
|
||
const newBtn = btn.cloneNode(true);
|
||
btn.parentNode.replaceChild(newBtn, btn);
|
||
});
|
||
|
||
document.querySelectorAll(".start-game-btn").forEach((btn) => {
|
||
// Clone the button to remove all event listeners
|
||
const newBtn = btn.cloneNode(true);
|
||
btn.parentNode.replaceChild(newBtn, btn);
|
||
});
|
||
|
||
// Mode selection
|
||
document.querySelectorAll(".mode-btn").forEach((btn) => {
|
||
btn.addEventListener("click", (e) => {
|
||
document
|
||
.querySelectorAll(".mode-btn")
|
||
.forEach((b) => b.classList.remove("active"));
|
||
e.target.classList.add("active");
|
||
this.gameMode = e.target.dataset.mode;
|
||
|
||
// Show/hide timer controls for two-player mode
|
||
const timerControls = document.getElementById("timer-controls");
|
||
if (this.gameMode === "two-player") {
|
||
timerControls.style.display = "block";
|
||
} else {
|
||
timerControls.style.display = "none";
|
||
}
|
||
});
|
||
});
|
||
|
||
// Timer selection
|
||
document.querySelectorAll(".timer-btn").forEach((btn) => {
|
||
btn.addEventListener("click", (e) => {
|
||
document
|
||
.querySelectorAll(".timer-btn")
|
||
.forEach((b) => b.classList.remove("active"));
|
||
e.target.classList.add("active");
|
||
this.turnTimer = parseInt(e.target.dataset.timer);
|
||
});
|
||
});
|
||
|
||
// Grid size selection and immediate game start
|
||
document.querySelectorAll(".start-game-btn").forEach((btn) => {
|
||
btn.addEventListener("click", (e) => {
|
||
// Get the pairs count from the clicked button
|
||
const target = e.currentTarget; // Use currentTarget to get the button itself
|
||
this.selectedPairs = parseInt(target.dataset.pairs);
|
||
|
||
// Start the game immediately
|
||
this.startMatching();
|
||
});
|
||
});
|
||
|
||
// Action buttons
|
||
document
|
||
.getElementById("new-matching")
|
||
.addEventListener("click", () => this.newChallenge());
|
||
document
|
||
.getElementById("end-matching")
|
||
.addEventListener("click", () => this.endMatching());
|
||
|
||
// Score modal event listeners
|
||
document
|
||
.querySelector(".matching-score-modal-close")
|
||
.addEventListener("click", () => this.hideScoreModal());
|
||
document
|
||
.getElementById("matching-score-modal-close-btn")
|
||
.addEventListener("click", () => this.hideScoreModal());
|
||
document
|
||
.getElementById("matching-score-modal-new-game")
|
||
.addEventListener("click", () => {
|
||
this.hideScoreModal();
|
||
this.newChallenge();
|
||
});
|
||
|
||
// Close modal when clicking outside
|
||
document
|
||
.getElementById("matching-score-modal")
|
||
.addEventListener("click", (e) => {
|
||
if (e.target.id === "matching-score-modal") {
|
||
this.hideScoreModal();
|
||
}
|
||
});
|
||
}
|
||
|
||
startMatching() {
|
||
// Select random cards for matching
|
||
const shuffledCards = [...this.cards].sort(() => Math.random() - 0.5);
|
||
const selectedCards = shuffledCards.slice(0, this.selectedPairs);
|
||
|
||
// Create pairs: abacus and number
|
||
this.gameCards = [];
|
||
selectedCards.forEach((card) => {
|
||
// Add abacus card
|
||
this.gameCards.push({
|
||
id: `abacus_${card.number}`,
|
||
type: "abacus",
|
||
number: card.number,
|
||
content: card.svg,
|
||
matched: false,
|
||
});
|
||
|
||
// Add number card
|
||
this.gameCards.push({
|
||
id: `number_${card.number}`,
|
||
type: "number",
|
||
number: card.number,
|
||
content: card.number.toString(),
|
||
matched: false,
|
||
});
|
||
});
|
||
|
||
// Shuffle the game cards
|
||
this.gameCards = this.gameCards.sort(() => Math.random() - 0.5);
|
||
this.totalPairs = this.selectedPairs;
|
||
this.matchedPairs = 0;
|
||
this.moves = 0;
|
||
this.flippedCards = [];
|
||
|
||
// Initialize two-player mode variables
|
||
if (this.gameMode === "two-player") {
|
||
this.currentPlayer = 1;
|
||
this.player1Score = 0;
|
||
this.player2Score = 0;
|
||
this.playerTurnActive = true;
|
||
}
|
||
|
||
// Hide configuration controls, show game
|
||
document.querySelector(".matching-controls").style.display = "none";
|
||
document.getElementById("matching-game").style.display = "block";
|
||
document.getElementById("matching-header").style.display = "block";
|
||
|
||
this.renderMatchingGrid();
|
||
this.adjustCardSizes();
|
||
this.startTimer();
|
||
this.setupGameInterface();
|
||
this.updateMatchingStatus(
|
||
this.gameMode === "two-player"
|
||
? "Player 1 starts!"
|
||
: "Find all matching pairs!",
|
||
);
|
||
this.updateMatchingStats();
|
||
|
||
// For two-player mode with timer, show timer but don't start counting until first move
|
||
if (this.gameMode === "two-player" && this.turnTimer > 0) {
|
||
this.turnTimeLeft = this.turnTimer;
|
||
this.updateTurnTimerDisplay();
|
||
this.updateTurnTimerStatus("waiting"); // Show timer is waiting for first move
|
||
}
|
||
}
|
||
|
||
renderMatchingGrid() {
|
||
const grid = document.getElementById("matching-grid");
|
||
|
||
// Set grid class based on number of pairs
|
||
grid.className = "matching-grid";
|
||
if (this.selectedPairs === 6) grid.classList.add("grid-3x4");
|
||
else if (this.selectedPairs === 8) grid.classList.add("grid-4x4");
|
||
else if (this.selectedPairs === 12) grid.classList.add("grid-4x6");
|
||
else if (this.selectedPairs === 15) grid.classList.add("grid-5x6");
|
||
|
||
grid.innerHTML = "";
|
||
|
||
this.gameCards.forEach((card, index) => {
|
||
const cardElement = document.createElement("div");
|
||
cardElement.className = "match-card";
|
||
cardElement.dataset.index = index;
|
||
cardElement.dataset.cardId = card.id;
|
||
cardElement.dataset.number = card.number;
|
||
|
||
const typeClass =
|
||
card.type === "abacus" ? "abacus-type" : "number-type";
|
||
const typeIcon = card.type === "abacus" ? "🧮" : "🔢";
|
||
|
||
cardElement.innerHTML = `
|
||
<div class="match-card-back ${typeClass}">
|
||
<div class="card-type-icon">${typeIcon}</div>
|
||
</div>
|
||
<div class="match-card-content">
|
||
${
|
||
card.type === "number"
|
||
? `<div class="match-card-number">${card.content}</div>`
|
||
: `<div class="match-card-abacus">${card.content}</div>`
|
||
}
|
||
</div>
|
||
`;
|
||
|
||
cardElement.addEventListener("click", () =>
|
||
this.cardClicked(index),
|
||
);
|
||
grid.appendChild(cardElement);
|
||
});
|
||
}
|
||
|
||
cardClicked(index) {
|
||
const card = this.gameCards[index];
|
||
const cardElement = document.querySelector(`[data-index="${index}"]`);
|
||
|
||
// Ignore if card is already flipped or matched
|
||
if (card.matched || cardElement.classList.contains("flipped")) return;
|
||
|
||
// Ignore if two cards are already flipped
|
||
if (this.flippedCards.length >= 2) return;
|
||
|
||
// In two-player mode, ignore clicks if it's not the player's turn
|
||
if (this.gameMode === "two-player" && !this.playerTurnActive) return;
|
||
|
||
// Start timer on first move in two-player mode
|
||
if (
|
||
this.gameMode === "two-player" &&
|
||
this.turnTimer > 0 &&
|
||
this.isFirstMove
|
||
) {
|
||
this.isFirstMove = false;
|
||
this.startTurnTimer();
|
||
this.updateTurnTimerStatus("active");
|
||
}
|
||
|
||
// If one card is already flipped, only allow clicking cards of opposite type
|
||
if (this.flippedCards.length === 1) {
|
||
const flippedCardType = this.flippedCards[0].card.type;
|
||
if (card.type === flippedCardType) {
|
||
// Same type - show visual feedback but don't flip
|
||
this.showInvalidMoveHint(cardElement);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Flip the card
|
||
cardElement.classList.add("flipped");
|
||
this.flippedCards.push({ index, element: cardElement, card });
|
||
|
||
// If this is the first card flipped, highlight valid choices
|
||
if (this.flippedCards.length === 1) {
|
||
this.highlightValidCards(card.type);
|
||
}
|
||
|
||
// If two cards are flipped, check for match
|
||
if (this.flippedCards.length === 2) {
|
||
this.clearHighlights();
|
||
this.stopTurnTimer();
|
||
|
||
if (this.gameMode === "single") {
|
||
this.moves++;
|
||
}
|
||
|
||
this.updateMatchingStats();
|
||
setTimeout(() => this.checkMatch(), 1000);
|
||
}
|
||
}
|
||
|
||
checkMatch() {
|
||
const [first, second] = this.flippedCards;
|
||
|
||
// Check if numbers match and types are different
|
||
if (
|
||
first.card.number === second.card.number &&
|
||
first.card.type !== second.card.type
|
||
) {
|
||
// Match found!
|
||
first.element.classList.add("matched");
|
||
second.element.classList.add("matched");
|
||
first.card.matched = true;
|
||
second.card.matched = true;
|
||
|
||
this.matchedPairs++;
|
||
|
||
// In two-player mode, award point to current player and mark cards
|
||
if (this.gameMode === "two-player") {
|
||
const playerClass = `matched-player${this.currentPlayer}`;
|
||
first.element.classList.add(playerClass);
|
||
second.element.classList.add(playerClass);
|
||
|
||
// Add player badges
|
||
this.addPlayerBadge(first.element, this.currentPlayer);
|
||
this.addPlayerBadge(second.element, this.currentPlayer);
|
||
|
||
if (this.currentPlayer === 1) {
|
||
this.player1Score++;
|
||
} else {
|
||
this.player2Score++;
|
||
}
|
||
// Player gets another turn after a match
|
||
this.updatePlayerStats();
|
||
} else {
|
||
this.updateMatchingStats();
|
||
}
|
||
|
||
// Check if game is complete
|
||
if (this.matchedPairs === this.totalPairs) {
|
||
this.endGame();
|
||
}
|
||
} else {
|
||
// No match - flip cards back
|
||
first.element.classList.remove("flipped");
|
||
second.element.classList.remove("flipped");
|
||
|
||
// In two-player mode, switch turns after a miss
|
||
if (this.gameMode === "two-player") {
|
||
this.switchPlayer();
|
||
}
|
||
}
|
||
|
||
this.flippedCards = [];
|
||
this.clearHighlights();
|
||
this.playerTurnActive = true; // Re-enable clicks
|
||
}
|
||
|
||
endGame() {
|
||
this.stopTimer();
|
||
this.stopTurnTimer();
|
||
const elapsedTime = Date.now() - this.gameStartTime;
|
||
const seconds = Math.floor(elapsedTime / 1000);
|
||
|
||
if (this.gameMode === "two-player") {
|
||
// Two-player results
|
||
let winner, description;
|
||
if (this.player1Score > this.player2Score) {
|
||
winner = "Player 1 Wins!";
|
||
description = `${this.player1Score} - ${this.player2Score}`;
|
||
} else if (this.player2Score > this.player1Score) {
|
||
winner = "Player 2 Wins!";
|
||
description = `${this.player2Score} - ${this.player1Score}`;
|
||
} else {
|
||
winner = "It's a Tie!";
|
||
description = `${this.player1Score} - ${this.player2Score}`;
|
||
}
|
||
|
||
this.showScoreModal({
|
||
gameMode: "two-player",
|
||
winner,
|
||
description,
|
||
player1Score: this.player1Score,
|
||
player2Score: this.player2Score,
|
||
totalPairs: this.totalPairs,
|
||
time: seconds,
|
||
});
|
||
} else {
|
||
// Single player results
|
||
const minimumMoves = this.totalPairs;
|
||
const efficiency = this.moves / minimumMoves;
|
||
|
||
let medal, description;
|
||
if (efficiency <= 1.5) {
|
||
medal = "🏆 Gold";
|
||
description = "Perfect efficiency!";
|
||
} else if (efficiency <= 2.0) {
|
||
medal = "🥈 Silver";
|
||
description = "Great job!";
|
||
} else if (efficiency <= 3.0) {
|
||
medal = "🥉 Bronze";
|
||
description = "Good effort!";
|
||
} else {
|
||
medal = "🎯 Practice";
|
||
description = "Keep practicing!";
|
||
}
|
||
|
||
this.showScoreModal({
|
||
gameMode: "single",
|
||
pairs: this.totalPairs,
|
||
moves: this.moves,
|
||
time: seconds,
|
||
efficiency: efficiency.toFixed(1),
|
||
medal,
|
||
description,
|
||
});
|
||
}
|
||
}
|
||
|
||
showScoreModal(results) {
|
||
const modal = document.getElementById("matching-score-modal");
|
||
const body = document.getElementById("matching-score-modal-body");
|
||
|
||
const timeStr = this.formatTime(results.time);
|
||
|
||
if (results.gameMode === "two-player") {
|
||
// Two-player results layout
|
||
body.innerHTML = `
|
||
<div class="score-summary">
|
||
<div class="score-medal">${results.winner}</div>
|
||
<div class="score-description">${results.description}</div>
|
||
</div>
|
||
|
||
<div class="two-player-results">
|
||
<div class="player-result">
|
||
<div class="player-name">🔵 Player 1</div>
|
||
<div class="player-final-score" style="color: #007bff;">${results.player1Score} pairs</div>
|
||
</div>
|
||
<div class="vs-divider">VS</div>
|
||
<div class="player-result">
|
||
<div class="player-name">🟠 Player 2</div>
|
||
<div class="player-final-score" style="color: #fd7e14;">${results.player2Score} pairs</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="game-summary">
|
||
<div class="summary-item">
|
||
<span class="summary-label">Total Pairs:</span>
|
||
<span class="summary-value">${results.totalPairs}</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<span class="summary-label">Game Time:</span>
|
||
<span class="summary-value">${timeStr}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
// Single player results layout
|
||
body.innerHTML = `
|
||
<div class="score-summary">
|
||
<div class="score-medal">${results.medal}</div>
|
||
<div class="score-description">${results.description}</div>
|
||
</div>
|
||
|
||
<div class="score-details">
|
||
<div class="score-item">
|
||
<span class="score-label">Pairs Matched:</span>
|
||
<span class="score-value">${results.pairs}</span>
|
||
</div>
|
||
<div class="score-item">
|
||
<span class="score-label">Total Moves:</span>
|
||
<span class="score-value">${results.moves}</span>
|
||
</div>
|
||
<div class="score-item">
|
||
<span class="score-label">Time:</span>
|
||
<span class="score-value">${timeStr}</span>
|
||
</div>
|
||
<div class="score-item">
|
||
<span class="score-label">Efficiency:</span>
|
||
<span class="score-value">${results.efficiency}x</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="score-explanation">
|
||
<h4>Scoring System</h4>
|
||
<div class="score-tiers">
|
||
<div>🏆 Gold: ≤1.5x minimum moves</div>
|
||
<div>🥈 Silver: ≤2.0x minimum moves</div>
|
||
<div>🥉 Bronze: ≤3.0x minimum moves</div>
|
||
<div>🎯 Practice: >3.0x minimum moves</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
modal.style.display = "block";
|
||
}
|
||
|
||
hideScoreModal() {
|
||
document.getElementById("matching-score-modal").style.display =
|
||
"none";
|
||
}
|
||
|
||
newChallenge() {
|
||
this.endMatching();
|
||
setTimeout(() => this.startMatching(), 100);
|
||
}
|
||
|
||
endMatching() {
|
||
// Stop timer
|
||
this.stopTimer();
|
||
this.stopTurnTimer();
|
||
|
||
// Hide game and sticky header, show configuration controls
|
||
document.getElementById("matching-game").style.display = "none";
|
||
document.getElementById("matching-header").style.display = "none";
|
||
document.querySelector(".matching-controls").style.display = "block";
|
||
|
||
// Reset game state
|
||
this.flippedCards = [];
|
||
this.matchedPairs = 0;
|
||
this.moves = 0;
|
||
this.currentPlayer = 1;
|
||
this.player1Score = 0;
|
||
this.player2Score = 0;
|
||
this.playerTurnActive = true;
|
||
this.isFirstMove = true;
|
||
|
||
// Reset mode selection display but preserve selected values
|
||
this.updateModeDisplay();
|
||
|
||
// Re-bind events to ensure they work after DOM changes
|
||
this.bindMatchingEvents();
|
||
}
|
||
|
||
updateModeDisplay() {
|
||
// Ensure the current game mode button is properly highlighted
|
||
document.querySelectorAll(".mode-btn").forEach((btn) => {
|
||
btn.classList.remove("active");
|
||
if (btn.dataset.mode === this.gameMode) {
|
||
btn.classList.add("active");
|
||
}
|
||
});
|
||
|
||
// Show/hide timer controls based on current mode
|
||
const timerControls = document.getElementById("timer-controls");
|
||
if (this.gameMode === "two-player") {
|
||
timerControls.style.display = "block";
|
||
} else {
|
||
timerControls.style.display = "none";
|
||
}
|
||
|
||
// Ensure timer button is highlighted
|
||
document.querySelectorAll(".timer-btn").forEach((btn) => {
|
||
btn.classList.remove("active");
|
||
if (parseInt(btn.dataset.timer) === this.turnTimer) {
|
||
btn.classList.add("active");
|
||
}
|
||
});
|
||
}
|
||
|
||
startTimer() {
|
||
this.gameStartTime = Date.now();
|
||
this.timerInterval = setInterval(() => {
|
||
const elapsed = Date.now() - this.gameStartTime;
|
||
const seconds = Math.floor(elapsed / 1000);
|
||
document.getElementById("matching-timer").textContent =
|
||
this.formatTime(seconds);
|
||
}, 1000);
|
||
}
|
||
|
||
stopTimer() {
|
||
if (this.timerInterval) {
|
||
clearInterval(this.timerInterval);
|
||
this.timerInterval = null;
|
||
}
|
||
}
|
||
|
||
formatTime(totalSeconds) {
|
||
const minutes = Math.floor(totalSeconds / 60);
|
||
const seconds = totalSeconds % 60;
|
||
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||
}
|
||
|
||
updateMatchingStatus(message) {
|
||
document.getElementById("matching-status").textContent = message;
|
||
}
|
||
|
||
updateMatchingStats() {
|
||
document.getElementById("moves-counter").textContent =
|
||
`Moves: ${this.moves}`;
|
||
document.getElementById("pairs-found").textContent =
|
||
`Pairs: ${this.matchedPairs}/${this.totalPairs}`;
|
||
}
|
||
|
||
adjustCardSizes() {
|
||
// CSS now handles responsive sizing automatically
|
||
// This method can be kept for any future dynamic adjustments if needed
|
||
|
||
// Just ensure font sizes scale with container
|
||
const grid = document.getElementById("matching-grid");
|
||
const cards = grid.querySelectorAll(".match-card");
|
||
|
||
cards.forEach((card) => {
|
||
const cardRect = card.getBoundingClientRect();
|
||
const cardSize = Math.min(cardRect.width, cardRect.height);
|
||
|
||
// Adjust font sizes proportionally to actual card size
|
||
const baseFontSize = Math.max(cardSize * 0.25, 12);
|
||
const iconSize = Math.max(cardSize * 0.4, 16);
|
||
|
||
const number = card.querySelector(".match-card-number");
|
||
if (number) number.style.fontSize = `${baseFontSize}px`;
|
||
|
||
const icon = card.querySelector(".card-type-icon");
|
||
if (icon) icon.style.fontSize = `${iconSize}px`;
|
||
});
|
||
}
|
||
|
||
showInvalidMoveHint(cardElement) {
|
||
// Add temporary visual feedback for invalid moves
|
||
cardElement.classList.add("invalid-move");
|
||
|
||
// Remove the class after animation
|
||
setTimeout(() => {
|
||
cardElement.classList.remove("invalid-move");
|
||
}, 600);
|
||
}
|
||
|
||
highlightValidCards(flippedCardType) {
|
||
// Highlight cards of the opposite type
|
||
const validType = flippedCardType === "abacus" ? "number" : "abacus";
|
||
|
||
this.gameCards.forEach((card, index) => {
|
||
const cardElement = document.querySelector(
|
||
`[data-index="${index}"]`,
|
||
);
|
||
if (!card.matched && !cardElement.classList.contains("flipped")) {
|
||
if (card.type === validType) {
|
||
cardElement.classList.add("valid-choice");
|
||
} else {
|
||
cardElement.classList.add("invalid-choice");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
clearHighlights() {
|
||
// Remove all highlight classes
|
||
const cards = document.querySelectorAll(".match-card");
|
||
cards.forEach((card) => {
|
||
card.classList.remove("valid-choice", "invalid-choice");
|
||
});
|
||
}
|
||
|
||
setupGameInterface() {
|
||
// Show/hide appropriate UI elements based on game mode
|
||
const singleStats = document.querySelector(".matching-stats");
|
||
const playerStats = document.getElementById("player-stats");
|
||
|
||
if (this.gameMode === "two-player") {
|
||
singleStats.style.display = "none";
|
||
playerStats.style.display = "flex";
|
||
this.updatePlayerStats();
|
||
|
||
// Show turn timer if enabled
|
||
const turnTimer = document.getElementById("turn-timer");
|
||
if (this.turnTimer > 0) {
|
||
turnTimer.style.display = "block";
|
||
turnTimer.textContent = `${this.turnTimer}s`;
|
||
} else {
|
||
turnTimer.style.display = "none";
|
||
}
|
||
} else {
|
||
singleStats.style.display = "flex";
|
||
playerStats.style.display = "none";
|
||
}
|
||
}
|
||
|
||
updatePlayerStats() {
|
||
// Update player scores and current turn indicator
|
||
document.querySelector("#player1-info .player-score").textContent =
|
||
`${this.player1Score} pairs`;
|
||
document.querySelector("#player2-info .player-score").textContent =
|
||
`${this.player2Score} pairs`;
|
||
|
||
// Update active player highlighting
|
||
const player1Info = document.getElementById("player1-info");
|
||
const player2Info = document.getElementById("player2-info");
|
||
const currentTurn = document.getElementById("current-turn");
|
||
|
||
if (this.currentPlayer === 1) {
|
||
player1Info.classList.add("active");
|
||
player2Info.classList.remove("active");
|
||
currentTurn.textContent = "🔵 Player 1's Turn";
|
||
} else {
|
||
player1Info.classList.remove("active");
|
||
player2Info.classList.add("active");
|
||
currentTurn.textContent = "🟠 Player 2's Turn";
|
||
}
|
||
}
|
||
|
||
addPlayerBadge(cardElement, player) {
|
||
// Create player badge element
|
||
const badge = document.createElement("div");
|
||
badge.className = `player-badge player${player}`;
|
||
badge.textContent = player;
|
||
|
||
// Add badge to card
|
||
cardElement.appendChild(badge);
|
||
}
|
||
|
||
switchPlayer() {
|
||
this.currentPlayer = this.currentPlayer === 1 ? 2 : 1;
|
||
this.updatePlayerStats();
|
||
this.updateMatchingStatus(`Player ${this.currentPlayer}'s turn`);
|
||
|
||
// Start turn timer immediately for new player if enabled
|
||
if (this.turnTimer > 0) {
|
||
this.startTurnTimer();
|
||
}
|
||
}
|
||
|
||
startTurnTimer() {
|
||
if (this.turnTimer <= 0) return;
|
||
|
||
this.turnTimeLeft = this.turnTimer;
|
||
this.updateTurnTimerDisplay();
|
||
|
||
this.turnTimerInterval = setInterval(() => {
|
||
this.turnTimeLeft--;
|
||
this.updateTurnTimerDisplay();
|
||
|
||
if (this.turnTimeLeft <= 0) {
|
||
this.handleTurnTimeout();
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
stopTurnTimer() {
|
||
if (this.turnTimerInterval) {
|
||
clearInterval(this.turnTimerInterval);
|
||
this.turnTimerInterval = null;
|
||
}
|
||
}
|
||
|
||
updateTurnTimerDisplay() {
|
||
const timerElement = document.getElementById("turn-timer");
|
||
if (!timerElement || this.turnTimer <= 0) return;
|
||
|
||
timerElement.textContent = `${this.turnTimeLeft}s`;
|
||
|
||
// Add warning/critical classes based on time left
|
||
timerElement.classList.remove("warning", "critical");
|
||
if (this.turnTimeLeft <= 5) {
|
||
timerElement.classList.add("critical");
|
||
} else if (this.turnTimeLeft <= 10) {
|
||
timerElement.classList.add("warning");
|
||
}
|
||
}
|
||
|
||
updateTurnTimerStatus(status) {
|
||
const timerElement = document.getElementById("turn-timer");
|
||
if (!timerElement) return;
|
||
|
||
if (status === "waiting") {
|
||
timerElement.classList.add("waiting");
|
||
const timeString = `${this.turnTimeLeft}s`;
|
||
timerElement.textContent = `⏸️ ${timeString}`;
|
||
} else if (status === "active") {
|
||
timerElement.classList.remove("waiting");
|
||
this.updateTurnTimerDisplay();
|
||
}
|
||
}
|
||
|
||
handleTurnTimeout() {
|
||
this.stopTurnTimer();
|
||
this.playerTurnActive = false;
|
||
|
||
// Clear any flipped cards
|
||
if (this.flippedCards.length > 0) {
|
||
this.flippedCards.forEach((item) => {
|
||
item.element.classList.remove("flipped");
|
||
});
|
||
this.flippedCards = [];
|
||
this.clearHighlights();
|
||
}
|
||
|
||
// Switch to next player
|
||
setTimeout(() => {
|
||
this.switchPlayer();
|
||
this.playerTurnActive = true;
|
||
}, 500);
|
||
}
|
||
}
|
||
|
||
// Function to handle print requests by opening PDF in print dialog
|
||
function handlePrintRequest() {
|
||
console.log("Print request intercepted!");
|
||
// Get the companion PDF URL
|
||
const pdfUrl = window.location.href.replace(/\.html$/, ".pdf");
|
||
|
||
// Create a modal overlay explaining the situation
|
||
const modal = document.createElement("div");
|
||
modal.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
z-index: 10000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
`;
|
||
|
||
const content = document.createElement("div");
|
||
content.style.cssText = `
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 30px;
|
||
max-width: 500px;
|
||
text-align: center;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
`;
|
||
|
||
const title = document.createElement("h3");
|
||
title.textContent = "Print High-Quality Flashcards";
|
||
title.style.margin = "0 0 20px 0";
|
||
title.style.color = "#333";
|
||
|
||
const explanation = document.createElement("p");
|
||
explanation.innerHTML =
|
||
"The web version is not optimized for printing.<br>For best results, download the PDF version which includes:<br><br>• Proper card sizing and layout<br>• Cut marks for easy trimming<br>• Registration marks for alignment";
|
||
explanation.style.textAlign = "left";
|
||
explanation.style.margin = "0 0 25px 0";
|
||
explanation.style.lineHeight = "1.6";
|
||
explanation.style.color = "#555";
|
||
|
||
const buttonContainer = document.createElement("div");
|
||
buttonContainer.style.cssText = "display: flex; gap: 15px;";
|
||
|
||
const downloadButton = document.createElement("button");
|
||
downloadButton.textContent = "📄 Open PDF";
|
||
downloadButton.style.cssText = `
|
||
padding: 12px 24px;
|
||
background: #007bff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
`;
|
||
|
||
const cancelButton = document.createElement("button");
|
||
cancelButton.textContent = "Cancel";
|
||
cancelButton.style.cssText = `
|
||
padding: 12px 24px;
|
||
background: #6c757d;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
`;
|
||
|
||
// Download button handler
|
||
downloadButton.onclick = function () {
|
||
showNotification("📄 Opening PDF in new tab...");
|
||
window.open(pdfUrl, "_blank");
|
||
modal.remove();
|
||
};
|
||
|
||
// Cancel button handler
|
||
cancelButton.onclick = function () {
|
||
modal.remove();
|
||
};
|
||
|
||
// Close on backdrop click
|
||
modal.onclick = function (e) {
|
||
if (e.target === modal) {
|
||
modal.remove();
|
||
}
|
||
};
|
||
|
||
// Prevent clicks inside content from closing modal
|
||
content.onclick = function (e) {
|
||
e.stopPropagation();
|
||
};
|
||
|
||
// Assemble the modal
|
||
buttonContainer.appendChild(downloadButton);
|
||
buttonContainer.appendChild(cancelButton);
|
||
content.appendChild(title);
|
||
content.appendChild(explanation);
|
||
content.appendChild(buttonContainer);
|
||
modal.appendChild(content);
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
// Fallback function to download PDF
|
||
function downloadPDF(pdfUrl) {
|
||
const link = document.createElement("a");
|
||
link.href = pdfUrl;
|
||
link.download = pdfUrl.split("/").pop();
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
// Function to show brief notifications
|
||
function showNotification(message) {
|
||
const notification = document.createElement("div");
|
||
notification.style.cssText = `
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
background: #28a745;
|
||
color: white;
|
||
padding: 15px 20px;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
z-index: 10000;
|
||
animation: slideInFromRight 0.3s ease-out;
|
||
`;
|
||
notification.textContent = message;
|
||
|
||
// Add animation styles
|
||
const style = document.createElement("style");
|
||
style.textContent = `
|
||
@keyframes slideInFromRight {
|
||
from { transform: translateX(100%); opacity: 0; }
|
||
to { transform: translateX(0); opacity: 1; }
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// Auto-remove after 3 seconds
|
||
setTimeout(() => {
|
||
notification.style.animation =
|
||
"slideInFromRight 0.3s ease-out reverse";
|
||
setTimeout(() => {
|
||
if (notification.parentNode) {
|
||
document.body.removeChild(notification);
|
||
}
|
||
if (style.parentNode) {
|
||
document.head.removeChild(style);
|
||
}
|
||
}, 300);
|
||
}, 3000);
|
||
}
|
||
|
||
// Section Navigation Manager
|
||
class SectionNavigator {
|
||
constructor() {
|
||
this.currentSection = "introduction";
|
||
this.setupEventListeners();
|
||
}
|
||
|
||
setupEventListeners() {
|
||
document.querySelectorAll(".nav-btn").forEach((btn) => {
|
||
btn.addEventListener("click", (e) => {
|
||
const section = e.target.getAttribute("data-section");
|
||
this.showSection(section);
|
||
});
|
||
});
|
||
}
|
||
|
||
showSection(sectionId) {
|
||
// Hide all sections
|
||
document.querySelectorAll(".page-section").forEach((section) => {
|
||
section.classList.remove("active");
|
||
});
|
||
|
||
// Remove active from all nav buttons
|
||
document.querySelectorAll(".nav-btn").forEach((btn) => {
|
||
btn.classList.remove("active");
|
||
});
|
||
|
||
// Show selected section
|
||
const targetSection = document.getElementById(sectionId);
|
||
if (targetSection) {
|
||
targetSection.classList.add("active");
|
||
}
|
||
|
||
// Activate corresponding nav button
|
||
const targetBtn = document.querySelector(
|
||
`[data-section="${sectionId}"]`,
|
||
);
|
||
if (targetBtn) {
|
||
targetBtn.classList.add("active");
|
||
}
|
||
|
||
this.currentSection = sectionId;
|
||
|
||
// Scroll to top when switching sections
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
}
|
||
}
|
||
|
||
// Tutorial functionality
|
||
function revealAnswer() {
|
||
const label = document.getElementById("practice-label");
|
||
const btn = document.getElementById("reveal-answer");
|
||
const example = document.getElementById("practice-example");
|
||
|
||
if (label && btn && example) {
|
||
label.textContent = "Practice Number: 456";
|
||
btn.textContent = "Try Another!";
|
||
btn.onclick = function () {
|
||
// Generate new random number for practice
|
||
const randomNum = Math.floor(Math.random() * 900) + 100;
|
||
label.textContent = "Practice Number";
|
||
btn.textContent = "Reveal Answer";
|
||
btn.onclick = revealAnswer;
|
||
|
||
// Add reveal animation
|
||
example.classList.add("revealed");
|
||
setTimeout(() => example.classList.remove("revealed"), 500);
|
||
};
|
||
|
||
// Add reveal animation
|
||
example.classList.add("revealed");
|
||
setTimeout(() => example.classList.remove("revealed"), 500);
|
||
}
|
||
}
|
||
|
||
// Initialize quiz and sorting when DOM is loaded
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
new ModalManager();
|
||
new SectionNavigator();
|
||
new SorobanQuiz();
|
||
new SortingChallenge();
|
||
new MatchingChallenge();
|
||
|
||
// Intercept print attempts and download PDF instead
|
||
window.addEventListener("beforeprint", (e) => {
|
||
e.preventDefault();
|
||
handlePrintRequest();
|
||
});
|
||
|
||
// Also intercept Ctrl+P / Cmd+P
|
||
document.addEventListener("keydown", (e) => {
|
||
if ((e.ctrlKey || e.metaKey) && e.key === "p") {
|
||
e.preventDefault();
|
||
handlePrintRequest();
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|