soroban-abacus-flashcards/out/flashcards.html

23656 lines
1.3 MiB
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>
/* CSS @property declarations for smooth gradient transitions */
@property --sky-gradient {
syntax: '<image>';
inherits: false;
initial-value: linear-gradient(135deg, #ffb347 0%, #ffcc5c 50%, #87ceeb 100%);
}
/* Time-of-day gradient definitions */
:root {
--dawn-gradient: linear-gradient(135deg, #ffb347 0%, #ffcc5c 30%, #87ceeb 70%, #98d8e8 100%);
--morning-gradient: linear-gradient(135deg, #87ceeb 0%, #98d8e8 30%, #b6e2ff 70%, #cce7ff 100%);
--midday-gradient: linear-gradient(135deg, #87ceeb 0%, #a8d8ea 30%, #c7e2f7 70%, #e3f2fd 100%);
--afternoon-gradient: linear-gradient(135deg, #ffecd2 0%, #fcb69f 30%, #ff8a65 70%, #ff7043 100%);
--dusk-gradient: linear-gradient(135deg, #ff8a65 0%, #ff7043 30%, #8e44ad 70%, #5b2c87 100%);
--night-gradient: linear-gradient(135deg, #2c3e50 0%, #34495e 30%, #1a252f 70%, #0f1419 100%);
}
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;
}
/* Welcome Section Styles */
.welcome-hero {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
margin: -60px -40px 0;
padding: 80px 40px 60px;
text-align: center;
position: relative;
overflow: hidden;
}
.welcome-hero::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"><circle cx="20" cy="20" r="2" fill="white" opacity="0.1"/><circle cx="80" cy="30" r="1.5" fill="white" opacity="0.1"/><circle cx="60" cy="70" r="1" fill="white" opacity="0.15"/><circle cx="30" cy="80" r="2.5" fill="white" opacity="0.08"/><circle cx="90" cy="80" r="1" fill="white" opacity="0.1"/></svg>');
animation: float 20s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.welcome-content {
position: relative;
z-index: 1;
max-width: 1200px;
margin: 0 auto;
}
.welcome-title {
font-size: 3.5rem;
font-weight: 800;
margin-bottom: 20px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
}
.welcome-subtitle {
font-size: 1.4rem;
margin-bottom: 60px;
opacity: 0.95;
font-weight: 300;
line-height: 1.6;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 30px;
margin: 60px 0;
padding: 0 20px;
}
.feature-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px 30px;
text-align: center;
color: #333;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.feature-card:hover {
transform: translateY(-10px);
box-shadow: 0 30px 60px rgba(0,0,0,0.15);
}
.feature-icon {
font-size: 3rem;
margin-bottom: 20px;
display: block;
}
.feature-card h3 {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 15px;
color: #333;
}
.feature-card p {
color: #666;
line-height: 1.6;
margin-bottom: 25px;
}
.feature-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
}
.feature-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
}
.quick-start {
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 50px 40px;
margin: 60px 20px 40px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.quick-start h2 {
font-size: 2.2rem;
font-weight: 700;
margin-bottom: 40px;
text-align: center;
}
.steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 30px;
}
.step {
display: flex;
align-items: flex-start;
gap: 20px;
}
.step-number {
background: rgba(255, 255, 255, 0.2);
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1.3rem;
flex-shrink: 0;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.step-content h4 {
font-size: 1.3rem;
font-weight: 600;
margin-bottom: 10px;
}
.step-content p {
opacity: 0.9;
line-height: 1.6;
}
.stats-preview {
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 40px;
margin: 40px 20px 0;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
text-align: center;
}
.stats-preview h3 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 30px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 30px;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 3rem;
font-weight: 800;
margin-bottom: 10px;
background: rgba(255, 255, 255, 0.2);
border-radius: 15px;
padding: 20px;
display: inline-block;
min-width: 80px;
}
.stat-label {
font-size: 1.1rem;
opacity: 0.9;
font-weight: 500;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.welcome-title {
font-size: 2.5rem;
}
.welcome-subtitle {
font-size: 1.2rem;
}
.feature-grid {
grid-template-columns: 1fr;
gap: 20px;
padding: 0 10px;
}
.quick-start {
margin: 40px 10px 20px;
padding: 30px 20px;
}
.steps {
grid-template-columns: 1fr;
gap: 20px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
}
.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: #2E86AB;
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;
}
/* Configuration Showcase Styles */
.config-showcase {
display: flex;
flex-direction: column;
gap: 30px;
margin-top: 30px;
}
.config-item {
background: white;
padding: 25px;
border-radius: 15px;
border: 1px solid rgba(102, 126, 234, 0.1);
position: relative;
transition: all 0.3s ease;
}
.config-item:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.15);
}
.config-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 2px 0 0 2px;
}
.config-label {
font-size: 16px;
margin-bottom: 8px;
color: #2d3748;
}
.config-value {
font-size: 18px;
color: #4a5568;
margin-bottom: 10px;
font-weight: 500;
}
.config-description {
font-size: 14px;
color: #718096;
line-height: 1.5;
margin-top: 8px;
}
.color-palette {
display: flex;
gap: 12px;
margin: 12px 0;
align-items: center;
}
.color-sample {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid rgba(0,0,0,0.1);
cursor: pointer;
transition: transform 0.2s ease;
}
.color-sample:hover {
transform: scale(1.2);
}
.color-sample.heaven {
background: #8B4513;
}
.color-sample.earth {
background: #F4A460;
}
.color-sample.frame {
background: #2F4F4F;
}
.bead-examples {
display: flex;
align-items: center;
gap: 8px;
}
.bead-sample {
width: 20px;
height: 20px;
border: 2px solid rgba(0,0,0,0.2);
transition: transform 0.2s ease;
}
.bead-sample:hover {
transform: scale(1.3);
}
.bead-sample.circle {
border-radius: 50%;
background: #8B4513;
}
.bead-sample.diamond {
transform: rotate(45deg);
background: #8B4513;
}
.bead-sample.square {
border-radius: 2px;
background: #8B4513;
}
.shape-label {
font-size: 13px;
color: #718096;
font-weight: 500;
}
.offline-info {
background: linear-gradient(135deg, #e6fffa 0%, #b2f5ea 100%);
padding: 30px;
border-radius: 20px;
border: 1px solid #4fd1c7;
margin-top: 40px;
text-align: center;
}
.offline-info h3 {
color: #234e52;
margin-top: 0;
margin-bottom: 15px;
font-size: 1.4rem;
}
.offline-info p {
color: #2d5a5f;
line-height: 1.6;
margin-bottom: 20px;
}
.tech-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.feature {
background: rgba(255, 255, 255, 0.7);
padding: 12px 20px;
border-radius: 25px;
font-size: 14px;
font-weight: 500;
color: #234e52;
border: 1px solid rgba(79, 209, 199, 0.3);
}
/* 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);
}
}
/* Arithmetic Section Styles */
.arithmetic-intro {
background: rgba(102, 126, 234, 0.05);
padding: 20px;
border-radius: 12px;
margin-bottom: 30px;
border-left: 4px solid #667eea;
}
.operation-section {
margin: 30px 0;
padding: 25px;
background: #f8fafc;
border-radius: 15px;
border: 1px solid rgba(102, 126, 234, 0.1);
}
.operation-section h4 {
color: #2d3748;
margin-top: 0;
margin-bottom: 20px;
font-size: 1.3rem;
font-weight: 600;
}
.calculation-example {
margin: 20px 0;
}
.calc-step {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 30px;
align-items: start;
}
.step-description {
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.step-description h5 {
color: #667eea;
margin-top: 0;
margin-bottom: 15px;
font-size: 1.1rem;
font-weight: 600;
}
.technique-steps {
margin: 15px 0;
padding-left: 20px;
}
.technique-steps li {
margin-bottom: 8px;
color: #4a5568;
line-height: 1.5;
}
.calc-visual-sequence {
display: flex;
align-items: center;
gap: 15px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
overflow-x: auto;
}
.calc-frame {
text-align: center;
min-width: 120px;
flex-shrink: 0;
}
.frame-label {
font-size: 0.9rem;
font-weight: 600;
color: #2d3748;
margin-bottom: 10px;
}
.calc-arrow {
font-size: 0.85rem;
color: #667eea;
font-weight: 600;
text-align: center;
min-width: 80px;
flex-shrink: 0;
}
.technique-box {
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
padding: 20px;
border-radius: 12px;
margin: 20px 0;
border: 1px solid rgba(102, 126, 234, 0.2);
}
.technique-box h5 {
color: #2d3748;
margin-top: 0;
margin-bottom: 15px;
font-size: 1.1rem;
font-weight: 600;
}
.complement-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 12px;
margin: 15px 0;
}
.complement-pair {
background: white;
padding: 12px 15px;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
color: #4a5568;
border: 1px solid rgba(102, 126, 234, 0.15);
}
.mult-layout, .div-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.mult-setup, .mult-result, .div-setup, .div-result {
text-align: center;
}
.mult-label, .div-label {
font-size: 0.95rem;
font-weight: 600;
color: #2d3748;
margin-bottom: 15px;
}
.tips-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
margin-top: 30px;
}
.tip-card {
background: linear-gradient(135deg, #fff5f5 0%, #f0fff4 100%);
padding: 25px;
border-radius: 15px;
border: 1px solid rgba(102, 126, 234, 0.1);
box-shadow: 0 8px 20px rgba(0,0,0,0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.tip-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(102, 126, 234, 0.15);
}
.tip-card h4 {
color: #2d3748;
margin-top: 0;
margin-bottom: 15px;
font-size: 1.1rem;
font-weight: 600;
}
.tip-card p {
color: #4a5568;
line-height: 1.6;
margin: 0;
}
/* Guide Navigation Styles */
.guide-nav {
background: rgba(102, 126, 234, 0.05);
padding: 20px;
border-radius: 15px;
margin-bottom: 40px;
border: 1px solid rgba(102, 126, 234, 0.1);
}
.guide-nav-buttons {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.guide-nav-btn {
padding: 12px 24px;
border: 2px solid #667eea;
background: white;
color: #667eea;
border-radius: 25px;
cursor: pointer;
font-size: 15px;
font-weight: 600;
transition: all 0.3s ease;
text-decoration: none;
}
.guide-nav-btn:hover {
background: #667eea;
color: white;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
}
.guide-nav-btn.active {
background: #667eea;
color: white;
box-shadow: 0 6px 15px rgba(102, 126, 234, 0.4);
}
.guide-content {
position: relative;
}
.guide-section {
display: none;
animation: fadeInGuide 0.4s ease-out;
}
.guide-section.active {
display: block;
}
@keyframes fadeInGuide {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.section-intro {
text-align: center;
margin-bottom: 40px;
padding: 30px;
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
border-radius: 15px;
border: 1px solid rgba(102, 126, 234, 0.1);
}
.section-intro h3 {
color: #2d3748;
margin: 0 0 15px 0;
font-size: 1.8rem;
font-weight: 600;
}
.section-intro p {
color: #4a5568;
margin: 0;
font-size: 1.1rem;
line-height: 1.6;
}
@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;
}
.calc-step {
grid-template-columns: 1fr;
gap: 20px;
}
.calc-visual-sequence {
padding: 15px;
}
.mult-layout, .div-layout {
grid-template-columns: 1fr;
gap: 20px;
}
.tips-grid {
grid-template-columns: 1fr;
}
}
@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); }
}
/* Particle Effects System */
.particle-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 10000;
}
.particle {
position: absolute;
border-radius: 50%;
pointer-events: none;
}
.particle.star {
background: linear-gradient(45deg, #ffd700, #ffed4e);
box-shadow: 0 0 10px #ffd700;
}
.particle.heart {
background: #ff6b6b;
border-radius: 0;
transform: rotate(-45deg);
}
.particle.heart::before,
.particle.heart::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: #ff6b6b;
border-radius: 50%;
}
.particle.heart::before {
top: -50%;
left: 0;
}
.particle.heart::after {
top: 0;
left: 50%;
}
.particle.circle {
background: linear-gradient(45deg, #667eea, #764ba2);
box-shadow: 0 0 8px rgba(102, 126, 234, 0.4);
}
.particle.diamond {
background: linear-gradient(45deg, #00b894, #00cec9);
transform: rotate(45deg);
border-radius: 0;
}
.particle.confetti {
background: linear-gradient(45deg, #ff6b6b, #feca57);
border-radius: 2px;
}
/* Burst Animation */
@keyframes particleBurst {
0% {
opacity: 1;
transform: translateY(0) scale(0) rotate(0deg);
}
30% {
opacity: 1;
transform: translateY(-20px) scale(1.2) rotate(180deg);
}
100% {
opacity: 0;
transform: translateY(-80px) scale(0.3) rotate(360deg);
}
}
/* Celebration Firework */
@keyframes firework {
0% {
opacity: 1;
transform: translateY(0) scale(0);
}
20% {
opacity: 1;
transform: translateY(-30px) scale(1.5);
}
100% {
opacity: 0;
transform: translateY(-120px) scale(0.5);
}
}
/* Floating Animation */
@keyframes float {
0% {
opacity: 0;
transform: translateY(20px) scale(0) rotate(0deg);
}
20% {
opacity: 1;
transform: translateY(0) scale(1) rotate(72deg);
}
100% {
opacity: 0;
transform: translateY(-100px) scale(0.8) rotate(360deg);
}
}
/* Trail Effect */
@keyframes trail {
0% {
opacity: 1;
transform: translate(0, 0) scale(1);
}
100% {
opacity: 0;
transform: translate(var(--dx), var(--dy)) scale(0.3);
}
}
/* Sparkle Effect */
@keyframes sparkle {
0%, 100% {
opacity: 0;
transform: scale(0) rotate(0deg);
}
50% {
opacity: 1;
transform: scale(1) rotate(180deg);
}
}
.particle.burst {
animation: particleBurst 0.8s ease-out forwards;
}
.particle.firework {
animation: firework 1.2s ease-out forwards;
}
.particle.floating {
animation: float 2s ease-out forwards;
}
.particle.trail {
animation: trail 1.5s ease-out forwards;
}
.particle.sparkle {
animation: sparkle 1s ease-in-out forwards;
}
.input-feedback {
margin-top: 10px;
font-weight: bold;
min-height: 20px;
}
.input-feedback.success {
color: #28a745;
}
.input-feedback.error {
color: #dc3545;
}
.input-feedback.survival {
color: #ff6b6b;
background: linear-gradient(135deg, rgba(255, 107, 107, 0.1), rgba(255, 193, 7, 0.1));
padding: 8px 12px;
border-radius: 8px;
font-weight: 600;
border: 1px solid rgba(255, 107, 107, 0.3);
}
.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;
}
.tag-filter-bar {
padding: 15px;
}
.filter-title {
font-size: 1rem;
margin-bottom: 12px;
}
.tag-filters {
gap: 8px;
}
.tag-filter {
font-size: 0.8rem;
padding: 6px 12px;
}
.challenges-grid {
grid-template-columns: 1fr;
gap: 20px;
margin: 20px 0;
}
.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;
}
/* Race Results Specific Styles */
.race-results-summary {
text-align: center;
}
.race-podium {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
}
.medal-display {
margin-bottom: 25px;
}
.medal-icon {
font-size: 4rem;
margin-bottom: 10px;
display: block;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
}
.medal-rank {
font-size: 1.8rem;
font-weight: 700;
margin-bottom: 5px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.medal-title {
font-size: 1.3rem;
font-weight: 600;
opacity: 0.9;
letter-spacing: 1px;
}
.race-performance {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-top: 25px;
}
.perf-metric {
background: rgba(255, 255, 255, 0.15);
border-radius: 10px;
padding: 15px;
backdrop-filter: blur(10px);
}
.metric-label {
display: block;
font-size: 0.9rem;
opacity: 0.8;
margin-bottom: 5px;
}
.metric-value {
display: block;
font-size: 1.4rem;
font-weight: 700;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.race-statistics {
background: #f8f9fa;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
}
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
.stat-row:last-child {
border-bottom: none;
}
.stat-label {
font-weight: 500;
color: #495057;
}
.stat-value {
font-weight: 700;
color: #007bff;
font-size: 1.1rem;
}
.race-feedback {
background: linear-gradient(135deg, #e3f2fd, #f3e5f5);
border-radius: 10px;
padding: 20px;
border-left: 4px solid #2196f3;
}
.race-feedback p {
margin: 0;
font-size: 1rem;
line-height: 1.5;
color: #495057;
text-align: center;
}
/* Responsive adjustments for race results */
@media (max-width: 768px) {
.race-performance {
grid-template-columns: 1fr;
gap: 15px;
}
.medal-icon {
font-size: 3rem;
}
.medal-rank {
font-size: 1.5rem;
}
.metric-value {
font-size: 1.2rem;
}
}
@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;
}
/* Tag Filter Bar */
.tag-filter-bar {
background: white;
border-radius: 16px;
padding: 25px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.filter-title {
font-size: 1.1rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 15px;
text-align: center;
}
.tag-filters {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.tag-filter {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 25px;
padding: 8px 16px;
font-size: 0.9rem;
font-weight: 500;
color: #6c757d;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 5px;
}
.tag-filter:hover {
background: #e9ecef;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.tag-filter.active {
background: linear-gradient(135deg, #4a90e2, #357abd);
color: white;
border-color: #4a90e2;
box-shadow: 0 4px 15px rgba(74, 144, 226, 0.3);
}
.tag-count {
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 2px 8px;
font-size: 0.8rem;
font-weight: 600;
}
.tag-filter.active .tag-count {
background: rgba(255, 255, 255, 0.3);
}
.challenges-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
margin-top: 0px;
}
.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-tags {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 8px;
}
.tag {
background: rgba(255,255,255,0.2);
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
border: 1px solid rgba(255,255,255,0.3);
}
.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, .game-type-btn, .timeout-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, .game-type-btn.active, .timeout-btn.active {
background: #007bff;
border-color: #007bff;
color: white;
}
.mode-btn:hover, .timer-btn:hover, .game-type-btn:hover, .timeout-btn:hover {
border-color: #007bff;
background: #e3f2fd;
}
.mode-btn.active:hover, .timer-btn.active:hover, .game-type-btn.active:hover, .timeout-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.friends-5-type {
background: linear-gradient(135deg, #ff6b6b, #feca57);
}
.match-card-back.friends-10-type {
background: linear-gradient(135deg, #667eea, #764ba2);
}
.complement-number {
font-size: 1.8rem;
font-weight: bold;
text-align: center;
line-height: 1.2;
white-space: pre-line;
}
.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; }
}
/* Speed Complement Race Enhanced UI Styles */
.complement-intro {
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 15px;
margin-bottom: 30px;
position: relative;
overflow: hidden;
}
.complement-intro::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="20" cy="20" r="2" fill="white" opacity="0.1"/><circle cx="80" cy="30" r="1.5" fill="white" opacity="0.1"/><circle cx="60" cy="70" r="1" fill="white" opacity="0.15"/></svg>');
animation: float 15s ease-in-out infinite;
}
.intro-icon {
font-size: 4rem;
margin-bottom: 15px;
display: block;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
}
.complement-intro h3 {
margin: 0 0 15px 0;
font-size: 2rem;
font-weight: 700;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
position: relative;
z-index: 1;
}
.intro-description {
font-size: 1.1rem;
margin-bottom: 25px;
opacity: 0.95;
position: relative;
z-index: 1;
line-height: 1.4;
}
.example-demo {
background: rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 20px;
backdrop-filter: blur(10px);
position: relative;
z-index: 1;
}
.demo-equation {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin-bottom: 10px;
font-size: 2rem;
font-weight: bold;
}
.demo-number {
background: #ff6b6b;
color: white;
padding: 10px 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.demo-answer {
background: #feca57;
color: #333;
padding: 10px 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
animation: pulse 2s infinite;
}
.demo-target {
background: #48dbfb;
color: white;
padding: 10px 15px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.demo-plus, .demo-equals {
color: white;
font-weight: bold;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.demo-instruction {
font-size: 1.1rem;
color: #fff;
opacity: 0.9;
}
/* Enhanced Game Display */
.complement-display {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
padding: 40px;
margin-bottom: 30px;
color: white;
position: relative;
overflow: hidden;
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);
}
.complement-display::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"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/></svg>');
pointer-events: none;
}
.challenge-prompt {
text-align: center;
position: relative;
z-index: 1;
margin-bottom: 30px;
}
/* Coal Spilling Animation */
@keyframes coalSpill {
0% {
transform: translateY(0px) rotate(0deg);
opacity: 1;
}
50% {
transform: translateY(40px) translateX(20px) rotate(180deg);
opacity: 0.8;
}
100% {
transform: translateY(80px) translateX(40px) rotate(360deg);
opacity: 0;
}
}
.coal-chunk {
color: #2c2c2c;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
/* Pressure Alarm Animation */
@keyframes pressureAlarm {
0%, 100% {
filter: brightness(1) saturate(1);
transform: scale(1);
}
25% {
filter: brightness(1.5) saturate(2) hue-rotate(0deg);
transform: scale(1.05);
}
50% {
filter: brightness(2) saturate(3) hue-rotate(15deg);
transform: scale(1.1);
}
75% {
filter: brightness(1.5) saturate(2) hue-rotate(-15deg);
transform: scale(1.05);
}
}
/* Route Completion Celebration */
.route-completion-celebration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: celebrationFadeIn 0.5s ease-out;
}
.celebration-content {
background: linear-gradient(135deg, #ffd700, #ffed4e);
color: #333;
padding: 40px;
border-radius: 20px;
text-align: center;
box-shadow: 0 20px 40px rgba(255, 215, 0, 0.3);
animation: celebrationBounce 0.8s ease-out;
max-width: 400px;
}
.celebration-content h2 {
margin: 0 0 15px 0;
font-size: 1.8rem;
font-weight: 700;
}
.celebration-content p {
margin: 0 0 20px 0;
font-size: 1.1rem;
font-weight: 600;
}
.progress-stats {
display: flex;
flex-direction: column;
gap: 8px;
font-size: 0.95rem;
font-weight: 600;
}
.progress-stats div {
background: rgba(255, 255, 255, 0.3);
padding: 8px 12px;
border-radius: 8px;
}
@keyframes celebrationFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes celebrationBounce {
0% {
transform: scale(0.3) rotate(-10deg);
opacity: 0;
}
50% {
transform: scale(1.05) rotate(2deg);
}
100% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
}
.equation-visual {
display: flex;
align-items: center;
justify-content: center;
gap: 25px;
flex-wrap: wrap;
}
.challenge-number-container,
.answer-container,
.target-container {
text-align: center;
position: relative;
}
.challenge-number {
font-size: 4rem;
font-weight: 700;
background: linear-gradient(135deg, #ff6b6b, #feca57);
color: white;
padding: 20px 25px;
border-radius: 20px;
box-shadow: 0 10px 25px rgba(255, 107, 107, 0.4);
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
animation: bounceIn 1s ease-out;
}
.answer-display {
font-size: 4rem;
font-weight: 700;
background: linear-gradient(135deg, #feca57, #ff9ff3);
color: white;
padding: 20px 25px;
border-radius: 20px;
box-shadow: 0 10px 25px rgba(254, 202, 87, 0.4);
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
text-align: center;
min-width: 100px;
display: flex;
align-items: center;
justify-content: center;
animation: pulse 2s infinite;
transition: all 0.3s ease;
user-select: none;
}
.answer-display.typing {
animation: none;
transform: scale(1.1);
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.3), 0 15px 35px rgba(254, 202, 87, 0.6);
background: linear-gradient(135deg, #ff9ff3, #feca57);
}
.answer-display.empty {
color: rgba(255, 255, 255, 0.7);
}
.target-number {
font-size: 4rem;
font-weight: 700;
background: linear-gradient(135deg, #48dbfb, #0abde3);
color: white;
padding: 20px 25px;
border-radius: 20px;
box-shadow: 0 10px 25px rgba(72, 219, 251, 0.4);
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.equation-symbol {
font-size: 3rem;
font-weight: bold;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.number-label,
.answer-label,
.target-label {
font-size: 0.9rem;
margin-top: 8px;
opacity: 0.8;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 1px;
}
.answer-arrow {
font-size: 1.5rem;
margin-top: 5px;
animation: bounce 1s infinite;
}
/* Enhanced Timer */
.timer-section {
text-align: center;
position: relative;
z-index: 1;
}
.timer-label {
font-size: 1.1rem;
margin-bottom: 10px;
opacity: 0.9;
font-weight: 500;
}
.timer-bar {
background: rgba(255, 255, 255, 0.2);
height: 12px;
border-radius: 25px;
overflow: hidden;
margin-bottom: 10px;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.2);
}
.timer-fill {
height: 100%;
background: linear-gradient(90deg, #ff6b6b, #feca57, #ff6b6b);
border-radius: 25px;
transition: width 0.1s linear;
box-shadow: 0 0 10px rgba(255, 107, 107, 0.6);
animation: shimmer 2s infinite;
}
.timer-text {
font-size: 1.3rem;
font-weight: bold;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
/* Race Track Styles */
.race-track-section {
margin-top: 25px;
padding: 50px 150px 20px 150px;
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
border: 2px solid rgba(255, 255, 255, 0.2);
}
@media (max-width: 768px) {
.race-track-section {
padding: 30px 80px 20px 80px;
}
}
@media (max-width: 480px) {
.race-track-section {
padding: 30px 40px 20px 40px;
}
}
.race-label {
text-align: center;
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 20px;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.race-track {
position: relative;
height: 150px;
background: linear-gradient(to right, #4CAF50 0%, #8BC34A 25%, #CDDC39 50%, #FFC107 75%, #FF5722 100%);
border-radius: 60px;
margin: 0;
overflow: visible;
box-shadow: inset 0 4px 8px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3);
}
.track-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
90deg,
transparent 0px,
transparent 18px,
rgba(255,255,255,0.1) 18px,
rgba(255,255,255,0.1) 20px
);
border-radius: 60px;
overflow: hidden;
}
.track-line {
position: absolute;
top: 0;
bottom: 0;
width: 3px;
background: rgba(255, 255, 255, 0.6);
box-shadow: 0 0 6px rgba(255,255,255,0.8);
}
.start-line { left: 5%; }
.quarter-line { left: 25%; opacity: 0.5; }
.half-line { left: 50%; }
.three-quarter-line { left: 75%; opacity: 0.5; }
.finish-line { left: 95%; }
.racer {
position: absolute;
width: 50px;
height: 50px;
background: white;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: left 0.6s ease-out, transform 0.3s ease;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
left: 2%;
z-index: 10;
}
.racer:hover {
transform: scale(1.1);
}
.player-racer {
top: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 3px solid #FFD700;
z-index: 10;
}
.ai-racer {
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
border: 2px solid #ff4757;
}
#ai-racer-1 {
top: 35px;
z-index: 8;
}
#ai-racer-2 {
top: 60px;
z-index: 9;
}
.racer-character {
font-size: 1.5rem;
line-height: 1;
animation: bounce 1s infinite alternate;
}
.racer-label {
position: absolute;
bottom: -25px;
left: 50%;
transform: translateX(-50%);
font-size: 0.75rem;
font-weight: bold;
color: white;
background: rgba(0,0,0,0.7);
padding: 2px 6px;
border-radius: 8px;
white-space: nowrap;
text-shadow: none;
}
.racer-progress {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
font-size: 0.7rem;
font-weight: bold;
color: #FFD700;
background: rgba(0,0,0,0.8);
padding: 1px 4px;
border-radius: 6px;
text-shadow: none;
}
.finish-zone {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 60px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
border-top-right-radius: 60px;
border-bottom-right-radius: 60px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
z-index: 2;
}
.finish-flag {
font-size: 1.5rem;
animation: wave 1s infinite ease-in-out;
}
.finish-text {
font-size: 0.6rem;
font-weight: bold;
color: #333;
text-shadow: 0 1px 2px rgba(255,255,255,0.8);
transform: rotate(-90deg);
margin-top: 5px;
}
.race-stats {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 15px;
color: white;
}
.race-stat {
text-align: center;
background: rgba(255, 255, 255, 0.1);
padding: 8px 15px;
border-radius: 15px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.race-stat .stat-label {
font-size: 0.85rem;
opacity: 0.9;
margin-right: 5px;
}
/* Race Animations */
@keyframes bounce {
0% { transform: translateY(0); }
100% { transform: translateY(-3px); }
}
@keyframes wave {
0%, 100% { transform: rotate(-10deg); }
50% { transform: rotate(10deg); }
}
/* Circular Track for Survival Mode */
.race-track-section.circular-track {
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
/* Prevent container from affecting track shape */
flex-shrink: 0;
}
/* Infinite Mode Track for Sprint Mode */
.race-track-section.infinite-mode {
background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #667eea 100%);
position: relative;
}
.race-track-section.infinite-mode::before {
content: '⚡ TIME-BASED CHALLENGE ⚡';
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 215, 0, 0.9);
color: #333;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 1px;
z-index: 10;
}
.race-track-section.infinite-mode .race-track {
background: linear-gradient(90deg,
transparent 0%,
rgba(255,255,255,0.1) 10%,
rgba(255,255,255,0.2) 50%,
rgba(255,255,255,0.1) 90%,
transparent 100%);
border: 2px dashed rgba(255, 215, 0, 0.6);
}
/* Steam Train Journey Visualization for Sprint Mode */
.race-track-section.steam-journey {
/* Dynamic sky gradient based on time of day with smooth transitions */
background: var(--sky-gradient, var(--dawn-gradient));
transition: --sky-gradient 3s ease-in-out, background 2s ease-out;
padding: 20px;
height: 400px;
max-height: 400px;
position: relative;
overflow: hidden;
}
/* Underground tunnel visualization */
.tunnel-container {
display: flex;
justify-content: space-around;
align-items: flex-start;
padding: 60px 40px 20px;
height: 100%;
position: relative;
max-height: 240px; /* Constrain to fit in container */
}
.tunnel-shaft {
width: 80px;
background: #4a4a4a;
border: 3px solid #333;
border-radius: 8px;
position: relative;
min-height: 50px;
transition: height 0.3s ease-out;
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
}
.tunnel-shaft::before {
content: '';
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 20px;
background: #90EE90;
border-radius: 50%;
border: 2px solid #6B8E23;
}
.fox-digger {
position: absolute;
bottom: 10px; /* Position inside tunnel, not at the bottom */
left: 50%;
transform: translateX(-50%);
font-size: 2rem;
z-index: 5;
transition: all 0.3s ease;
}
.fox-digger.digging {
animation: foxDigging 0.6s ease-in-out;
}
.fox-digger.idle {
animation: foxBreathe 2s ease-in-out infinite;
}
/* Active digging animation */
@keyframes foxDigging {
0% {
transform: translateX(-50%) translateY(0) rotate(0deg);
}
20% {
transform: translateX(-50%) translateY(-8px) rotate(-10deg) scaleY(0.9);
}
40% {
transform: translateX(-50%) translateY(8px) rotate(10deg) scaleY(1.1);
}
60% {
transform: translateX(-50%) translateY(-5px) rotate(-5deg) scaleY(0.95);
}
80% {
transform: translateX(-50%) translateY(3px) rotate(5deg) scaleY(1.05);
}
100% {
transform: translateX(-50%) translateY(0) rotate(0deg);
}
}
/* Breathing animation when idle */
@keyframes foxBreathe {
0%, 100% {
transform: translateX(-50%) scale(1);
}
50% {
transform: translateX(-50%) scale(1.02);
}
}
/* Deeper foxes appear smaller (perspective effect) */
.tunnel-shaft.depth-deep .fox-digger {
font-size: 1.8rem;
bottom: 15px;
}
.tunnel-shaft.depth-very-deep .fox-digger {
font-size: 1.6rem;
bottom: 20px;
animation: foxDigDeep 0.8s ease-in-out;
}
@keyframes foxDigDeep {
0% {
transform: translateX(-50%) translateY(0) rotate(0deg) scale(1);
}
25% {
transform: translateX(-50%) translateY(-10px) rotate(-15deg) scale(0.9) scaleY(0.8);
}
50% {
transform: translateX(-50%) translateY(10px) rotate(15deg) scale(1.1) scaleY(1.2);
}
75% {
transform: translateX(-50%) translateY(-5px) rotate(-8deg) scale(0.95);
}
100% {
transform: translateX(-50%) translateY(0) rotate(0deg) scale(1);
}
}
.tunnel-depth {
position: absolute;
bottom: -25px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.9);
color: #333;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
min-width: 40px;
text-align: center;
}
.tunnel-player .tunnel-depth {
background: rgba(34, 197, 94, 0.9);
color: white;
}
.tunnel-ai .tunnel-depth {
background: rgba(239, 68, 68, 0.9);
color: white;
}
/* Dirt particles effect when digging */
@keyframes dirtFly {
0% {
opacity: 1;
transform: translate(0, 0) scale(1);
}
100% {
opacity: 0;
transform: translate(var(--fly-x), var(--fly-y)) scale(0.3);
}
}
.dirt-particle {
position: absolute;
width: 4px;
height: 4px;
background: #8B4513;
border-radius: 50%;
animation: dirtFly 0.6s ease-out forwards;
pointer-events: none;
}
/* Treasure animations */
@keyframes treasureFloat {
0% {
opacity: 0;
transform: translate(-50%, 0) scale(0);
}
20% {
opacity: 1;
transform: translate(-50%, -20px) scale(1.2);
}
100% {
opacity: 0;
transform: translate(-50%, -80px) scale(0.8);
}
}
@keyframes treasureSlideIn {
0% {
opacity: 0;
transform: translateX(100px);
}
20% {
opacity: 1;
transform: translateX(0);
}
80% {
opacity: 1;
transform: translateX(0);
}
100% {
opacity: 0;
transform: translateX(-100px);
}
}
/* Speech bubbles for tunnel mode */
.tunnel-speech {
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
}
/* Screen shake animation for deep digging */
@keyframes screenShake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-2px); }
75% { transform: translateX(2px); }
}
/* Day/Night Cycle CSS Variables */
:root {
/* Dawn (0-17%) */
--dawn-gradient: linear-gradient(180deg,
#4a5568 0%, #667eea 20%, #f093fb 40%, #f5deb3 60%, #90EE90 80%, #654321 100%);
/* Morning (17-33%) */
--morning-gradient: linear-gradient(180deg,
#87ceeb 0%, #f0e68c 20%, #90EE90 50%, #8FBC8F 80%, #654321 100%);
/* Midday (33-67%) */
--midday-gradient: linear-gradient(180deg,
#87ceeb 0%, #87ceeb 30%, #90EE90 60%, #228B22 80%, #654321 100%);
/* Afternoon (67-83%) */
--afternoon-gradient: linear-gradient(180deg,
#ff7f50 0%, #ffd700 20%, #90EE90 50%, #8B4513 80%, #654321 100%);
/* Dusk (83-92%) */
--dusk-gradient: linear-gradient(180deg,
#4b0082 0%, #ff6347 30%, #ffa500 50%, #8B4513 70%, #2f4f4f 100%);
/* Night (92-100%) */
--night-gradient: linear-gradient(180deg,
#191970 0%, #2f4f4f 40%, #1a1a1a 70%, #000000 100%);
}
.steam-journey-header {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 215, 0, 0.95);
color: #333;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 700;
letter-spacing: 1px;
z-index: 10;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
text-align: center;
}
.route-progress {
font-size: 0.75rem;
font-weight: 600;
margin-top: 4px;
opacity: 0.8;
letter-spacing: 0.5px;
}
/* Winding Route Map */
.route-map {
position: relative;
width: 100%;
height: 350px;
overflow: hidden;
}
.route-path {
position: absolute;
width: 800px;
height: 600px;
top: -100px;
left: 50%;
transform: translateX(-50%);
}
/* SVG path for the winding route */
.train-route {
fill: none;
stroke: #8B4513;
stroke-width: 8;
stroke-dasharray: 15, 5;
stroke-linecap: round;
}
.train-route-bg {
fill: none;
stroke: #654321;
stroke-width: 12;
stroke-linecap: round;
}
/* Train locomotive on the route */
.train-locomotive {
position: absolute;
width: 60px;
height: 40px;
background: linear-gradient(135deg, #2c3e50, #34495e);
border-radius: 8px;
border: 2px solid #1a252f;
box-shadow: 0 3px 10px rgba(0,0,0,0.4);
transition: transform 0.2s ease-out, left 0.1s linear, top 0.1s linear;
transform-origin: center center;
z-index: 5;
/* Start at the beginning of the route */
left: 50px;
top: 300px;
transform: translate(-50%, -50%);
}
.train-locomotive::before {
content: '🚂';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scaleX(-1);
font-size: 1.8rem;
z-index: 6;
}
.train-math-display {
position: absolute;
background: rgba(255, 255, 255, 0.95);
border: 2px solid #4a90e2;
border-radius: 12px;
padding: 8px 12px;
font-size: 1.2rem;
font-weight: bold;
color: #333;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 7;
pointer-events: none;
white-space: nowrap;
transform: translate(-50%, -100%);
font-family: 'Arial', sans-serif;
}
/* Steam Pressure Instrument Panel */
.instrument-panel {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 10;
}
.panel-frame {
background: linear-gradient(135deg, #667eea, #764ba2);
border: 3px solid #5a67d8;
border-radius: 20px;
padding: 18px;
box-shadow:
0 10px 30px rgba(102, 126, 234, 0.3),
inset 0 2px 8px rgba(255, 255, 255, 0.2);
position: relative;
}
.panel-background {
position: absolute;
top: 6px;
left: 6px;
right: 6px;
bottom: 6px;
background: linear-gradient(45deg, #667eea, #764ba2);
border-radius: 12px;
border: 2px solid rgba(255, 255, 255, 0.1);
}
.pressure-gauge-container {
position: relative;
z-index: 2;
text-align: center;
}
.pressure-gauge {
display: block;
margin: 0 auto 10px auto;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.gauge-background {
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.4));
}
.gauge-progress {
transition: stroke-dashoffset 0.5s ease-out;
filter: drop-shadow(0 0 8px rgba(231, 76, 60, 0.6));
}
.pressure-needle {
transition: transform 0.5s ease-out;
transform-origin: 100px 100px;
}
.gauge-title {
color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 5px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.gauge-value {
color: #ffd700;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 0.9rem;
font-weight: 800;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
/* Momentum gauge */
.momentum-display {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 200px;
z-index: 10;
}
.momentum-gauge {
width: 100%;
height: 12px;
background: rgba(0,0,0,0.3);
border-radius: 6px;
border: 1px solid #333;
overflow: hidden;
}
.momentum-bar {
height: 100%;
background: linear-gradient(90deg, #e74c3c 0%, #f39c12 30%, #27ae60 100%);
transition: width 0.3s ease-out;
width: 0%;
}
.momentum-label {
text-align: center;
color: white;
font-size: 0.8rem;
font-weight: 600;
margin-top: 5px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
}
/* City stations along the route */
.train-station {
position: absolute;
width: 20px;
height: 20px;
background: #8B4513;
border: 2px solid #654321;
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 4;
}
.train-station::after {
content: attr(data-city);
position: absolute;
top: 25px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.7);
color: white;
padding: 2px 6px;
border-radius: 8px;
font-size: 0.7rem;
white-space: nowrap;
font-weight: 600;
}
.train-station.active {
background: #ffd700;
border-color: #ffb347;
animation: stationPulse 1s ease-in-out infinite alternate;
}
@keyframes stationPulse {
0% { transform: translate(-50%, -50%) scale(1); }
100% { transform: translate(-50%, -50%) scale(1.2); }
}
/* Passenger car display */
.passenger-car {
position: absolute;
bottom: 60px;
right: 20px;
background: rgba(255,255,255,0.9);
border-radius: 10px;
padding: 10px;
min-width: 150px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.passenger-car h4 {
margin: 0 0 8px 0;
font-size: 0.9rem;
color: #333;
}
.passenger-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.passenger {
background: #667eea;
color: white;
padding: 2px 6px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 600;
}
.passenger.urgent {
background: #e74c3c;
animation: passengerUrgent 1s ease-in-out infinite alternate;
}
@keyframes passengerUrgent {
0% { opacity: 1; }
100% { opacity: 0.6; }
}
/* Coal shoveling animation - enhanced */
.coal-shoveler {
position: absolute;
bottom: 80px;
left: 30px;
font-size: 2rem;
z-index: 6;
transition: all 0.3s ease;
}
.coal-shoveler.shoveling {
animation: shoveling 0.8s ease-in-out;
}
@keyframes shoveling {
0% { transform: rotate(0deg) scale(1); }
20% { transform: rotate(-25deg) scale(0.9); }
40% { transform: rotate(25deg) scale(1.2); filter: brightness(1.3); }
60% { transform: rotate(-15deg) scale(1.1); }
80% { transform: rotate(10deg) scale(1.05); }
100% { transform: rotate(0deg) scale(1); }
}
/* Coal particles effect */
.coal-particle {
position: absolute;
background: #2c2c2c;
border-radius: 50%;
pointer-events: none;
z-index: 4;
}
@keyframes coalFly {
0% {
transform: translate(0, 0) scale(1);
opacity: 1;
}
50% {
transform: translate(-20px, -30px) scale(0.8);
opacity: 0.8;
}
100% {
transform: translate(-40px, -60px) scale(0.3);
opacity: 0;
}
}
/* Steam puffs from locomotive */
.steam-puff {
position: absolute;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
pointer-events: none;
z-index: 3;
}
@keyframes steamRise {
0% {
transform: translate(0, 0) scale(0.5);
opacity: 0.9;
}
50% {
transform: translate(10px, -40px) scale(1.2);
opacity: 0.6;
}
100% {
transform: translate(20px, -80px) scale(2);
opacity: 0;
}
}
/* Momentum bar flash effect */
.momentum-boost {
animation: momentumBoost 0.8s ease-out;
}
@keyframes momentumBoost {
0% {
box-shadow: 0 0 0px rgba(40, 167, 69, 0.6);
transform: scale(1);
}
30% {
box-shadow: 0 0 20px rgba(40, 167, 69, 0.8);
transform: scale(1.05);
}
100% {
box-shadow: 0 0 5px rgba(40, 167, 69, 0.3);
transform: scale(1);
}
}
/* Steam effects */
.steam-effect {
position: absolute;
top: 10px;
left: 30px;
width: 8px;
height: 8px;
background: rgba(255,255,255,0.8);
border-radius: 50%;
animation: steamRise 2s ease-out infinite;
z-index: 3;
}
@keyframes steamRise {
0% {
opacity: 0.8;
transform: translateY(0) scale(0.5);
}
100% {
opacity: 0;
transform: translateY(-60px) scale(2);
}
}
/* Geographical landmarks */
.landmark {
position: absolute;
font-size: 1.5rem;
z-index: 2;
opacity: 0.7;
}
.landmark.mountain { font-size: 2rem; }
.landmark.tree { font-size: 1.2rem; }
.landmark.bridge { font-size: 1.8rem; }
/* Time of day indicator */
.time-display {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0,0,0,0.7);
color: white;
padding: 5px 10px;
border-radius: 15px;
font-size: 0.8rem;
font-weight: 600;
}
/* Hide linear track elements when in circular mode */
.race-track.circular .track-background,
.race-track.circular .track-line {
display: none;
}
.race-track.circular {
width: 400px;
height: 400px;
border-radius: 50%;
position: relative;
/* Maintain circular aspect ratio */
flex-shrink: 0;
aspect-ratio: 1 / 1;
/* Create donut shape with inner and outer borders */
background:
radial-gradient(circle at center, transparent 35%, rgba(34, 197, 94, 0.2) 35%, rgba(34, 197, 94, 0.2) 65%, transparent 65%);
border: 6px solid rgba(34, 197, 94, 0.6);
box-shadow:
inset 0 0 30px rgba(34, 197, 94, 0.3),
0 0 20px rgba(0,0,0,0.3);
}
/* Responsive circular track for smaller screens */
@media (max-width: 768px) {
.race-track.circular {
width: 300px;
height: 300px;
}
}
@media (max-width: 480px) {
.race-track.circular {
width: 250px;
height: 250px;
}
}
/* Inner donut hole */
.race-track.circular::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 140px;
height: 140px;
background: rgba(0,0,0,0.1);
border: 4px solid rgba(34, 197, 94, 0.4);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
/* Position racers on circular track */
.race-track.circular .racer {
position: absolute;
width: 40px;
height: 40px;
transition: all 0.6s ease-out;
}
/* Counteract rotation for speech bubbles on circular track */
.race-track.circular .racer .speech-bubble {
transform: translateY(-50%) rotate(var(--counter-rotation, 0deg));
}
/* Speech Bubbles */
.speech-bubble {
position: absolute;
top: -45px;
left: 60px;
transform: translateY(-50%);
z-index: 200;
opacity: 0;
visibility: hidden;
transition: all 0.4s ease;
pointer-events: none;
}
.speech-bubble.visible {
opacity: 1;
visibility: visible;
animation: bubblePopIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.bubble-content {
background: white;
border-radius: 18px;
padding: 8px 12px;
min-width: 120px;
max-width: 200px;
font-size: 0.8rem;
font-weight: 600;
color: #2c3e50;
text-align: center;
line-height: 1.2;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border: 2px solid #f0f2f5;
position: relative;
}
.bubble-content::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
border-radius: 20px;
background: linear-gradient(135deg, #4a90e2, #357abd);
z-index: -1;
}
.bubble-tail {
position: absolute;
top: 50%;
left: -8px;
transform: translateY(-50%);
width: 0;
height: 0;
border: 8px solid transparent;
border-right-color: white;
z-index: 1;
}
.bubble-tail::before {
content: '';
position: absolute;
top: -9px;
left: 2px;
width: 0;
height: 0;
border: 9px solid transparent;
border-right-color: #4a90e2;
z-index: -1;
}
/* Different bubble styles for different AI personalities */
#ai-racer-1 .bubble-content {
background: linear-gradient(135deg, #ff6b6b, #feca57);
color: white;
border-color: #ff6b6b;
}
#ai-racer-1 .speech-bubble {
left: -140px; /* Position behind the racer */
top: -10px; /* Align with ai-racer-1 which is at top: 35px */
}
#ai-racer-1 .bubble-tail {
border-right-color: #ff6b6b;
left: auto;
right: -8px; /* Point from the right side */
border-right-color: transparent;
border-left-color: #ff6b6b;
}
#ai-racer-1 .bubble-tail::before {
left: auto;
right: 2px;
border-right-color: transparent;
border-left-color: #ff6b6b;
}
#ai-racer-2 .speech-bubble {
top: 15px; /* Align with ai-racer-2 which is at top: 60px */
}
#ai-racer-2 .bubble-content {
background: linear-gradient(135deg, #4ecdc4, #44a08d);
color: white;
border-color: #4ecdc4;
}
#ai-racer-2 .bubble-tail {
border-right-color: #4ecdc4;
}
@keyframes bubblePopIn {
0% {
opacity: 0;
transform: translateY(-50%) translateX(10px) scale(0.3);
}
50% {
opacity: 1;
transform: translateY(-50%) translateX(-5px) scale(1.1);
}
100% {
opacity: 1;
transform: translateY(-50%) translateX(0) scale(1);
}
}
/* Player Race Animations */
.racer.tripped {
animation: tripAndRecover 1.2s ease-out;
}
.racer.moving-backwards {
animation: moveBackwards 0.8s ease-out;
}
@keyframes tripAndRecover {
0% { transform: rotate(0deg) translateY(0px); }
20% { transform: rotate(-15deg) translateY(8px); }
40% { transform: rotate(-25deg) translateY(15px); }
60% { transform: rotate(-20deg) translateY(12px); }
80% { transform: rotate(-5deg) translateY(3px); }
100% { transform: rotate(0deg) translateY(0px); }
}
@keyframes moveBackwards {
0% { transform: translateX(0px) scale(1); }
30% { transform: translateX(-20px) scale(0.9); }
60% { transform: translateX(-15px) scale(0.95); }
100% { transform: translateX(0px) scale(1); }
}
@keyframes raceFinish {
0% { transform: scale(1) rotate(0deg); }
50% { transform: scale(1.2) rotate(180deg); }
100% { transform: scale(1) rotate(360deg); }
}
.racer.winner {
animation: raceFinish 1s ease-in-out;
border-color: #FFD700;
box-shadow: 0 0 20px #FFD700;
}
.racer.celebrating {
animation: bounce 0.3s infinite alternate;
}
/* Special bounce for circular track that preserves rotation */
.race-track.circular .racer.celebrating {
animation: circularBounce 0.3s infinite alternate;
}
@keyframes circularBounce {
0% {
transform: rotate(var(--racer-rotation, 0deg)) scale(1) translateY(0px);
}
100% {
transform: rotate(var(--racer-rotation, 0deg)) scale(1.1) translateY(-2px);
}
}
/* Enhanced Feedback Area */
.complement-feedback-area {
text-align: center;
background: white;
border-radius: 20px;
padding: 25px;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
margin-bottom: 30px;
position: relative;
}
.input-hint {
font-size: 1.1rem;
color: #666;
opacity: 0.8;
margin-top: 10px;
}
/* Enhanced Game Style Buttons */
.style-buttons {
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 15px;
}
.game-style-btn {
background: white;
border: 2px solid #e9ecef;
border-radius: 15px;
padding: 20px 25px;
cursor: pointer;
transition: all 0.3s ease;
text-align: left;
width: 100%;
display: flex;
align-items: center;
gap: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
position: relative;
overflow: hidden;
}
.game-style-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;
}
.game-style-btn:hover {
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15);
}
.game-style-btn:hover::before {
left: 100%;
}
.game-style-btn.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-color: #667eea;
color: white;
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.game-style-btn.active:hover {
transform: translateY(-3px);
box-shadow: 0 12px 35px rgba(102, 126, 234, 0.4);
}
.game-style-btn.active::before {
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
}
.style-icon {
font-size: 2.5rem;
line-height: 1;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
flex-shrink: 0;
}
.game-style-btn.active .style-icon {
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
}
.style-content {
flex: 1;
}
.style-title {
font-size: 1.3rem;
font-weight: 700;
margin-bottom: 5px;
color: #333;
}
.game-style-btn.active .style-title {
color: white;
}
.style-description {
font-size: 0.95rem;
color: #666;
line-height: 1.4;
opacity: 0.9;
}
.game-style-btn.active .style-description {
color: rgba(255, 255, 255, 0.9);
}
/* Hover effects for individual styles */
.game-style-btn[data-style="practice"]:not(.active):hover {
border-color: #28a745;
box-shadow: 0 8px 25px rgba(40, 167, 69, 0.15);
}
.game-style-btn[data-style="sprint"]:not(.active):hover {
border-color: #ffc107;
box-shadow: 0 8px 25px rgba(255, 193, 7, 0.15);
}
.game-style-btn[data-style="survival"]:not(.active):hover {
border-color: #dc3545;
box-shadow: 0 8px 25px rgba(220, 53, 69, 0.15);
}
/* Enhanced Feedback */
.input-feedback {
margin-top: 15px;
font-size: 1.2rem;
font-weight: 600;
min-height: 30px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.input-feedback.correct {
color: #28a745;
animation: success 0.6s ease-out;
}
.input-feedback.incorrect {
color: #dc3545;
animation: shake 0.6s ease-out;
}
.input-feedback.timeout {
color: #ffc107;
animation: fadeIn 0.5s ease-out;
}
/* Adaptive Difficulty Feedback */
.adaptive-feedback {
margin-top: 10px;
font-size: 0.95rem;
font-weight: 500;
min-height: 20px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.4s ease;
opacity: 0.8;
}
.adaptive-feedback.learning {
color: #17a2b8;
background: rgba(23, 162, 184, 0.1);
border-radius: 15px;
padding: 8px 16px;
}
.adaptive-feedback.struggling {
color: #dc3545;
background: rgba(220, 53, 69, 0.1);
border-radius: 15px;
padding: 8px 16px;
}
.adaptive-feedback.mastered {
color: #28a745;
background: rgba(40, 167, 69, 0.1);
border-radius: 15px;
padding: 8px 16px;
}
.adaptive-feedback.adapted {
color: #6f42c1;
background: rgba(111, 66, 193, 0.1);
border-radius: 15px;
padding: 8px 16px;
animation: adaptivePulse 1s ease-out;
}
@keyframes adaptivePulse {
0% { transform: scale(1); opacity: 0; }
50% { transform: scale(1.05); opacity: 1; }
100% { transform: scale(1); opacity: 0.8; }
}
/* Animations */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes glow {
0% { box-shadow: 0 0 5px rgba(255, 255, 255, 0.5); }
100% { box-shadow: 0 0 20px rgba(255, 255, 255, 0.8), 0 0 30px rgba(255, 255, 255, 0.6); }
}
@keyframes bounceIn {
0% { transform: scale(0.3) rotate(-10deg); opacity: 0; }
50% { transform: scale(1.1) rotate(5deg); }
100% { transform: scale(1) rotate(0deg); opacity: 1; }
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shimmer {
0% { background-position: -200px 0; }
100% { background-position: 200px 0; }
}
/* Lapping Celebration Animations */
@keyframes lapCelebrationBounce {
0% {
transform: translateX(-50%) scale(0);
opacity: 0;
rotation: -10deg;
}
30% {
transform: translateX(-50%) scale(1.2);
opacity: 1;
rotation: 5deg;
}
60% {
transform: translateX(-50%) scale(0.95);
rotation: -2deg;
}
100% {
transform: translateX(-50%) scale(1);
opacity: 1;
rotation: 0deg;
}
}
.lapping-celebration {
animation: lappingPulse 1.5s ease-in-out;
transform-origin: center;
}
@keyframes lappingPulse {
0%, 100% { transform: scale(1) rotate(0deg); }
25% { transform: scale(1.3) rotate(-5deg); box-shadow: 0 0 25px #ffd700; }
50% { transform: scale(1.1) rotate(5deg); box-shadow: 0 0 35px #ffd700; }
75% { transform: scale(1.2) rotate(-3deg); box-shadow: 0 0 20px #ffd700; }
}
/* Race Countdown */
.race-countdown {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: none;
justify-content: center;
align-items: center;
z-index: 10000;
backdrop-filter: blur(5px);
}
.countdown-display {
text-align: center;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.countdown-number {
font-size: 15rem;
font-weight: 900;
line-height: 1;
text-shadow: 0 0 50px rgba(255, 255, 255, 0.5);
margin-bottom: 20px;
animation: countdownPulse 1s ease-out;
}
.countdown-text {
font-size: 2rem;
font-weight: 600;
margin-bottom: 40px;
opacity: 0.9;
}
.countdown-go {
font-size: 20rem;
font-weight: 900;
background: linear-gradient(135deg, #ff6b6b, #feca57, #48dbfb, #ff9ff3);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 50px rgba(255, 255, 255, 0.8);
animation: goAnimation 1s ease-out;
}
@keyframes countdownPulse {
0% {
transform: scale(0.5);
opacity: 0;
}
20% {
transform: scale(1.2);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes goAnimation {
0% {
transform: scale(0.3) rotate(-10deg);
opacity: 0;
}
50% {
transform: scale(1.2) rotate(5deg);
opacity: 1;
}
100% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes success {
0% { transform: scale(0.8); opacity: 0; }
50% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Responsive Design */
@media (max-width: 768px) {
.equation-visual {
gap: 15px;
}
.challenge-number,
.answer-display,
.target-number {
font-size: 2.5rem;
padding: 15px 20px;
}
.answer-display {
min-width: 80px;
}
.equation-symbol {
font-size: 2rem;
}
}
</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="welcome">🏠 Welcome</button>
<button class="nav-btn" data-section="guide">📚 Complete Guide</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">📊 Practice</button>
</div>
</nav>
<!-- Content Area -->
<div class="content-area">
<!-- Section 1: Welcome -->
<section id="welcome" class="page-section active">
<div class="welcome-hero">
<div class="welcome-content">
<h1 class="welcome-title">🏠 Welcome to Soroban Mastery</h1>
<p class="welcome-subtitle">Your complete interactive learning platform for mastering the Japanese abacus</p>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">📚</div>
<h3>Complete Guide</h3>
<p>Learn to read numbers and perform arithmetic operations with step-by-step visual tutorials</p>
<button class="feature-btn" data-section="guide">Start Learning →</button>
</div>
<div class="feature-card">
<div class="feature-icon">🎯</div>
<h3>Interactive Challenges</h3>
<p>Test your skills with engaging games including speed reading challenges and pattern matching</p>
<button class="feature-btn" data-section="challenges">Play Games →</button>
</div>
<div class="feature-card">
<div class="feature-icon">📊</div>
<h3>Practice Flashcards</h3>
<p>Master number recognition with beautiful, customizable flashcards and printable PDF sets</p>
<button class="feature-btn" data-section="flashcards">Practice Now →</button>
</div>
<div class="feature-card">
<div class="feature-icon">⚙️</div>
<h3>Customization</h3>
<p>Personalize your learning experience with different color schemes, card layouts, and difficulty levels</p>
<button class="feature-btn" data-section="configuration">Customize →</button>
</div>
</div>
<div class="quick-start">
<h2>Quick Start Guide</h2>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Learn the Basics</h4>
<p>Start with our complete guide to understand how to read numbers on the soroban</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Practice Recognition</h4>
<p>Use interactive flashcards to build speed and accuracy in number recognition</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Challenge Yourself</h4>
<p>Test your skills with timed challenges and competitive games</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Master Arithmetic</h4>
<p>Learn advanced techniques for addition, subtraction, and more complex operations</p>
</div>
</div>
</div>
</div>
<div class="stats-preview">
<h3>What You Will Learn</h3>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-number">50</div>
<div class="stat-label">Practice Numbers</div>
</div>
<div class="stat-item">
<div class="stat-number">4</div>
<div class="stat-label">Game Modes</div>
</div>
<div class="stat-item">
<div class="stat-number">5</div>
<div class="stat-label">Color Schemes</div>
</div>
<div class="stat-item">
<div class="stat-number">∞</div>
<div class="stat-label">Skill Levels</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Section 2: Complete Guide -->
<section id="guide" class="page-section">
<div class="section-header">
<h2 class="section-title">📚 Complete Soroban Mastery Guide</h2>
<p class="section-subtitle">From basic reading to advanced arithmetic - everything you need to master the Japanese abacus</p>
</div>
<!-- Guide Navigation -->
<div class="guide-nav">
<div class="guide-nav-buttons">
<button class="guide-nav-btn active" data-guide-section="reading">📖 Reading Numbers</button>
<button class="guide-nav-btn" data-guide-section="arithmetic">🧮 Arithmetic Operations</button>
</div>
</div>
<!-- Guide Content -->
<div class="guide-content">
<!-- Reading Numbers Section -->
<div id="reading" class="guide-section active">
<!-- 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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#f18f01" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(56.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#f18f01" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#f18f01" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#f18f01" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#f18f01" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(56.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(56.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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> <!-- End Reading Section -->
<!-- Arithmetic Operations Section -->
<div id="arithmetic" class="guide-section">
<div class="section-intro">
<h3>🧮 Soroban Arithmetic Mastery</h3>
<p>Learn traditional Japanese calculation techniques step-by-step with visual examples</p>
</div>
<!-- Addition Section -->
<div class="tutorial-step">
<div class="step-header">
<h3><span class="step-number">+</span>Addition Techniques</h3>
</div>
<div class="step-content">
<div class="arithmetic-intro">
<p>Addition on the soroban follows specific rules for manipulating beads efficiently. Master these fundamental patterns:</p>
</div>
<!-- Simple Addition -->
<div class="operation-section">
<h4>Simple Addition (No Carries)</h4>
<div class="calculation-example">
<div class="calc-step">
<div class="step-description">
<h5>Example: 23 + 14 = 37</h5>
<p>Add ones first, then tens. No carrying required.</p>
</div>
<div class="calc-visual-sequence">
<div class="calc-frame">
<div class="frame-label">Start: 23</div>
<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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="calc-arrow">+4 ones →</div>
<div class="calc-frame">
<div class="frame-label">Add 4 to ones: 27</div>
<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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="calc-arrow">+1 ten →</div>
<div class="calc-frame">
<div class="frame-label">Final: 37</div>
<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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
</div>
</div>
</div>
</div>
<!-- Addition with Carry -->
<div class="operation-section">
<h4>Addition with Carrying</h4>
<div class="calculation-example">
<div class="calc-step">
<div class="step-description">
<h5>Example: 47 + 28 = 75</h5>
<p>When ones exceed 9, carry to tens column.</p>
<ol class="technique-steps">
<li>Start with 47 on the abacus</li>
<li>Add 8 to ones: 7 + 8 = 15 (carry 1)</li>
<li>Place 5 in ones, carry 1 to tens</li>
<li>Add 2 + 1 (carry) = 3 to tens: 4 + 3 = 7</li>
</ol>
</div>
<div class="calc-visual-sequence">
<div class="calc-frame">
<div class="frame-label">Start: 47</div>
<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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="calc-arrow">+8 ones<br>(7+8=15) →</div>
<div class="calc-frame">
<div class="frame-label">Carry: 55</div>
<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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="calc-arrow">+2 tens →</div>
<div class="calc-frame">
<div class="frame-label">Final: 75</div>
<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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Subtraction Section -->
<div class="tutorial-step">
<div class="step-header">
<h3><span class="step-number"></span>Subtraction Techniques</h3>
</div>
<div class="step-content">
<div class="arithmetic-intro">
<p>Subtraction requires understanding complement methods and borrowing. These are traditional soroban techniques.</p>
</div>
<!-- Simple Subtraction -->
<div class="operation-section">
<h4>Simple Subtraction (No Borrowing)</h4>
<div class="calculation-example">
<div class="calc-step">
<div class="step-description">
<h5>Example: 89 - 34 = 55</h5>
<p>Subtract ones first, then tens. No borrowing needed.</p>
</div>
<div class="calc-visual-sequence">
<div class="calc-frame">
<div class="frame-label">Start: 89</div>
<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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="calc-arrow">-4 ones →</div>
<div class="calc-frame">
<div class="frame-label">After -4: 85</div>
<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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="calc-arrow">-3 tens →</div>
<div class="calc-frame">
<div class="frame-label">Final: 55</div>
<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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
</div>
</div>
</div>
</div>
<!-- Complement Method -->
<div class="operation-section">
<h4>Complement Method (Advanced)</h4>
<div class="technique-box">
<h5>Five Complement Rule</h5>
<p>When subtracting from 5, use complement pairs:</p>
<div class="complement-grid">
<div class="complement-pair">5 - 1 = 4 → Remove heaven, add 4 earth</div>
<div class="complement-pair">5 - 2 = 3 → Remove heaven, add 3 earth</div>
<div class="complement-pair">5 - 3 = 2 → Remove heaven, add 2 earth</div>
<div class="complement-pair">5 - 4 = 1 → Remove heaven, add 1 earth</div>
</div>
</div>
<div class="technique-box">
<h5>Ten Complement Rule</h5>
<p>When borrowing from next column:</p>
<div class="complement-grid">
<div class="complement-pair">10 - 1 = 9 → Borrow 1, add 9</div>
<div class="complement-pair">10 - 7 = 3 → Borrow 1, add 3</div>
<div class="complement-pair">10 - 8 = 2 → Borrow 1, add 2</div>
</div>
</div>
</div>
</div>
</div>
<!-- Multiplication Section -->
<div class="tutorial-step">
<div class="step-header">
<h3><span class="step-number">×</span>Multiplication Methods</h3>
</div>
<div class="step-content">
<div class="arithmetic-intro">
<p>Soroban multiplication uses repeated addition and special column techniques for efficiency.</p>
</div>
<!-- Single Digit Multiplication -->
<div class="operation-section">
<h4>Single-Digit Multiplication</h4>
<div class="calculation-example">
<div class="calc-step">
<div class="step-description">
<h5>Example: 23 × 4 = 92</h5>
<p>Multiply each digit by 4, handling carries properly.</p>
<ol class="technique-steps">
<li>Set up 23 on the left, multiply by 4</li>
<li>3 × 4 = 12 → Write 2, carry 1</li>
<li>2 × 4 = 8, plus carry 1 = 9</li>
<li>Result: 92</li>
</ol>
</div>
<div class="mult-layout">
<div class="mult-setup">
<div class="mult-label">Setup: 23 × 4</div>
<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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="mult-result">
<div class="mult-label">Result: 92</div>
<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 65.5 L 3 65.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
</div>
</div>
</div>
</div>
<!-- Traditional Method -->
<div class="operation-section">
<h4>Traditional Multiplication Layout</h4>
<div class="technique-box">
<h5>Column Arrangement</h5>
<p>Traditional soroban multiplication uses specific column arrangements:</p>
<ul>
<li><strong>Multiplicand:</strong> Left side of abacus</li>
<li><strong>Multiplier:</strong> Right side of abacus</li>
<li><strong>Product:</strong> Center columns for result</li>
<li><strong>Working space:</strong> Extra columns for carries</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Division Section -->
<div class="tutorial-step">
<div class="step-header">
<h3><span class="step-number">÷</span>Division Techniques</h3>
</div>
<div class="step-content">
<div class="arithmetic-intro">
<p>Division on soroban uses traditional algorithms with quotient estimation and remainder handling.</p>
</div>
<!-- Simple Division -->
<div class="operation-section">
<h4>Simple Division</h4>
<div class="calculation-example">
<div class="calc-step">
<div class="step-description">
<h5>Example: 84 ÷ 4 = 21</h5>
<p>Divide left to right, estimating quotient digits.</p>
<ol class="technique-steps">
<li>Set dividend 84 on left side</li>
<li>8 ÷ 4 = 2 → Place 2 in quotient</li>
<li>4 ÷ 4 = 1 → Place 1 in quotient</li>
<li>Result: 21</li>
</ol>
</div>
<div class="div-layout">
<div class="div-setup">
<div class="div-label">Dividend: 84</div>
<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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
<div class="div-result">
<div class="div-label">Quotient: 21</div>
<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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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>
</div>
</div>
</div>
</div>
<!-- Division Layout -->
<div class="operation-section">
<h4>Traditional Division Layout</h4>
<div class="technique-box">
<h5>Column Organization</h5>
<ul>
<li><strong>Dividend:</strong> Starting on left columns</li>
<li><strong>Divisor:</strong> Held in memory or right side</li>
<li><strong>Quotient:</strong> Built up in designated columns</li>
<li><strong>Remainder:</strong> Left in dividend columns</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Practice Tips -->
<div class="tutorial-step">
<div class="step-header">
<h3><span class="step-number">💡</span>Mastery Tips</h3>
</div>
<div class="step-content">
<div class="tips-grid">
<div class="tip-card">
<h4>🎯 Accuracy First</h4>
<p>Master accuracy before attempting speed. Slow, correct movements build muscle memory for rapid calculation.</p>
</div>
<div class="tip-card">
<h4>🔄 Practice Complements</h4>
<p>Memorize 5-complements (1-4, 2-3) and 10-complements (1-9, 2-8, 3-7, 4-6) for efficient subtraction.</p>
</div>
<div class="tip-card">
<h4>👁️ Visualization</h4>
<p>Learn to see numbers as bead patterns. This enables mental soroban calculation without the physical device.</p>
</div>
<div class="tip-card">
<h4>📚 Progressive Learning</h4>
<p>Master single digits → two digits → three digits. Build complexity gradually for solid foundation.</p>
</div>
</div>
</div>
</div>
</div> <!-- End Arithmetic Section -->
</div> <!-- End Guide Content -->
</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="config-showcase">
<div class="config-item">
<div class="config-label"><strong>📊 Cards Generated:</strong></div>
<div class="config-value">50 flashcards</div>
</div>
<div class="config-item">
<div class="config-label"><strong>🔢 Number Range:</strong></div>
<div class="config-value">1 - 50</div>
<div class="config-description">Learn numbers from 1 - 50 with authentic soroban bead patterns</div>
</div>
<div class="config-item">
<div class="config-label"><strong>🎨 Color Scheme:</strong></div>
<div class="config-value">Each place value (ones, tens, hundreds) has a different color</div>
<div class="color-palette">
<div class="color-sample heaven" title="Heaven beads (5s)"></div>
<div class="color-sample earth" title="Earth beads (1s)"></div>
<div class="color-sample frame" title="Frame"></div>
</div>
<div class="config-description">Colors help distinguish between heaven beads (worth 5) and earth beads (worth 1)</div>
</div>
<div class="config-item">
<div class="config-label"><strong>⚪ Bead Shape:</strong></div>
<div class="config-value" style="display: flex; align-items: center; gap: 15px;">
Circle
<div class="bead-examples">
<div class="bead-sample" data-shape="Circle" title="Current bead shape"></div>
<div class="shape-label">Circle beads</div>
</div>
</div>
<div class="config-description">Visual style affects readability - choose what works best for you</div>
</div>
</div>
<div class="offline-info">
<h3>📱 Offline Ready</h3>
<p>This is a complete, self-contained web page that works without an internet connection. All interactive games, sound effects, and features are built-in. Save this file locally or bookmark it for offline practice anywhere!</p>
<div class="tech-features">
<div class="feature">✨ Pure HTML/CSS/JavaScript</div>
<div class="feature">🎵 Generated sound effects</div>
<div class="feature">🎮 Interactive games built-in</div>
<div class="feature">📲 Works on any device</div>
</div>
</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">
<!-- Tag Filter Bar -->
<div class="tag-filter-bar">
<div class="filter-title">Filter by Tags:</div>
<div class="tag-filters">
<button class="tag-filter active" data-tag="all">All Games <span class="tag-count">(4)</span></button>
<button class="tag-filter" data-tag="memory">🔍 Memory <span class="tag-count">(2)</span></button>
<button class="tag-filter" data-tag="speed">⚡ Speed <span class="tag-count">(3)</span></button>
<button class="tag-filter" data-tag="logic">🧩 Logic <span class="tag-count">(1)</span></button>
<button class="tag-filter" data-tag="patterns">👀 Patterns <span class="tag-count">(1)</span></button>
<button class="tag-filter" data-tag="arithmetic">🏃‍♂️ Arithmetic <span class="tag-count">(1)</span></button>
</div>
</div>
<div class="challenges-grid">
<button id="open-quiz-modal" class="challenge-card quiz-card" data-tags="memory,speed">
<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-tags">
<span class="tag">🔍 Memory</span>
<span class="tag">⚡ Speed</span>
</div>
</div>
</button>
<button id="open-complement-modal" class="challenge-card complement-card" data-tags="speed,arithmetic">
<div class="challenge-icon">🔥</div>
<div class="challenge-content">
<h4>Speed Complement Race</h4>
<p>Lightning-fast complement recall training</p>
<div class="challenge-tags">
<span class="tag">🏃‍♂️ Arithmetic</span>
<span class="tag">⚡ Speed</span>
</div>
</div>
</button>
<button id="open-sorting-modal" class="challenge-card sorting-card" data-tags="logic,patterns">
<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-tags">
<span class="tag">🧩 Logic</span>
<span class="tag">👀 Patterns</span>
</div>
</div>
</button>
<button id="open-matching-modal" class="challenge-card matching-card" data-tags="memory,speed">
<div class="challenge-icon">🧩</div>
<div class="challenge-content">
<h4>Matching Pairs</h4>
<p>Match abacus patterns with their numerals</p>
<div class="challenge-tags">
<span class="tag">🔍 Memory</span>
<span class="tag">⚡ Speed</span>
</div>
</div>
</button>
</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">&times;</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="2">2</button>
<button type="button" class="count-btn active" data-count="5">5</button>
<button type="button" class="count-btn" data-count="8">8</button>
<button type="button" class="count-btn" data-count="12">12</button>
<button type="button" class="count-btn" data-count="15">15</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">&times;</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">&times;</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">&times;</button>
</div>
</div>
<div class="modal-body">
<p id="matching-description">Match abacus patterns with their corresponding numerals. Find all pairs in minimum moves!</p>
<div class="matching-controls">
<div class="control-group">
<label>Number of Players:</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">
<label>Game Type:</label>
<div class="mode-buttons">
<button type="button" class="game-type-btn active" data-game-type="abacus-numeral">
<div class="mode-icon">🧮</div>
<div class="mode-text">Abacus / Numeral Pairs</div>
</button>
<button type="button" class="game-type-btn" data-game-type="complement-pairs">
<div class="mode-icon">🎯</div>
<div class="mode-text">Friends of 5 & 10</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">&times;</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>
<!-- Speed Complement Race Modal -->
<div id="complement-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Speed Complement Race</h2>
<div class="modal-controls">
<button id="complement-fullscreen-btn" class="fullscreen-btn" title="Toggle Fullscreen">⛶</button>
<button id="close-complement-modal" class="close-btn">&times;</button>
</div>
</div>
<div class="modal-body">
<div class="complement-intro">
<div class="intro-icon">🚀</div>
<h3>Lightning-Fast Mental Math Training!</h3>
<p class="intro-description">Master complement pairs through rapid-fire practice. See a number, type its friend instantly!</p>
<div class="example-demo">
<div class="demo-equation">
<span class="demo-number">3</span>
<span class="demo-plus">+</span>
<span class="demo-answer">?</span>
<span class="demo-equals">=</span>
<span class="demo-target">5</span>
</div>
<div class="demo-instruction">👆 Type <strong>2</strong> as fast as you can!</div>
</div>
</div>
<div class="complement-controls">
<div class="control-group">
<label>Training Mode:</label>
<div class="mode-buttons">
<button type="button" class="mode-btn active" data-mode="friends5">
<div class="mode-icon">✋</div>
<div class="mode-text">Friends of 5</div>
</button>
<button type="button" class="mode-btn" data-mode="friends10">
<div class="mode-icon">🔟</div>
<div class="mode-text">Friends of 10</div>
</button>
<button type="button" class="mode-btn" data-mode="mixed">
<div class="mode-icon">🎯</div>
<div class="mode-text">Mixed Challenge</div>
</button>
</div>
</div>
<div class="control-group">
<label>Time Per Question:</label>
<div class="mode-buttons">
<button type="button" class="timeout-btn" data-timeout="preschool">
<div class="mode-icon">🧸</div>
<div class="mode-text">Preschool (20s)</div>
</button>
<button type="button" class="timeout-btn" data-timeout="kindergarten">
<div class="mode-icon">🌸</div>
<div class="mode-text">Kindergarten (15s)</div>
</button>
<button type="button" class="timeout-btn" data-timeout="relaxed">
<div class="mode-icon">🐢</div>
<div class="mode-text">Relaxed (12s)</div>
</button>
<button type="button" class="timeout-btn" data-timeout="slow">
<div class="mode-icon">🕰️</div>
<div class="mode-text">Slow (8s)</div>
</button>
<button type="button" class="timeout-btn active" data-timeout="normal">
<div class="mode-icon">⚡</div>
<div class="mode-text">Normal (5s)</div>
</button>
<button type="button" class="timeout-btn" data-timeout="fast">
<div class="mode-icon">🚀</div>
<div class="mode-text">Fast (3s)</div>
</button>
<button type="button" class="timeout-btn" data-timeout="expert">
<div class="mode-icon">⚡</div>
<div class="mode-text">Expert (2s)</div>
</button>
</div>
</div>
<div class="control-group">
<label>Choose Your Challenge:</label>
<div class="style-buttons">
<button type="button" class="game-style-btn active" data-style="practice">
<div class="style-icon">🎯</div>
<div class="style-content">
<div class="style-title">Endurance Race</div>
<div class="style-description">Race to 20 correct answers - has finish line!</div>
</div>
</button>
<button type="button" class="game-style-btn" data-style="sprint">
<div class="style-icon">⚡</div>
<div class="style-content">
<div class="style-title">Lightning Sprint</div>
<div class="style-description">60-second time attack! Beat the clock, not the finish line!</div>
</div>
</button>
<button type="button" class="game-style-btn" data-style="survival">
<div class="style-icon">🔥</div>
<div class="style-content">
<div class="style-title">Survival Mode</div>
<div class="style-description">Keep going forever - no finish line, just survive!</div>
</div>
</button>
</div>
</div>
</div>
<!-- Game Area (hidden initially) -->
<div id="complement-game" class="complement-game" style="display: none;">
<!-- Game Header (sticky when playing) -->
<div id="complement-header" class="game-sticky-header" style="display: none;">
<div class="header-stats">
<div class="stat-item">
<span class="stat-label">Score:</span>
<span id="complement-score">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Streak:</span>
<span id="complement-streak">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Time:</span>
<span id="complement-time">0s</span>
</div>
</div>
<div class="header-actions">
<button id="pause-complement" class="header-btn">⏸️ Pause</button>
<button id="end-complement" class="header-btn">🏁 End</button>
</div>
</div>
<!-- Main Game Display -->
<div class="complement-display">
<div class="challenge-prompt">
<div class="equation-visual">
<div class="challenge-number-container">
<div class="challenge-number" id="challenge-number">7</div>
<div class="number-label">Given</div>
</div>
<div class="equation-symbol">+</div>
<div class="answer-container">
<div id="answer-display" class="answer-display">?</div>
<div class="answer-label">Your answer</div>
</div>
<div class="equation-symbol">=</div>
<div class="target-container">
<div class="target-number" id="target-number">10</div>
<div class="target-label">Target</div>
</div>
</div>
</div>
<div class="timer-section">
<div class="timer-label">⏱️ Time Remaining</div>
<div class="timer-bar">
<div class="timer-fill" id="timer-fill"></div>
</div>
<div class="timer-text" id="timer-text">5.0s</div>
</div>
<!-- Race Track Section -->
<div class="race-track-section">
<div class="race-label">🏁 Mental Math Race</div>
<div class="race-track">
<div class="track-background">
<div class="track-line start-line"></div>
<div class="track-line quarter-line"></div>
<div class="track-line half-line"></div>
<div class="track-line three-quarter-line"></div>
<div class="track-line finish-line" id="finish-line"></div>
</div>
<!-- Player Racer -->
<div class="racer player-racer" id="player-racer">
<div class="racer-character">🏃‍♀️</div>
<div class="racer-label">You</div>
<div class="racer-progress" id="player-progress">0</div>
</div>
<!-- AI Opponents -->
<div class="racer ai-racer" id="ai-racer-1">
<div class="speech-bubble" id="speech-bubble-1">
<div class="bubble-content"></div>
<div class="bubble-tail"></div>
</div>
<div class="racer-character">🏃‍♂️</div>
<div class="racer-label">Swift AI</div>
</div>
<div class="racer ai-racer" id="ai-racer-2">
<div class="speech-bubble" id="speech-bubble-2">
<div class="bubble-content"></div>
<div class="bubble-tail"></div>
</div>
<div class="racer-character">🏃</div>
<div class="racer-label">Math Bot</div>
</div>
<!-- Finish Line -->
<div class="finish-zone" id="finish-zone">
<div class="finish-flag">🏁</div>
<div class="finish-text">FINISH</div>
</div>
</div>
<!-- Steam Train Journey (for Sprint Mode) -->
<div class="route-map" style="display: none;" id="steam-journey">
<div class="steam-journey-header">
🚂 STEAM TRAIN JOURNEY 🚂
<div class="route-progress" id="route-progress">Route 1 - Mountain Valley Express</div>
</div>
<div class="time-display" id="time-of-day">Dawn - 5:30 AM</div>
<!-- SVG Route Path -->
<div class="route-path">
<svg viewBox="0 0 800 600" width="800" height="600">
<!-- Dynamic track elements will be generated here -->
</svg>
<!-- Train locomotive -->
<div class="train-locomotive" id="train-position">
<div class="steam-effect"></div>
<div class="steam-effect" style="animation-delay: 0.3s;"></div>
<div class="steam-effect" style="animation-delay: 0.6s;"></div>
</div>
<!-- Coal shoveler -->
<div class="coal-shoveler" id="coal-worker">👷</div>
<!-- City stations -->
<div class="train-station" style="left: 50px; top: 300px;" data-city="Depot">🏭</div>
<div class="train-station" style="left: 200px; top: 200px;" data-city="Hillview">🏔️</div>
<div class="train-station" style="left: 300px; top: 250px;" data-city="Riverside">🌊</div>
<div class="train-station" style="left: 500px; top: 200px;" data-city="Summit">⛰️</div>
<div class="train-station" style="left: 700px; top: 150px;" data-city="Goldfield">⚱️</div>
<div class="train-station" style="left: 750px; top: 300px;" data-city="Capital">🏛️</div>
<!-- Geographic landmarks -->
<div class="landmark mountain" style="left: 150px; top: 180px;">⛰️</div>
<div class="landmark tree" style="left: 250px; top: 270px;">🌲</div>
<div class="landmark bridge" style="left: 350px; top: 280px;">🌉</div>
<div class="landmark mountain" style="left: 550px; top: 120px;">🏔️</div>
<div class="landmark tree" style="left: 680px; top: 170px;">🌳</div>
</div>
<!-- Steam Pressure Instrument Panel -->
<div class="instrument-panel">
<div class="panel-frame">
<div class="panel-background"></div>
<div class="pressure-gauge-container">
<svg class="pressure-gauge" viewBox="0 0 200 120" width="160" height="96">
<!-- Gauge background arc -->
<path class="gauge-background"
d="M 20 100 A 80 80 0 0 1 180 100"
fill="none"
stroke="rgba(255, 255, 255, 0.3)"
stroke-width="12"/>
<!-- Gauge progress arc -->
<path class="gauge-progress"
id="pressure-arc"
d="M 20 100 A 80 80 0 0 1 180 100"
fill="none"
stroke="#ff6b6b"
stroke-width="10"
stroke-dasharray="251.2"
stroke-dashoffset="251.2"/>
<!-- Gauge tick marks -->
<g class="gauge-ticks">
<line x1="100" y1="20" x2="100" y2="30" stroke="rgba(255,255,255,0.6)" stroke-width="2"/>
<line x1="150" y1="35" x2="145" y2="43" stroke="rgba(255,255,255,0.4)" stroke-width="1"/>
<line x1="180" y1="100" x2="170" y2="100" stroke="rgba(255,255,255,0.6)" stroke-width="2"/>
<line x1="150" y1="165" x2="145" y2="157" stroke="rgba(255,255,255,0.4)" stroke-width="1"/>
<line x1="50" y1="35" x2="55" y2="43" stroke="rgba(255,255,255,0.4)" stroke-width="1"/>
<line x1="20" y1="100" x2="30" y2="100" stroke="rgba(255,255,255,0.6)" stroke-width="2"/>
<line x1="50" y1="165" x2="55" y2="157" stroke="rgba(255,255,255,0.4)" stroke-width="1"/>
</g>
<!-- Pressure indicator needle (narrow isosceles triangle) -->
<g class="pressure-needle" id="pressure-needle" transform="rotate(0 100 100)">
<polygon points="100,95 98,100 102,100"
fill="#ffd700"
stroke="#ffed4e"
stroke-width="1"/>
<circle cx="100" cy="100" r="6" fill="rgba(255,255,255,0.9)" stroke="rgba(255,255,255,0.6)" stroke-width="2"/>
</g>
<!-- Gauge labels -->
<text x="30" y="105" fill="rgba(255,255,255,0.7)" font-family="sans-serif" font-size="10" font-weight="600">0</text>
<text x="95" y="25" fill="rgba(255,255,255,0.7)" font-family="sans-serif" font-size="10" font-weight="600">50</text>
<text x="175" y="105" fill="rgba(255,255,255,0.7)" font-family="sans-serif" font-size="10" font-weight="600">100</text>
</svg>
<div class="gauge-title">🚂 Steam Power</div>
<div class="gauge-value" id="pressure-value">0 PSI</div>
</div>
</div>
</div>
<!-- Passenger car -->
<div class="passenger-car" id="passenger-display">
<h4>👥 Passengers</h4>
<div class="passenger-list" id="passenger-list">
<!-- Passengers will be added dynamically -->
</div>
</div>
</div>
<div class="race-stats">
<div class="race-stat">
<span class="stat-label">Correct:</span>
<span id="race-correct">0</span>
</div>
<div class="race-stat">
<span class="stat-label">Goal:</span>
<span id="race-goal">20</span>
</div>
</div>
</div>
</div>
<!-- Feedback Area -->
<div class="complement-feedback-area">
<div class="input-feedback" id="input-feedback"></div>
<div class="adaptive-feedback" id="adaptive-feedback"></div>
</div>
<!-- Visual Complement Display -->
<div class="visual-complement" id="visual-complement" style="display: none;">
<div class="complement-equation">
<span class="number-part" id="number-part">7</span>
<span class="plus">+</span>
<span class="complement-part" id="complement-part">3</span>
<span class="equals">=</span>
<span class="sum-part" id="sum-part">10</span>
</div>
<div class="abacus-visualization" id="abacus-visualization">
<!-- Mini abacus showing the complement will go here -->
</div>
</div>
<!-- Progress & Achievement Display -->
<div class="progress-display">
<div class="progress-bar">
<div class="progress-fill" id="progress-fill"></div>
<span class="progress-text" id="progress-text">0/10 Complete</span>
</div>
<div class="achievement-notice" id="achievement-notice" style="display: none;">
<!-- Achievement unlocks will appear here -->
</div>
</div>
</div>
<!-- Complement Score Modal -->
<div id="complement-score-modal" class="score-modal" style="display: none;">
<div class="score-modal-content">
<div class="score-modal-header">
<h3>🔥 Complement Race Results</h3>
<button class="complement-score-modal-close">&times;</button>
</div>
<div id="complement-score-modal-body" class="score-modal-body">
<!-- Score content will be inserted here -->
</div>
<div class="score-modal-footer">
<button id="complement-score-modal-new-game" class="modal-btn new-game-btn">Race Again</button>
<button id="complement-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="1">
<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(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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">1</span></div>
</div>
<div class="flashcard" data-number="2">
<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(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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">2</span></div>
</div>
<div class="flashcard" data-number="3">
<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(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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">3</span></div>
</div>
<div class="flashcard" data-number="4">
<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(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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">4</span></div>
</div>
<div class="flashcard" data-number="5">
<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(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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">5</span></div>
</div>
<div class="flashcard" data-number="6">
<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(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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">6</span></div>
</div>
<div class="flashcard" data-number="7">
<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(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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">7</span></div>
</div>
<div class="flashcard" data-number="8">
<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(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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">8</span></div>
</div>
<div class="flashcard" data-number="9">
<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(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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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="numeral"><span style="color: #2E86AB;">9</span></div>
</div>
<div class="flashcard" data-number="10">
<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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">0</span></div>
</div>
<div class="flashcard" data-number="11">
<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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">1</span></div>
</div>
<div class="flashcard" data-number="12">
<div class="card-number">#12</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">2</span></div>
</div>
<div class="flashcard" data-number="13">
<div class="card-number">#13</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">3</span></div>
</div>
<div class="flashcard" data-number="14">
<div class="card-number">#14</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">4</span></div>
</div>
<div class="flashcard" data-number="15">
<div class="card-number">#15</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">5</span></div>
</div>
<div class="flashcard" data-number="16">
<div class="card-number">#16</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">6</span></div>
</div>
<div class="flashcard" data-number="17">
<div class="card-number">#17</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">7</span></div>
</div>
<div class="flashcard" data-number="18">
<div class="card-number">#18</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">8</span></div>
</div>
<div class="flashcard" data-number="19">
<div class="card-number">#19</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">1</span><span style="color: #2E86AB;">9</span></div>
</div>
<div class="flashcard" data-number="20">
<div class="card-number">#20</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">0</span></div>
</div>
<div class="flashcard" data-number="21">
<div class="card-number">#21</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">1</span></div>
</div>
<div class="flashcard" data-number="22">
<div class="card-number">#22</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">2</span></div>
</div>
<div class="flashcard" data-number="23">
<div class="card-number">#23</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">3</span></div>
</div>
<div class="flashcard" data-number="24">
<div class="card-number">#24</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">4</span></div>
</div>
<div class="flashcard" data-number="25">
<div class="card-number">#25</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">5</span></div>
</div>
<div class="flashcard" data-number="26">
<div class="card-number">#26</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">6</span></div>
</div>
<div class="flashcard" data-number="27">
<div class="card-number">#27</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">7</span></div>
</div>
<div class="flashcard" data-number="28">
<div class="card-number">#28</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">8</span></div>
</div>
<div class="flashcard" data-number="29">
<div class="card-number">#29</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">2</span><span style="color: #2E86AB;">9</span></div>
</div>
<div class="flashcard" data-number="30">
<div class="card-number">#30</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">0</span></div>
</div>
<div class="flashcard" data-number="31">
<div class="card-number">#31</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">1</span></div>
</div>
<div class="flashcard" data-number="32">
<div class="card-number">#32</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">2</span></div>
</div>
<div class="flashcard" data-number="33">
<div class="card-number">#33</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">3</span></div>
</div>
<div class="flashcard" data-number="34">
<div class="card-number">#34</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">4</span></div>
</div>
<div class="flashcard" data-number="35">
<div class="card-number">#35</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">5</span></div>
</div>
<div class="flashcard" data-number="36">
<div class="card-number">#36</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">6</span></div>
</div>
<div class="flashcard" data-number="37">
<div class="card-number">#37</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">7</span></div>
</div>
<div class="flashcard" data-number="38">
<div class="card-number">#38</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">8</span></div>
</div>
<div class="flashcard" data-number="39">
<div class="card-number">#39</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(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">3</span><span style="color: #2E86AB;">9</span></div>
</div>
<div class="flashcard" data-number="40">
<div class="card-number">#40</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">0</span></div>
</div>
<div class="flashcard" data-number="41">
<div class="card-number">#41</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">1</span></div>
</div>
<div class="flashcard" data-number="42">
<div class="card-number">#42</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">2</span></div>
</div>
<div class="flashcard" data-number="43">
<div class="card-number">#43</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">3</span></div>
</div>
<div class="flashcard" data-number="44">
<div class="card-number">#44</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">4</span></div>
</div>
<div class="flashcard" data-number="45">
<div class="card-number">#45</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">5</span></div>
</div>
<div class="flashcard" data-number="46">
<div class="card-number">#46</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">6</span></div>
</div>
<div class="flashcard" data-number="47">
<div class="card-number">#47</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">7</span></div>
</div>
<div class="flashcard" data-number="48">
<div class="card-number">#48</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">8</span></div>
</div>
<div class="flashcard" data-number="49">
<div class="card-number">#49</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 72.5 L 3 72.5 L 3 0 Z "/>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 33)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 45.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 58)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 70.5)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#2e86ab" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">4</span><span style="color: #2E86AB;">9</span></div>
</div>
<div class="flashcard" data-number="50">
<div class="card-number">#50</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 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(6.5 17)">
<g class="typst-group">
<g>
<g transform="translate(-0 -0)">
<path class="typst-shape" fill="#a23b72" fill-rule="nonzero" stroke="#000000" stroke-width="0.5" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(6.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</g>
</g>
</g>
</g>
<g transform="translate(31.5 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 0 6 C 0 2.689296 2.689296 0 6 0 C 9.310704 0 12 2.689296 12 6 C 12 9.310704 9.310704 12 6 12 C 2.689296 12 0 9.310704 0 6 "/>
</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"><span style="color: #A23B72;">5</span><span style="color: #2E86AB;">0</span></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');
});
document.getElementById('open-complement-modal').addEventListener('click', () => {
this.openModal('complement-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');
});
document.getElementById('close-complement-modal').addEventListener('click', () => {
this.closeModal('complement-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');
});
document.getElementById('complement-fullscreen-btn').addEventListener('click', () => {
this.toggleFullscreen('complement-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 = '';
// Stop active games when their modals are closed
if (modalId === 'complement-modal' && this.complementRace && this.complementRace.isGameActive) {
console.log('🛑 Stopping active complement race due to modal close');
this.complementRace.endRace('Game stopped - modal closed');
} else if (modalId === 'matching-modal' && this.matchingGame && this.matchingGame.gameActive) {
console.log('🛑 Stopping active matching game due to modal close');
this.matchingGame.endGame();
} else if (modalId === 'sorting-modal' && this.sortingGame && this.sortingGame.gameActive) {
console.log('🛑 Stopping active sorting game due to modal close');
this.sortingGame.endGame();
} else if (modalId === 'quiz-modal' && this.quizGame && this.quizGame.gameActive) {
console.log('🛑 Stopping active quiz game due to modal close');
this.quizGame.endGame();
}
}
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.prefixAcceptanceTimeout = null; // Timeout management for prefix conflicts
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');
// Always clear any existing timeout first
if (this.prefixAcceptanceTimeout) {
clearTimeout(this.prefixAcceptanceTimeout);
this.prefixAcceptanceTimeout = null;
}
// 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)) {
// Check if this input is a prefix of any other correct answers
if (!this.isPrefix(value, this.correctAnswers)) {
// Safe to auto-accept immediately - no timeout needed
this.acceptCorrectNumber(number, input, display);
} else {
// Brief delay for potential prefixes - store timeout ID
this.prefixAcceptanceTimeout = setTimeout(() => {
this.prefixAcceptanceTimeout = null; // Clear reference
this.acceptCorrectNumber(number, input, display);
}, 500);
}
} 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);
}
}
isPrefix(input, numbers) {
return numbers.some(n => n.toString().startsWith(input) && n.toString() !== input);
}
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.cleanupQuizState();
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;
// Clean up any pending timeouts
this.cleanupQuizState();
}
cleanupQuizState() {
// Clear any pending prefix acceptance timeout
if (this.prefixAcceptanceTimeout) {
clearTimeout(this.prefixAcceptanceTimeout);
this.prefixAcceptanceTimeout = null;
}
// 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'; // 'single' or 'two-player'
this.gameType = 'abacus-numeral'; // 'abacus-numeral' or 'complement-pairs'
this.turnTimer = 0; // 0 = no timer
this.currentPlayer = 1;
this.player1Score = 0;
this.player2Score = 0;
this.eventsAlreadyBound = false;
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
}));
// Hide/show grid size buttons based on available cards
this.updateAvailableGridSizes();
}
updateAvailableGridSizes() {
const availableCards = this.cards.length;
const gridButtons = document.querySelectorAll('.match-size-btn');
gridButtons.forEach(button => {
const requiredPairs = parseInt(button.dataset.pairs);
if (availableCards < requiredPairs) {
button.style.display = 'none';
} else {
button.style.display = 'block';
}
});
// Add a message if very few options are available
const visibleButtons = Array.from(gridButtons).filter(btn => btn.style.display !== 'none');
if (visibleButtons.length === 0) {
const countButtons = document.querySelector('.count-buttons');
countButtons.innerHTML += '<p style="color: #666; font-style: italic; margin-top: 15px;">Not enough flashcards available for memory matching. Need at least 6 unique cards.</p>';
} else if (visibleButtons.length < gridButtons.length) {
const countButtons = document.querySelector('.count-buttons');
const existingMessage = countButtons.querySelector('.cards-info');
if (!existingMessage) {
countButtons.innerHTML += `<p class="cards-info" style="color: #666; font-size: 0.9em; margin-top: 10px;">${availableCards} flashcards available</p>`;
}
}
}
bindMatchingEvents() {
// Skip if events are already bound to prevent duplicates
if (this.eventsAlreadyBound) {
return;
}
// Use event delegation on the modal container for reliable event handling
const modalBody = document.querySelector('#matching-modal .modal-body');
modalBody.addEventListener('click', (e) => {
// Handle player mode buttons
if (e.target.closest('.mode-btn')) {
const btn = e.target.closest('.mode-btn');
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.gameMode = btn.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';
}
this.updateDescription();
return;
}
// Handle game type buttons
if (e.target.closest('.game-type-btn')) {
const btn = e.target.closest('.game-type-btn');
document.querySelectorAll('.game-type-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.gameType = btn.dataset.gameType;
this.updateDescription();
return;
}
// Handle timer buttons
if (e.target.closest('.timer-btn')) {
const btn = e.target.closest('.timer-btn');
document.querySelectorAll('.timer-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.turnTimer = parseInt(btn.dataset.timer);
return;
}
// Handle start game buttons
if (e.target.closest('.start-game-btn')) {
const btn = e.target.closest('.start-game-btn');
this.selectedPairs = parseInt(btn.dataset.pairs);
this.startMatching();
return;
}
});
this.eventsAlreadyBound = true;
// Action buttons (not in modal, so handle separately)
document.getElementById('new-matching')?.addEventListener('click', () => this.newChallenge());
document.getElementById('end-matching')?.addEventListener('click', () => this.endMatching());
// Score modal event listeners (not in main modal, so handle separately)
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();
}
});
}
updateDescription() {
const description = document.getElementById('matching-description');
if (this.gameType === 'complement-pairs') {
if (this.gameMode === 'two-player') {
description.textContent = 'Match complement pairs! Find numbers that add up to 5 or 10. Two players take turns finding "friends" that work together.';
} else {
description.textContent = 'Match complement pairs! Find numbers that add up to 5 or 10. Each pair contains two numbers that are "friends" - they work together to make 5 or 10.';
}
} else {
if (this.gameMode === 'two-player') {
description.textContent = 'Match abacus patterns with their corresponding numerals. Find all pairs in minimum moves! Two players take turns.';
} else {
description.textContent = 'Match abacus patterns with their corresponding numerals. Find all pairs in minimum moves!';
}
}
}
startMatching() {
// Initialize game cards array
this.gameCards = [];
if (this.gameType === 'complement-pairs') {
// Create complement pairs for friends of 5 and friends of 10
const complementPairs = [
// Friends of 5
[0, 5], [1, 4], [2, 3],
// Friends of 10
[0, 10], [1, 9], [2, 8], [3, 7], [4, 6], [5, 5], [6, 4], [7, 3], [8, 2], [9, 1], [10, 0]
];
// Remove duplicates and limit to requested pairs
const uniquePairs = [];
const seen = new Set();
for (let pair of complementPairs) {
const key = Math.min(pair[0], pair[1]) + '_' + Math.max(pair[0], pair[1]);
if (!seen.has(key)) {
seen.add(key);
uniquePairs.push(pair);
}
}
// Shuffle and select pairs
const shuffledPairs = uniquePairs.sort(() => Math.random() - 0.5);
const selectedPairs = shuffledPairs.slice(0, this.selectedPairs);
selectedPairs.forEach((pair, index) => {
// Determine target sum (5 or 10)
const targetSum = pair[0] + pair[1];
const label = targetSum === 5 ? '✋' : '🔟';
// Add first number card
this.gameCards.push({
id: `comp1_${pair[0]}_${pair[1]}`,
type: 'complement',
number: pair[0],
complement: pair[1],
targetSum: targetSum,
content: `${pair[0]} ${label}`,
matched: false
});
// Add second number card
this.gameCards.push({
id: `comp2_${pair[0]}_${pair[1]}`,
type: 'complement',
number: pair[1],
complement: pair[0],
targetSum: targetSum,
content: `${pair[1]} ${label}`,
matched: false
});
});
} else {
// Original matching logic for abacus/number pairs
// Ensure we have enough cards available
if (this.cards.length < this.selectedPairs) {
alert(`Not enough cards available! Need ${this.selectedPairs} unique cards, but only ${this.cards.length} are available.`);
return;
}
// Select random cards for matching
const shuffledCards = [...this.cards].sort(() => Math.random() - 0.5);
const selectedCards = shuffledCards.slice(0, this.selectedPairs);
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);
// Debug logging
console.log(`Memory Game Debug:`, {
selectedPairs: this.selectedPairs,
availableCards: this.cards.length,
gameCards: this.gameCards.length,
expectedCards: this.selectedPairs * 2
});
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;
let typeClass, typeIcon, contentHtml;
if (card.type === 'complement') {
// Complement cards have different styling
const targetSum = card.targetSum;
typeClass = targetSum === 5 ? 'friends-5-type' : 'friends-10-type';
typeIcon = ''; // No icon needed - color coding is sufficient
contentHtml = `<div class="match-card-number complement-number">${card.content}</div>`;
} else {
// Original abacus/number cards
typeClass = card.type === 'abacus' ? 'abacus-type' : 'number-type';
typeIcon = card.type === 'abacus' ? '🧮' : '🔢';
contentHtml = card.type === 'number' ?
`<div class="match-card-number">${card.content}</div>` :
`<div class="match-card-abacus">${card.content}</div>`;
}
cardElement.innerHTML = `
<div class="match-card-back ${typeClass}">
<div class="card-type-icon">${typeIcon}</div>
</div>
<div class="match-card-content">
${contentHtml}
</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 valid pairs
if (this.flippedCards.length === 1) {
const flippedCard = this.flippedCards[0].card;
if (this.gameType === 'complement-pairs') {
// For complement pairs, prevent clicking the same card
if (flippedCard.id === card.id) {
this.showInvalidMoveHint(cardElement);
return;
}
// Only allow cards with the same targetSum (friends of 5 with friends of 5, etc.)
if (card.targetSum !== flippedCard.targetSum) {
this.showInvalidMoveHint(cardElement);
return;
}
} else {
// Original logic: only allow opposite types
const flippedCardType = flippedCard.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);
}
// 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;
let isMatch = false;
if (this.gameType === 'complement-pairs') {
// Check if the two numbers are complements
isMatch = (first.card.number === second.card.complement &&
second.card.number === first.card.complement &&
first.card.targetSum === second.card.targetSum);
} else {
// Original logic: Check if numbers match and types are different
isMatch = (first.card.number === second.card.number && first.card.type !== second.card.type);
}
if (isMatch) {
// Match found!
first.element.classList.add('matched');
second.element.classList.add('matched');
first.card.matched = true;
second.card.matched = true;
// Trigger particle celebration for the match
if (particleSystem) {
particleSystem.celebrateSuccess(first.element);
// Delayed second celebration for visual effect
setTimeout(() => {
particleSystem.celebrateSuccess(second.element);
}, 200);
}
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) {
// Celebrate game completion with major fireworks
if (particleSystem) {
const gameArea = document.getElementById('matching-grid');
if (gameArea) {
particleSystem.celebrateGameComplete(gameArea);
}
}
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');
}
});
// Ensure the current game type button is properly highlighted
document.querySelectorAll('.game-type-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.gameType === this.gameType) {
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(flippedCard) {
if (this.gameType === 'complement-pairs') {
// For complement pairs, highlight cards with the same targetSum only
const flippedTargetSum = flippedCard.targetSum;
this.gameCards.forEach((card, index) => {
const cardElement = document.querySelector(`[data-index="${index}"]`);
if (!card.matched && !cardElement.classList.contains('flipped')) {
if (card.targetSum === flippedTargetSum) {
cardElement.classList.add('valid-choice');
} else {
cardElement.classList.add('invalid-choice');
}
}
});
} else {
// For abacus/numeral pairs, highlight cards of the opposite type
const flippedCardType = flippedCard.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 = 'welcome';
this.currentGuideSection = 'reading';
this.setupEventListeners();
}
setupEventListeners() {
// Main navigation
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const section = e.target.getAttribute('data-section');
this.showSection(section);
});
});
// Guide navigation
document.querySelectorAll('.guide-nav-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const guideSection = e.target.getAttribute('data-guide-section');
this.showGuideSection(guideSection);
});
});
// Feature buttons in welcome section
document.querySelectorAll('.feature-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' });
}
showGuideSection(guideSectionId) {
// Hide all guide sections
document.querySelectorAll('.guide-section').forEach(section => {
section.classList.remove('active');
});
// Remove active from all guide nav buttons
document.querySelectorAll('.guide-nav-btn').forEach(btn => {
btn.classList.remove('active');
});
// Show selected guide section
const targetSection = document.getElementById(guideSectionId);
if (targetSection) {
targetSection.classList.add('active');
}
// Activate corresponding guide nav button
const targetBtn = document.querySelector(`[data-guide-section="${guideSectionId}"]`);
if (targetBtn) {
targetBtn.classList.add('active');
}
this.currentGuideSection = guideSectionId;
// Smooth scroll to guide content
const guideContent = document.querySelector('.guide-content');
if (guideContent) {
guideContent.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
}
// 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
// Speed Complement Race Challenge
class SpeedComplementRace {
constructor() {
this.mode = 'friends5'; // 'friends5', 'friends10', 'mixed'
this.style = 'practice'; // 'practice', 'sprint', 'survival'
this.timeoutSetting = 'normal'; // 'preschool', 'kindergarten', 'relaxed', 'slow', 'normal', 'fast', 'expert'
this.currentNumber = 0; // Initialize with default value instead of null
this.targetSum = 5;
this.correctAnswer = 0; // Initialize with default value instead of null
this.previousQuestion = null; // Track previous question to avoid repeats
this.score = 0;
this.streak = 0;
this.bestStreak = 0;
this.currentInput = '';
this.isGameActive = false;
this.isPaused = false;
this.autoSubmitTimeout = null;
this.totalQuestions = 0;
this.correctAnswers = 0;
this.gameStartTime = null;
this.questionStartTime = 0;
this.timerInterval = null;
this.gameInterval = null;
this.isScrolling = false;
this.isResetting = false;
// Lap tracking for circular race celebrations
this.playerLap = 0;
this.aiLaps = new Map(); // Track each AI's lap count
this.lapCelebrationCooldown = new Set(); // Prevent multiple celebrations for same lap
// Adaptive Difficulty System
this.difficultyTracker = {
// Track performance for each complement pair
pairPerformance: new Map(), // key: "number_complement_targetSum", value: { attempts, correct, avgTime, difficulty }
baseTimeLimit: 3000, // Base time limit in milliseconds
currentTimeLimit: 3000,
difficultyLevel: 1, // 1-5 scale
consecutiveCorrect: 0,
consecutiveIncorrect: 0,
learningMode: true, // Gives extra time initially
adaptationRate: 0.1, // How quickly to adapt (0.1 = gradual)
};
// Race properties
this.raceGoal = 20;
this.aiRacers = [
{
id: 'ai-racer-1',
position: 2,
speed: 0.25,
name: 'Swift AI',
personality: 'competitive',
icon: '🏃‍♂️',
lastComment: 0,
commentCooldown: 0,
previousPosition: 2
},
{
id: 'ai-racer-2',
position: 2,
speed: 0.15,
name: 'Math Bot',
personality: 'analytical',
icon: '🏃',
lastComment: 0,
commentCooldown: 0,
previousPosition: 2
}
];
this.previousPlayerProgress = 0;
this.aiUpdateInterval = null;
this.bindEvents();
this.updateTargetSum(); // Initialize target sum display
}
bindEvents() {
// Mode selection buttons
document.querySelectorAll('.mode-btn[data-mode]').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.mode = btn.dataset.mode;
this.updateTargetSum();
});
});
// Timeout selection buttons
document.querySelectorAll('.timeout-btn[data-timeout]').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.timeout-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.timeoutSetting = btn.dataset.timeout;
console.log('Timeout setting changed to:', this.timeoutSetting);
});
});
// Style selection buttons - start game directly when clicked
document.querySelectorAll('.game-style-btn[data-style]').forEach(btn => {
btn.addEventListener('click', (e) => {
// Update active state
document.querySelectorAll('.game-style-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Set style and start game immediately
this.style = btn.dataset.style;
this.startRace();
});
});
// Keystroke capture for the entire game area
document.addEventListener('keydown', (e) => this.handleKeydown(e));
// Game control buttons
document.getElementById('pause-complement').addEventListener('click', () => this.pauseRace());
document.getElementById('end-complement').addEventListener('click', () => this.endRace());
// Score modal events
document.querySelector('.complement-score-modal-close').addEventListener('click', () => {
document.getElementById('complement-score-modal').style.display = 'none';
});
document.getElementById('complement-score-modal-close-btn').addEventListener('click', () => {
document.getElementById('complement-score-modal').style.display = 'none';
});
document.getElementById('complement-score-modal-new-game').addEventListener('click', () => {
document.getElementById('complement-score-modal').style.display = 'none';
this.startRace();
});
}
updateTargetSum() {
if (this.mode === 'friends5') {
this.targetSum = 5;
} else if (this.mode === 'friends10') {
this.targetSum = 10;
} else if (this.mode === 'mixed') {
// For mixed mode, we'll set it during nextQuestion, but initialize to 5
this.targetSum = 5;
}
document.getElementById('target-number').textContent = this.targetSum;
}
startRace() {
this.isGameActive = false; // Don't start until countdown finishes
this.isPaused = false;
this.gameStartTime = null; // Will be set after countdown
this.score = 0;
this.streak = 0;
this.totalQuestions = 0;
this.correctAnswers = 0;
this.previousQuestion = null; // Reset previous question for new race
// Hide intro and controls, show game
document.querySelector('.complement-intro').style.display = 'none';
document.querySelector('.complement-controls').style.display = 'none';
document.getElementById('complement-game').style.display = 'block';
document.getElementById('complement-header').style.display = 'flex';
// Hide timer bar for train variant - use pressure system instead
const timerSection = document.querySelector('.timer-section');
if (timerSection) {
if (this.style === 'sprint') {
timerSection.style.display = 'none';
} else {
timerSection.style.display = 'block';
}
}
// Configure visualization based on game style
const raceTrackSection = document.querySelector('.race-track-section');
const raceTrack = document.querySelector('.race-track');
const tunnelContainer = document.querySelector('.tunnel-container');
if (this.style === 'survival') {
// Switch to circular track for survival mode
raceTrackSection.classList.add('circular-track');
raceTrack.classList.add('circular');
raceTrackSection.classList.remove('tunnel-digging');
raceTrack.style.display = 'block';
if (tunnelContainer) tunnelContainer.style.display = 'none';
} else if (this.style === 'sprint') {
// Switch to steam train journey for sprint mode
raceTrackSection.classList.add('steam-journey');
raceTrackSection.classList.remove('circular-track');
raceTrack.classList.remove('circular');
raceTrack.style.display = 'none'; // Hide track completely
const steamJourney = document.getElementById('steam-journey');
if (steamJourney) {
steamJourney.style.display = 'block';
// Initialize steam train journey
this.initializeSteamJourney();
}
} else {
// Use linear track for practice mode
raceTrackSection.classList.remove('circular-track', 'tunnel-digging');
raceTrack.classList.remove('circular');
raceTrack.style.display = 'block';
if (tunnelContainer) tunnelContainer.style.display = 'none';
}
// Initialize race system
this.initializeRace();
// Start countdown before race begins
this.startCountdown();
}
startCountdown() {
const countdownOverlay = document.getElementById('race-countdown');
const countdownNumber = document.getElementById('countdown-number');
const countdownText = document.getElementById('countdown-text');
// Show countdown overlay
countdownOverlay.style.display = 'flex';
let count = 3;
const raceTypes = {
'practice': 'Practice Race',
'sprint': 'Speed Sprint',
'survival': 'Survival Challenge'
};
countdownText.textContent = `${raceTypes[this.style]} Starting...`;
const countdownInterval = setInterval(() => {
if (count > 0) {
countdownNumber.textContent = count;
countdownNumber.className = 'countdown-number';
// Play countdown sound
this.playSound('countdown', 0.4);
count--;
} else {
// Show "GO!"
countdownNumber.textContent = 'GO!';
countdownNumber.className = 'countdown-go';
countdownText.textContent = 'Race in Progress!';
// Play start sound
this.playSound('race_start', 0.6);
// Hide countdown after GO animation
setTimeout(() => {
countdownOverlay.style.display = 'none';
// Actually start the race now
this.isGameActive = true;
this.gameStartTime = Date.now();
this.nextQuestion();
this.startGameTimer();
}, 1000);
clearInterval(countdownInterval);
}
}, 1000);
}
nextQuestion() {
if (!this.isGameActive) return;
// Clear any existing question timer first
if (this.timerInterval) {
clearInterval(this.timerInterval);
this.timerInterval = null;
}
// Update target sum for mixed mode
if (this.mode === 'mixed') {
this.targetSum = Math.random() > 0.5 ? 5 : 10;
document.getElementById('target-number').textContent = this.targetSum;
}
// Generate question - avoid repeating the same question
let newNumber, newTargetSum = this.targetSum;
let attempts = 0;
do {
if (newTargetSum === 5) {
newNumber = Math.floor(Math.random() * 5); // 0-4
} else {
newNumber = Math.floor(Math.random() * 10); // 0-9
}
attempts++;
} while (
this.previousQuestion &&
this.previousQuestion.number === newNumber &&
this.previousQuestion.targetSum === newTargetSum &&
attempts < 10 // Safety limit to prevent infinite loops
);
this.currentNumber = newNumber;
this.correctAnswer = this.targetSum - this.currentNumber;
// Store current question as previous for next iteration
this.previousQuestion = {
number: this.currentNumber,
targetSum: this.targetSum,
answer: this.correctAnswer
};
this.questionStartTime = Date.now();
// Update display and clear any pending input
document.getElementById('challenge-number').textContent = this.currentNumber;
this.clearAutoSubmitTimeout();
this.updateAnswerDisplay('');
document.getElementById('input-feedback').textContent = '';
document.getElementById('visual-complement').style.display = 'none';
// Reset display moving flag for new question
if (document.querySelector('.route-path')) {
this.isDisplayMoving = false;
}
// Handle display switching for train mode (static → floating after delay)
this.handleDisplaySwitching();
// Show persistent learning mode feedback
if (this.difficultyTracker.learningMode && this.totalQuestions % 3 === 0) {
const pairKey = `${this.currentNumber}_${this.correctAnswer}_${this.targetSum}`;
const adaptiveFeedback = this.getAdaptiveFeedbackMessage(pairKey, true, 0);
if (adaptiveFeedback) {
this.showAdaptiveFeedback(adaptiveFeedback.message, adaptiveFeedback.type);
}
}
this.startQuestionTimer();
this.totalQuestions++;
}
startQuestionTimer() {
const timerFill = document.getElementById('timer-fill');
const duration = this.getTimerDuration();
// Update the timer text to show actual duration
const timerText = document.getElementById('timer-text');
if (timerText) {
timerText.textContent = `${(duration / 1000).toFixed(1)}s`;
}
timerFill.style.width = '100%';
timerFill.style.backgroundColor = '#4CAF50';
let startTime = Date.now();
this.timerInterval = setInterval(() => {
if (!this.isGameActive) {
clearInterval(this.timerInterval);
return;
}
const elapsed = Date.now() - startTime;
const remaining = Math.max(0, duration - elapsed);
const percentage = (remaining / duration) * 100;
timerFill.style.width = percentage + '%';
// Update countdown text
if (timerText) {
timerText.textContent = `${(remaining / 1000).toFixed(1)}s`;
}
if (percentage < 30) {
timerFill.style.backgroundColor = '#f44336';
} else if (percentage < 60) {
timerFill.style.backgroundColor = '#ff9800';
}
if (remaining === 0) {
clearInterval(this.timerInterval);
this.timeUp();
}
}, 50);
}
getTimerDuration() {
// Use adaptive difficulty system
let adaptiveTime = this.getAdaptiveTimeLimit();
// Apply game style modifiers to adaptive base
if (this.style === 'sprint') {
adaptiveTime = Math.min(adaptiveTime, adaptiveTime * 0.8); // 20% faster for sprint
} else if (this.style === 'survival') {
// Survival gets progressively faster based on survival multiplier
adaptiveTime = adaptiveTime / (this.survivalMultiplier || 1.0);
}
// Apply streak bonus (gets faster with success)
const streakReduction = Math.min(this.streak * 50, adaptiveTime * 0.3); // Max 30% reduction
const finalTime = Math.max(1000, adaptiveTime - streakReduction); // Never below 1 second
// Show timeout adjustment feedback
if (this.totalQuestions > 5 && !this.difficultyTracker.learningMode) {
const originalTime = this.difficultyTracker.baseTimeLimit;
const timeDiff = Math.abs(finalTime - originalTime) / 1000; // Convert to seconds
if (timeDiff > 0.5) { // Only show if significant change
let timeoutMessage = '';
let timeoutType = '';
if (finalTime < originalTime * 0.8) {
timeoutMessage = `🔥 You're blazing fast! Reduced time to ${(finalTime/1000).toFixed(1)}s!`;
timeoutType = 'adapted';
} else if (finalTime > originalTime * 1.3) {
timeoutMessage = `🤗 Taking it easy - extended to ${(finalTime/1000).toFixed(1)}s`;
timeoutType = 'adapted';
}
if (timeoutMessage && this.totalQuestions % 8 === 0) { // Show occasionally
setTimeout(() => {
this.showAdaptiveFeedback(timeoutMessage, timeoutType);
}, 200);
}
}
}
// Debug logging for adaptive difficulty
if (this.totalQuestions % 3 === 0) {
console.log('🎯 Adaptive Timer:', {
question: this.totalQuestions,
baseTime: this.difficultyTracker.baseTimeLimit + 'ms',
adaptiveTime: adaptiveTime + 'ms',
finalTime: finalTime + 'ms',
streak: this.streak,
difficultyLevel: this.difficultyTracker.difficultyLevel,
learningMode: this.difficultyTracker.learningMode,
timeoutSetting: this.timeoutSetting
});
}
return finalTime;
}
handleKeydown(e) {
// Only capture keystrokes when game is active and not paused
if (!this.isGameActive || this.isPaused) return;
// Handle number keys (0-9)
if (e.key >= '0' && e.key <= '9') {
e.preventDefault();
const digit = e.key;
// Build multi-digit number (max 2 digits for this game)
let newInput = this.currentInput + digit;
const number = parseInt(newInput);
// Only allow valid answers (0 to 10 for friends of 10, 0 to 5 for friends of 5, max 2 digits)
const maxValid = Math.max(this.targetSum, 10); // Allow up to 10 for any mode
if (number <= maxValid && newInput.length <= 2) {
this.currentInput = newInput;
this.updateAnswerDisplay(newInput);
// Clear any existing auto-submit timeout
this.clearAutoSubmitTimeout();
// Auto-submit after delay
this.autoSubmitTimeout = setTimeout(() => {
if (this.currentInput === newInput && this.isGameActive) {
this.submitAnswer();
}
}, newInput === '1' && this.targetSum >= 10 ? 400 : 200);
}
}
// Handle backspace/delete to remove last digit
else if (e.key === 'Backspace' || e.key === 'Delete') {
e.preventDefault();
if (this.currentInput.length > 0) {
this.currentInput = this.currentInput.slice(0, -1);
this.updateAnswerDisplay(this.currentInput || '');
this.clearAutoSubmitTimeout();
} else {
this.updateAnswerDisplay('');
}
}
// Handle enter to submit current answer immediately
else if (e.key === 'Enter' && this.currentInput) {
e.preventDefault();
this.clearAutoSubmitTimeout();
this.submitAnswer();
}
}
clearAutoSubmitTimeout() {
if (this.autoSubmitTimeout) {
clearTimeout(this.autoSubmitTimeout);
this.autoSubmitTimeout = null;
}
}
updateAnswerDisplay(value) {
const display = document.getElementById('answer-display');
if (value === '') {
display.textContent = '?';
display.classList.remove('typing');
display.classList.add('empty');
this.currentInput = '';
} else {
display.textContent = value;
display.classList.add('typing');
display.classList.remove('empty');
}
}
submitAnswer() {
if (!this.isGameActive) return;
clearInterval(this.timerInterval);
const isCorrect = parseInt(this.currentInput) === this.correctAnswer;
if (isCorrect) {
this.handleCorrectAnswer();
} else {
this.handleIncorrectAnswer();
}
setTimeout(() => this.nextQuestion(), 50);
}
handleCorrectAnswer() {
this.correctAnswers++;
this.streak++;
this.bestStreak = Math.max(this.bestStreak, this.streak);
let points = 10 + Math.min(this.streak, 10);
this.score += points;
// Show different feedback based on game style
if (this.style === 'sprint') {
const config = this.getMomentumConfig();
this.showFeedback(`🚂 Coal shoveled! +${config.momentumGain}% momentum!`, 'correct');
} else {
this.showFeedback('🎉 Correct!', 'correct');
}
this.showVisualComplement();
// Update race progress
this.updatePlayerRace();
// Trigger particle effects
const numberDisplay = document.getElementById('current-number');
if (particleSystem && numberDisplay) {
if (this.streak > 0 && this.streak % 5 === 0) {
// Special celebration for every 5th streak
particleSystem.celebrateStreak(numberDisplay, this.streak);
} else {
particleSystem.celebrateSuccess(numberDisplay);
}
}
// Track performance for adaptive difficulty FIRST
const responseTime = Date.now() - this.questionStartTime;
this.trackPerformance(true);
// Show adaptive feedback after tracking
const pairKey = `${this.currentNumber}_${this.correctAnswer}_${this.targetSum}`;
const adaptiveFeedback = this.getAdaptiveFeedbackMessage(pairKey, true, responseTime);
if (adaptiveFeedback) {
setTimeout(() => {
this.showAdaptiveFeedback(adaptiveFeedback.message, adaptiveFeedback.type);
}, 600); // Show after the main feedback
}
// Trigger AI commentary
setTimeout(() => {
this.triggerAICommentary();
}, 800);
this.updateStats();
// Play appropriate sound based on performance
if (this.streak > 0 && this.streak % 5 === 0) {
// Epic streak sound for every 5th correct answer
this.playSound('streak');
} else if (responseTime < 800) {
// Whoosh sound for very fast responses (under 800ms)
this.playSound('whoosh');
} else if (responseTime < 1200 && this.streak >= 3) {
// Combo sound for rapid answers while on a streak
this.playSound('combo');
} else {
// Regular correct sound
this.playSound('correct');
}
}
handleIncorrectAnswer() {
this.streak = 0;
// Capture values before they might get overwritten, with fallbacks
const displayNumber = this.currentNumber !== null ? this.currentNumber : '?';
const displayAnswer = this.correctAnswer !== null ? this.correctAnswer : '?';
const displaySum = this.targetSum !== null ? this.targetSum : '?';
this.showFeedback(`❌ ${displayNumber} + ${displayAnswer} = ${displaySum}`, 'incorrect');
this.showVisualComplement();
// Trigger trip animation for player racer
const playerRacer = document.getElementById('player-racer');
if (playerRacer) {
playerRacer.classList.add('tripped');
setTimeout(() => {
playerRacer.classList.remove('tripped');
}, 1200); // Match animation duration
}
// Track performance for adaptive difficulty FIRST
const responseTime = Date.now() - this.questionStartTime;
this.trackPerformance(false);
// Handle steam train momentum loss in sprint mode
if (this.style === 'sprint') {
this.loseMomentum();
}
// Show adaptive feedback after tracking
const pairKey = `${displayNumber}_${displayAnswer}_${displaySum}`;
const adaptiveFeedback = this.getAdaptiveFeedbackMessage(pairKey, false, responseTime);
if (adaptiveFeedback) {
setTimeout(() => {
this.showAdaptiveFeedback(adaptiveFeedback.message, adaptiveFeedback.type);
}, 800); // Show after the main feedback
}
// Trigger AI commentary
setTimeout(() => {
this.triggerAICommentary();
}, 1000);
this.updateStats();
this.playSound('incorrect');
}
timeUp() {
this.streak = 0;
// Capture values before they might get overwritten, with fallbacks
const displayNumber = this.currentNumber !== null ? this.currentNumber : '?';
const displayAnswer = this.correctAnswer !== null ? this.correctAnswer : '?';
const displaySum = this.targetSum !== null ? this.targetSum : '?';
this.showFeedback(`⏰ Time's up! ${displayNumber} + ${displayAnswer} = ${displaySum}`, 'timeout');
this.showVisualComplement();
// Apply timeout penalty: move backwards in race
if (this.correctAnswers > 0) {
this.correctAnswers = Math.max(0, this.correctAnswers - 0.5); // Small setback
}
// Trigger backwards movement animation for player racer
const playerRacer = document.getElementById('player-racer');
if (playerRacer) {
playerRacer.classList.add('moving-backwards');
setTimeout(() => {
playerRacer.classList.remove('moving-backwards');
// Update position after animation (no momentum gain for timeout penalty)
this.updatePlayerRace(false);
}, 800); // Match animation duration
}
// Track performance for adaptive difficulty (timeout counts as incorrect)
this.trackPerformance(false);
// Handle steam train momentum loss in sprint mode
if (this.style === 'sprint') {
this.loseMomentum();
}
this.updateStats();
this.playSound('timeout');
setTimeout(() => this.nextQuestion(), 50);
}
showFeedback(message, type) {
const feedback = document.getElementById('input-feedback');
feedback.textContent = message;
feedback.className = `input-feedback ${type}`;
}
showAdaptiveFeedback(message, type) {
const adaptiveFeedback = document.getElementById('adaptive-feedback');
adaptiveFeedback.textContent = message;
adaptiveFeedback.className = `adaptive-feedback ${type}`;
// Auto-hide after some time unless it's persistent info
if (type !== 'learning') {
setTimeout(() => {
adaptiveFeedback.style.opacity = '0';
setTimeout(() => {
adaptiveFeedback.textContent = '';
adaptiveFeedback.style.opacity = '0.8';
}, 400);
}, 3000);
}
}
getAdaptiveFeedbackMessage(pairKey, isCorrect, responseTime) {
const pairData = this.difficultyTracker.pairPerformance.get(pairKey);
const [num1, num2, sum] = pairKey.split('_').map(Number);
// Learning mode messages
if (this.difficultyTracker.learningMode) {
const encouragements = [
"🧠 I'm learning your style! Keep going!",
"📊 Building your skill profile...",
"🎯 Every answer helps me understand you better!",
"🚀 Analyzing your complement superpowers!"
];
return {
message: encouragements[Math.floor(Math.random() * encouragements.length)],
type: 'learning'
};
}
// After learning - provide specific feedback
if (pairData && pairData.attempts >= 3) {
const accuracy = pairData.correct / pairData.attempts;
const avgTime = pairData.avgTime;
// Struggling pairs (< 60% accuracy)
if (accuracy < 0.6) {
const strugglingMessages = [
`💪 ${num1}+${num2} needs practice - I'm giving you extra time!`,
`🎯 Working on ${num1}+${num2} - you've got this!`,
`⏰ Taking it slower with ${num1}+${num2} - no rush!`,
`🧩 ${num1}+${num2} is getting special attention from me!`
];
return {
message: strugglingMessages[Math.floor(Math.random() * strugglingMessages.length)],
type: 'struggling'
};
}
// Mastered pairs (> 85% accuracy and fast)
if (accuracy > 0.85 && avgTime < 2000) {
const masteredMessages = [
`⚡ ${num1}+${num2} = MASTERED! Lightning mode activated!`,
`🔥 You've conquered ${num1}+${num2} - speeding it up!`,
`🏆 ${num1}+${num2} expert detected! Challenge mode ON!`,
`⭐ ${num1}+${num2} is your superpower! Going faster!`
];
return {
message: masteredMessages[Math.floor(Math.random() * masteredMessages.length)],
type: 'mastered'
};
}
}
// Show adaptation when difficulty changes
if (this.difficultyTracker.consecutiveCorrect >= 3) {
return {
message: "🚀 You're on fire! Increasing the challenge!",
type: 'adapted'
};
} else if (this.difficultyTracker.consecutiveIncorrect >= 2) {
return {
message: "🤗 Let's slow down a bit - I'm here to help!",
type: 'adapted'
};
}
return null;
}
// AI Commentary System - Speech Bubbles
showAICommentary(racer, message, type = 'default') {
// Get the speech bubble for this specific racer
const racerId = racer.id.split('-').pop(); // Extract number from 'ai-racer-1'
const speechBubble = document.getElementById(`speech-bubble-${racerId}`);
const bubbleContent = speechBubble?.querySelector('.bubble-content');
if (!speechBubble || !bubbleContent) return;
// Hide any currently visible bubbles first
document.querySelectorAll('.speech-bubble.visible').forEach(bubble => {
if (bubble !== speechBubble) {
bubble.classList.remove('visible');
}
});
// Update bubble content
bubbleContent.textContent = message;
// Show this bubble with animation
speechBubble.classList.add('visible');
// Set cooldown for this racer
racer.lastComment = Date.now();
racer.commentCooldown = Math.random() * 4000 + 2000; // 2-6 seconds
// Auto-hide after some time
setTimeout(() => {
speechBubble.classList.remove('visible');
}, 3500); // Show for 3.5 seconds
}
getAICommentary(racer, context) {
const now = Date.now();
// Check cooldown
if (now - racer.lastComment < racer.commentCooldown) {
return null;
}
const playerProgress = (this.correctAnswers / this.raceGoal) * 100;
const aiProgress = (racer.position / this.raceGoal) * 100;
let messages = [];
if (racer.personality === 'competitive') {
// Swift AI - Competitive and cocky
if (context === 'ahead') {
messages = [
"💨 Eat my dust!",
"🔥 Too slow for me!",
"⚡ You can't catch me!",
"🚀 I'm built for speed!",
"🏃‍♂️ This is way too easy!"
];
} else if (context === 'behind') {
messages = [
"😤 Not over yet!",
"💪 I'm just getting started!",
"🔥 Watch me catch up to you!",
"⚡ I'm coming for you!",
"🏃‍♂️ This is my comeback!"
];
} else if (context === 'adaptive_struggle') {
messages = [
"😏 You struggling much?",
"🤖 Math is easy for me!",
"⚡ You need to think faster!",
"🔥 Need me to slow down?"
];
} else if (context === 'adaptive_mastery') {
messages = [
"😮 You're actually impressive!",
"🤔 You're getting faster...",
"😤 Time for me to step it up!",
"⚡ Not bad for a human!"
];
} else if (context === 'player_passed') {
messages = [
"😠 No way you just passed me!",
"🔥 This isn't over!",
"💨 I'm just getting warmed up!",
"😤 Your lucky streak won't last!",
"⚡ I'll be back in front of you soon!"
];
} else if (context === 'ai_passed') {
messages = [
"💨 See ya later, slowpoke!",
"😎 Thanks for the warm-up!",
"🔥 This is how it's done!",
"⚡ I'll see you at the finish line!",
"💪 Try to keep up with me!"
];
} else if (context === 'lapped') {
messages = [
"😡 You just lapped me?! No way!",
"🤬 This is embarrassing for me!",
"😤 I'm not going down without a fight!",
"💢 How did you get so far ahead?!",
"🔥 Time to show you my real speed!",
"😠 You won't stay ahead for long!"
];
} else if (context === 'desperate_catchup') {
messages = [
"🚨 TURBO MODE ACTIVATED! I'm coming for you!",
"💥 You forced me to unleash my true power!",
"🔥 NO MORE MR. NICE AI! Time to go all out!",
"⚡ I'm switching to MAXIMUM OVERDRIVE!",
"😤 You made me angry - now you'll see what I can do!",
"🚀 AFTERBURNERS ENGAGED! This isn't over!"
];
}
} else if (racer.personality === 'analytical') {
// Math Bot - Analytical and encouraging but competitive
if (context === 'ahead') {
messages = [
"📊 My performance is optimal!",
"🤖 My logic beats your speed!",
"📈 I have 87% win probability!",
"⚙️ I'm perfectly calibrated!",
"🔬 Science prevails over you!"
];
} else if (context === 'behind') {
messages = [
"🤔 Recalculating my strategy...",
"📊 You're exceeding my projections!",
"⚙️ Adjusting my parameters!",
"🔬 I'm analyzing your technique!",
"📈 You're a statistical anomaly!"
];
} else if (context === 'adaptive_struggle') {
messages = [
"📊 I detect inefficiencies in you!",
"🔬 You should focus on patterns!",
"⚙️ Use that extra time wisely!",
"📈 You have room for improvement!"
];
} else if (context === 'adaptive_mastery') {
messages = [
"🤖 Your optimization is excellent!",
"📊 Your metrics are impressive!",
"⚙️ I'm updating my models because of you!",
"🔬 You have near-AI efficiency!"
];
} else if (context === 'player_passed') {
messages = [
"🤖 Your strategy is fascinating!",
"📊 You're an unexpected variable!",
"⚙️ I'm adjusting my algorithms...",
"🔬 Your execution is impressive!",
"📈 I'm recalculating the odds!"
];
} else if (context === 'ai_passed') {
messages = [
"🤖 My efficiency is optimized!",
"📊 Just as I calculated!",
"⚙️ All my systems nominal!",
"🔬 My logic prevails over you!",
"📈 I'm at 96% confidence level!"
];
} else if (context === 'lapped') {
messages = [
"🤖 Error: You have exceeded my projections!",
"📊 This outcome has 0.3% probability!",
"⚙️ I need to recalibrate my systems!",
"🔬 Your performance is... statistically improbable!",
"📈 My confidence level just dropped to 12%!",
"🤔 I must analyze your methodology!"
];
} else if (context === 'desperate_catchup') {
messages = [
"🤖 EMERGENCY PROTOCOL ACTIVATED! Initiating maximum speed!",
"🚨 CRITICAL GAP DETECTED! Engaging catchup algorithms!",
"⚙️ OVERCLOCKING MY PROCESSORS! Prepare for rapid acceleration!",
"📊 PROBABILITY OF FAILURE: UNACCEPTABLE! Switching to turbo mode!",
"🔬 HYPOTHESIS: You're about to see my true potential!",
"📈 CONFIDENCE LEVEL: RISING! My comeback protocol is online!"
];
}
}
if (messages.length > 0) {
return messages[Math.floor(Math.random() * messages.length)];
}
return null;
}
triggerAICommentary() {
// Don't comment too frequently
if (this.totalQuestions % 4 !== 0) return;
const playerProgress = this.correctAnswers;
this.aiRacers.forEach(racer => {
const aiProgress = Math.floor(racer.position);
let context = '';
// Determine context based on positions and adaptive system
if (aiProgress > playerProgress + 2) {
context = 'ahead';
} else if (playerProgress > aiProgress + 5) {
context = 'desperate_catchup'; // AI is way behind and desperate
} else if (playerProgress > aiProgress + 2) {
context = 'behind';
} else if (this.difficultyTracker.consecutiveIncorrect >= 2) {
context = 'adaptive_struggle';
} else if (this.streak >= 5) {
context = 'adaptive_mastery';
}
if (context) {
const message = this.getAICommentary(racer, context);
if (message) {
this.showAICommentary(racer, message, context);
// Play special turbo sound when AI goes desperate
if (context === 'desperate_catchup') {
this.playSound('ai_turbo', 0.12);
}
return; // Only one racer comments at a time
}
}
});
}
showVisualComplement() {
document.getElementById('number-part').textContent = this.currentNumber;
document.getElementById('complement-part').textContent = this.correctAnswer;
document.getElementById('sum-part').textContent = this.targetSum;
document.getElementById('visual-complement').style.display = 'block';
}
updateStats() {
document.getElementById('complement-score').textContent = this.score;
document.getElementById('complement-streak').textContent = this.streak;
// Update progress display
const progress = Math.min(this.totalQuestions / 20, 1) * 100;
document.getElementById('progress-fill').style.width = progress + '%';
document.getElementById('progress-text').textContent = `${this.correctAnswers}/${this.totalQuestions} Correct`;
// Start the continuous game timer if not already running
if (!this.gameInterval) {
this.startGameTimer();
}
}
startGameTimer() {
// Clear any existing game timer first
if (this.gameInterval) {
clearInterval(this.gameInterval);
}
// Start continuous timer that updates every second
this.gameInterval = setInterval(() => {
if (!this.isGameActive || this.isPaused) return;
const elapsed = Math.floor((Date.now() - this.gameStartTime) / 1000);
if (this.style === 'sprint') {
// For steam train journey - show elapsed time instead of countdown
document.getElementById('complement-time').textContent = elapsed + 's';
// Check for journey completion conditions instead of time limit
this.checkTrainJourneyCompletion();
} else {
// Count-up timer for practice and survival
document.getElementById('complement-time').textContent = elapsed + 's';
// Survival mode: increase difficulty over time
if (this.style === 'survival' && elapsed > 0 && elapsed % 10 === 0) {
this.increaseSurvivalDifficulty(elapsed);
}
}
}, 1000);
}
increaseSurvivalDifficulty(elapsed) {
// Increase difficulty every 10 seconds in survival mode
const level = Math.floor(elapsed / 10);
this.survivalMultiplier = 1.0 + (level * 0.15); // 15% faster each level
// Speed up AI racers too
const newSpeedMultiplier = this.speedMultiplier * this.survivalMultiplier;
this.aiRacers[0].speed = 0.25 * newSpeedMultiplier;
this.aiRacers[1].speed = 0.15 * newSpeedMultiplier;
// Show survival escalation message
const messages = [
'🔥 Pressure rising! Questions getting faster!',
'⚡ Heat turned up! Can you handle it?',
'🌪️ Intensity increasing! Stay focused!',
'🚀 Speed boost activated! Don\'t crack!',
'💥 Maximum pressure! You\'re in the zone!'
];
const message = level < messages.length ? messages[level - 1] : messages[messages.length - 1];
this.showFeedback(message, 'survival');
console.log(`Survival difficulty increased to level ${level}, multiplier: ${this.survivalMultiplier.toFixed(2)}`);
}
pauseRace() {
this.isGameActive = false;
this.isPaused = true;
// Clear all timers and timeouts
if (this.timerInterval) {
clearInterval(this.timerInterval);
this.timerInterval = null;
}
if (this.gameInterval) {
clearInterval(this.gameInterval);
this.gameInterval = null;
}
this.clearAutoSubmitTimeout();
this.showFeedback('⏸️ Paused - Click End to finish', 'pause');
}
endRace(customMessage = null) {
this.isGameActive = false;
// Stop all sounds immediately
this.stopAllSounds();
// Clear all timers and timeouts
if (this.timerInterval) {
clearInterval(this.timerInterval);
this.timerInterval = null;
}
if (this.gameInterval) {
clearInterval(this.gameInterval);
this.gameInterval = null;
}
if (this.momentumDecayInterval) {
clearInterval(this.momentumDecayInterval);
this.momentumDecayInterval = null;
}
if (this.trainMovementInterval) {
clearInterval(this.trainMovementInterval);
this.trainMovementInterval = null;
}
this.clearAutoSubmitTimeout();
const accuracy = this.totalQuestions > 0 ? (this.correctAnswers / this.totalQuestions) * 100 : 0;
const totalTime = (Date.now() - this.gameStartTime) / 1000;
// Stop race and show intro content when game ends
this.stopRace();
document.querySelector('.complement-intro').style.display = 'block';
this.showResults({
score: this.score,
correct: this.correctAnswers,
total: this.totalQuestions,
accuracy: accuracy,
bestStreak: this.bestStreak,
totalTime: totalTime,
mode: this.mode,
raceWinner: this.raceWinner || null,
winningAI: this.winningAI || null
});
this.resetGame();
}
showResults(results) {
const modal = document.getElementById('complement-score-modal');
const body = document.getElementById('complement-score-modal-body');
// Calculate race performance score (combination of speed and accuracy)
const raceResult = this.calculateRaceResult(results);
const modeNames = { 'friends5': 'Friends of 5', 'friends10': 'Friends of 10', 'mixed': 'Mixed Challenge' };
body.innerHTML = `
<div class="race-results-summary">
<div class="race-podium">
<div class="medal-display">
<div class="medal-icon">${raceResult.medal}</div>
<div class="medal-rank">${raceResult.rank}</div>
<div class="medal-title">${raceResult.title}</div>
</div>
<div class="race-performance">
<div class="perf-metric">
<span class="metric-label">Speed Rating</span>
<span class="metric-value">${raceResult.speedRating}/100</span>
</div>
<div class="perf-metric">
<span class="metric-label">Accuracy</span>
<span class="metric-value">${results.accuracy.toFixed(1)}%</span>
</div>
<div class="perf-metric">
<span class="metric-label">Race Score</span>
<span class="metric-value">${raceResult.raceScore}/100</span>
</div>
</div>
</div>
<div class="race-statistics">
<div class="stat-row">
<span class="stat-label">🏁 Questions Completed:</span>
<span class="stat-value">${results.correct}/${results.total}</span>
</div>
<div class="stat-row">
<span class="stat-label">🔥 Best Streak:</span>
<span class="stat-value">${results.bestStreak}</span>
</div>
<div class="stat-row">
<span class="stat-label">⏱️ Total Time:</span>
<span class="stat-value">${results.totalTime.toFixed(1)}s</span>
</div>
<div class="stat-row">
<span class="stat-label">📈 Points Earned:</span>
<span class="stat-value">${results.score}</span>
</div>
<div class="stat-row">
<span class="stat-label">🎯 Challenge:</span>
<span class="stat-value">${modeNames[results.mode]}</span>
</div>
</div>
<div class="race-feedback">
<p>${raceResult.feedback}</p>
</div>
</div>
`;
modal.style.display = 'block';
}
calculateRaceResult(results) {
// Calculate speed rating (questions per minute)
const questionsPerMinute = (results.correct / results.totalTime) * 60;
const maxQPM = 30; // Maximum expected questions per minute for normalization
const speedRating = Math.min(100, Math.round((questionsPerMinute / maxQPM) * 100));
// Calculate overall race score (weighted combination) for performance tracking
const raceScore = Math.round((results.accuracy * 0.7) + (speedRating * 0.3));
// Determine medal and rank based on ACTUAL RACE RESULTS first
let medal, rank, title, feedback;
if (results.raceWinner === 'player') {
// Player won the race - always gets gold!
medal = '🥇';
rank = '1st Place';
title = 'GOLD MEDAL - RACE WINNER!';
if (results.accuracy >= 90) {
feedback = 'PERFECT VICTORY! You won with outstanding accuracy. You\'re a true complement champion!';
} else if (results.accuracy >= 75) {
feedback = 'RACE WINNER! Great job beating the AI racers. Your speed gave you the edge!';
} else {
feedback = 'VICTORY BY SPEED! You won the race even with some mistakes. Speed can overcome accuracy!';
}
} else if (results.raceWinner === 'ai') {
// AI won the race, player gets silver for effort
medal = '🥈';
rank = '2nd Place';
title = 'SILVER MEDAL';
if (results.accuracy >= 85) {
feedback = 'So close! Your accuracy was excellent, but the AI was just a bit faster this time.';
} else if (results.accuracy >= 70) {
feedback = 'Good race! Work on speed to beat those AI racers next time.';
} else {
feedback = 'The AI won this round, but you\'re improving! Focus on accuracy first, then speed.';
}
} else {
// For sprint/survival modes - determine position based on who's ahead at end
const playerPosition = this.correctAnswers;
const aiPositions = this.aiRacers.map(ai => ai.position).sort((a, b) => b - a);
let playerRank = 1;
aiPositions.forEach(aiPos => {
if (aiPos > playerPosition) {
playerRank++;
}
});
if (playerRank === 1) {
medal = '🥇';
rank = '1st Place';
title = 'GOLD MEDAL - RACE LEADER!';
feedback = `RACE LEADER! You were ahead of both AI racers with ${playerPosition} correct answers. Outstanding performance!`;
} else if (playerRank === 2) {
const leadingAI = Math.max(...aiPositions);
medal = '🥈';
rank = '2nd Place';
title = 'SILVER MEDAL';
feedback = `STRONG SECOND! You finished just behind the leading AI racer (you: ${playerPosition}, AI: ${leadingAI}). Great effort!`;
} else if (playerRank === 3) {
medal = '🥉';
rank = '3rd Place';
title = 'BRONZE MEDAL';
feedback = `BRONZE FINISH! Both AI racers got ahead, but you completed the race with ${playerPosition} correct answers. Keep practicing!`;
} else {
medal = '🏅';
rank = 'Participant';
title = 'COMPLETION MEDAL';
feedback = 'Thanks for playing! Focus on accuracy first, then work on building speed.';
}
}
return {
medal,
rank,
title,
speedRating,
raceScore,
feedback
};
}
resetGame() {
// Show intro and controls, hide game
document.querySelector('.complement-intro').style.display = 'block';
document.querySelector('.complement-controls').style.display = 'block';
document.getElementById('complement-game').style.display = 'none';
document.getElementById('complement-header').style.display = 'none';
document.getElementById('visual-complement').style.display = 'none';
}
// Race Management Methods
initializeRace() {
// Set race goal and mechanics based on style
if (this.style === 'practice') {
this.raceGoal = 20; // Race to 20 correct answers
this.timeLimit = null; // No time limit
this.speedMultiplier = 0.7; // Moderate AI speed
} else if (this.style === 'sprint') {
this.raceGoal = 999; // No finish line - count as many as possible
this.timeLimit = 60; // 60-second time limit
this.speedMultiplier = 0.9; // Aggressive AI speed
} else if (this.style === 'survival') {
this.raceGoal = 999; // No finish line - survive as long as possible
this.timeLimit = null; // No time limit
this.speedMultiplier = 0.5; // Start slower, will increase over time
this.survivalMultiplier = 1.0; // Difficulty multiplier for survival
}
// Set appropriate goal display and labels for each mode
const raceGoalElement = document.getElementById('race-goal');
if (this.style === 'practice') {
raceGoalElement.textContent = this.raceGoal; // Show "20"
raceGoalElement.setAttribute('title', 'Race to this many correct answers');
} else if (this.style === 'sprint') {
raceGoalElement.textContent = `${this.timeLimit}s`; // Show "60s"
raceGoalElement.setAttribute('title', 'Time-based challenge! Get as many as possible before time runs out');
raceGoalElement.style.color = '#ffd700'; // Gold color for time display
raceGoalElement.style.fontWeight = 'bold';
} else if (this.style === 'survival') {
raceGoalElement.textContent = 'Lap 1'; // Show current lap
raceGoalElement.setAttribute('title', 'Survive as long as possible!');
}
document.getElementById('race-correct').textContent = '0';
// Adjust AI speeds based on style
this.aiRacers[0].speed = 0.25 * this.speedMultiplier; // Swift AI
this.aiRacers[1].speed = 0.15 * this.speedMultiplier; // Math Bot
console.log('Race initialized:', this.style, 'Goal:', this.raceGoal, 'Time limit:', this.timeLimit, 'AI speeds:', this.aiRacers.map(ai => ai.speed));
// Show/hide finish line based on race mode and update track appearance
const finishLine = document.getElementById('finish-line');
const finishZone = document.getElementById('finish-zone');
const raceTrackSection = document.querySelector('.race-track-section');
if (this.style === 'practice') {
// Show finish line for practice mode (endurance test)
if (finishLine) finishLine.style.display = 'block';
if (finishZone) finishZone.style.display = 'block';
if (raceTrackSection) raceTrackSection.classList.remove('infinite-mode');
} else if (this.style === 'sprint') {
// Hide finish line for sprint mode - it's time-based, not distance-based
if (finishLine) finishLine.style.display = 'none';
if (finishZone) finishZone.style.display = 'none';
if (raceTrackSection) raceTrackSection.classList.add('infinite-mode');
} else {
// Hide finish line for survival mode
if (finishLine) finishLine.style.display = 'none';
if (finishZone) finishZone.style.display = 'none';
if (raceTrackSection) raceTrackSection.classList.add('infinite-mode');
}
// Reset all racers to starting position
const playerRacer = document.getElementById('player-racer');
const aiRacer1 = document.getElementById('ai-racer-1');
const aiRacer2 = document.getElementById('ai-racer-2');
if (this.style === 'survival') {
// Position racers on circular track (all at starting position)
if (playerRacer) this.updateCircularPosition(playerRacer, 0);
if (aiRacer1) this.updateCircularPosition(aiRacer1, 0);
if (aiRacer2) this.updateCircularPosition(aiRacer2, 0);
} else {
// Position racers on linear track
if (playerRacer) playerRacer.style.left = '2%';
if (aiRacer1) aiRacer1.style.left = '2%';
if (aiRacer2) aiRacer2.style.left = '2%';
}
// Reset AI racers
this.aiRacers.forEach(ai => {
ai.position = 2;
});
// Reset lap tracking for circular mode
if (this.style === 'survival') {
this.playerLap = 0;
this.aiLaps.clear();
this.lapCelebrationCooldown.clear();
}
// Generate dynamic track for train variant
if (this.style === 'sprint') {
this.generateDynamicTrack();
}
// Start AI movement
this.startAIRacers();
}
generateDynamicTrack() {
// Generate a dynamic curved railroad track with interesting topology
const svg = document.querySelector('.route-path svg');
if (!svg) return;
// Clear existing track elements
svg.innerHTML = '';
// Generate track waypoints with some randomness for variety
const waypoints = this.generateTrackWaypoints();
// Generate smooth curved path through waypoints
const pathData = this.generateSmoothPath(waypoints);
// Create main reference path for train positioning
const referencePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
referencePath.setAttribute('class', 'train-route');
referencePath.setAttribute('d', pathData);
referencePath.setAttribute('fill', 'none');
referencePath.setAttribute('stroke', 'transparent');
referencePath.setAttribute('stroke-width', '2');
// Create railroad bed/ballast
const ballast = document.createElementNS('http://www.w3.org/2000/svg', 'path');
ballast.setAttribute('class', 'railroad-ballast');
ballast.setAttribute('d', pathData);
ballast.setAttribute('fill', 'none');
ballast.setAttribute('stroke', '#8B7355');
ballast.setAttribute('stroke-width', '24');
ballast.setAttribute('stroke-linecap', 'round');
// Create containers for ties and rails
const tiesGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
tiesGroup.setAttribute('class', 'railroad-ties');
const railsGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
railsGroup.setAttribute('class', 'railroad-rails');
// Add elements to SVG in correct order
svg.appendChild(ballast);
svg.appendChild(tiesGroup);
svg.appendChild(railsGroup);
svg.appendChild(referencePath);
// Generate ties and rails properly
this.generateRailroadTiesAndRails();
console.log('🛤️ Generated dynamic curved railroad track with proper rail construction');
}
generateTrackWaypoints() {
// Create interesting waypoints for a scenic railroad journey
const waypoints = [
{ x: 50, y: 300 }, // Start at depot
{ x: 150, y: 220 }, // Climb into hills
{ x: 280, y: 180 }, // Mountain pass
{ x: 420, y: 240 }, // Descent to valley
{ x: 550, y: 160 }, // Bridge over canyon
{ x: 680, y: 200 }, // Rolling hills
{ x: 750, y: 280 } // Arrive at destination
];
// Add some controlled randomness for variety (but keep it playable)
return waypoints.map((point, index) => {
if (index === 0 || index === waypoints.length - 1) {
return point; // Keep start/end points fixed
}
return {
x: point.x + (Math.random() - 0.5) * 30,
y: point.y + (Math.random() - 0.5) * 40
};
});
}
generateSmoothPath(waypoints) {
// Generate smooth cubic bezier curves through waypoints
if (waypoints.length < 2) return '';
let pathData = `M ${waypoints[0].x} ${waypoints[0].y}`;
for (let i = 1; i < waypoints.length; i++) {
const current = waypoints[i];
const previous = waypoints[i - 1];
// Calculate control points for smooth curves
const dx = current.x - previous.x;
const dy = current.y - previous.y;
const cp1x = previous.x + dx * 0.3;
const cp1y = previous.y + dy * 0.2;
const cp2x = current.x - dx * 0.3;
const cp2y = current.y - dy * 0.2;
pathData += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${current.x} ${current.y}`;
}
return pathData;
}
offsetPath(pathData, offset) {
// Simplified path offset - in a full implementation would use proper path offsetting
// For now, just offset the waypoints and regenerate
const points = this.extractPointsFromPath(pathData);
const offsetPoints = points.map(point => ({
x: point.x + (Math.random() - 0.5) * 2, // Slight randomness
y: point.y + offset
}));
return this.generateSmoothPath(offsetPoints);
}
extractPointsFromPath(pathData) {
// Extract key points from path data (simplified)
const matches = pathData.match(/(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)/g);
if (!matches) return [];
return matches.map(match => {
const [x, y] = match.split(/\s+/).map(Number);
return { x, y };
});
}
generateRailroadTiesAndRails() {
// Generate railroad properly: ties first, then rails connecting the tie ends
const tiesContainer = document.querySelector('.railroad-ties');
const railsContainer = document.querySelector('.railroad-rails');
const routePath = document.querySelector('.train-route');
if (!tiesContainer || !railsContainer || !routePath) return;
// Clear existing elements
tiesContainer.innerHTML = '';
railsContainer.innerHTML = '';
const pathLength = routePath.getTotalLength();
const tieSpacing = 12; // Distance between ties
const tieCount = Math.floor(pathLength / tieSpacing);
const gaugeWidth = 15; // Standard gauge (tie extends 15px each side)
const leftRailPoints = [];
const rightRailPoints = [];
// Generate ties and collect rail points
for (let i = 0; i < tieCount; i++) {
const distance = i * tieSpacing;
const point = routePath.getPointAtLength(distance);
// Calculate perpendicular angle for tie orientation
const nextDistance = Math.min(distance + 2, pathLength);
const nextPoint = routePath.getPointAtLength(nextDistance);
const angle = Math.atan2(nextPoint.y - point.y, nextPoint.x - point.x);
const perpAngle = angle + Math.PI / 2;
// Calculate tie end points
const leftX = point.x + Math.cos(perpAngle) * gaugeWidth;
const leftY = point.y + Math.sin(perpAngle) * gaugeWidth;
const rightX = point.x - Math.cos(perpAngle) * gaugeWidth;
const rightY = point.y - Math.sin(perpAngle) * gaugeWidth;
// Create railroad tie
const tie = document.createElementNS('http://www.w3.org/2000/svg', 'line');
tie.setAttribute('x1', leftX);
tie.setAttribute('y1', leftY);
tie.setAttribute('x2', rightX);
tie.setAttribute('y2', rightY);
tie.setAttribute('stroke', '#654321');
tie.setAttribute('stroke-width', '3');
tie.setAttribute('stroke-linecap', 'round');
tie.setAttribute('opacity', '0.8');
tiesContainer.appendChild(tie);
// Collect points for rails
leftRailPoints.push(`${leftX},${leftY}`);
rightRailPoints.push(`${rightX},${rightY}`);
}
// Create left rail by connecting left ends of ties
if (leftRailPoints.length > 1) {
const leftRail = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
leftRail.setAttribute('points', leftRailPoints.join(' '));
leftRail.setAttribute('fill', 'none');
leftRail.setAttribute('stroke', '#C0C0C0');
leftRail.setAttribute('stroke-width', '3');
leftRail.setAttribute('stroke-linecap', 'round');
railsContainer.appendChild(leftRail);
}
// Create right rail by connecting right ends of ties
if (rightRailPoints.length > 1) {
const rightRail = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
rightRail.setAttribute('points', rightRailPoints.join(' '));
rightRail.setAttribute('fill', 'none');
rightRail.setAttribute('stroke', '#C0C0C0');
rightRail.setAttribute('stroke-width', '3');
rightRail.setAttribute('stroke-linecap', 'round');
railsContainer.appendChild(rightRail);
}
console.log(`🛤️ Generated ${tieCount} ties and connected rails properly`);
}
updatePlayerRace(isCorrectAnswer = true) {
if (this.style === 'sprint') {
// Update steam train journey instead of race track
if (isCorrectAnswer) {
this.shovelsCoal(); // Only shovel coal on correct answers
}
this.updateTrainPosition();
return;
}
let visualProgress;
if (this.style === 'practice') {
// Traditional race to finish line
const progress = (this.correctAnswers / this.raceGoal) * 100;
visualProgress = Math.min(progress, 100);
} else {
// Sprint and survival: visual progress based on score relative to a reasonable target
if (this.style === 'survival') {
// For survival mode, let players continue moving forward with cycling
const visualTarget = 50; // Larger target for survival mode
const progress = (this.correctAnswers / visualTarget) * 100;
visualProgress = Math.min(progress, 95); // Allow almost to the end
} else {
// Sprint mode: visual progress based on score relative to a reasonable target
const visualTarget = 30;
const progress = (this.correctAnswers / visualTarget) * 100;
visualProgress = Math.min(progress, 95); // Cap at 95% so no one "wins" visually
}
}
const playerRacer = document.getElementById('player-racer');
const progressDisplay = document.getElementById('player-progress');
if (playerRacer) {
// Always move players based on their progress
playerRacer.style.left = visualProgress + '%';
// Add celebration animation for good progress
if (this.correctAnswers % 5 === 0 && this.correctAnswers > 0) {
playerRacer.classList.add('celebrating');
setTimeout(() => playerRacer.classList.remove('celebrating'), 600);
// Play milestone sound effect
this.playSound('milestone', 0.18);
}
// In survival mode, use circular positioning
if (this.style === 'survival') {
const lapInfo = this.updateCircularPosition(playerRacer, this.correctAnswers);
// Check if player completed a new lap
if (lapInfo.lap > this.playerLap) {
this.playerLap = lapInfo.lap;
this.checkForLappingCelebration();
// Update lap display in goal area
document.getElementById('race-goal').textContent = `Lap ${this.playerLap + 1}`; // Show next lap
}
}
}
if (progressDisplay) {
progressDisplay.textContent = this.correctAnswers;
}
document.getElementById('race-correct').textContent = this.correctAnswers;
// Check for passing events (player passing or getting passed by AI)
this.checkForPassingEvents();
// Update previous position for next comparison
this.previousPlayerProgress = this.correctAnswers;
// Check if player wins - only for practice mode with finish line
if (this.style === 'practice' && this.correctAnswers >= this.raceGoal && this.isGameActive) {
console.log(`🏁 PLAYER WINS! Correct: ${this.correctAnswers}, Goal: ${this.raceGoal}, Visual: ${visualProgress}%`);
this.handleRaceWin('player');
} else if (this.style === 'practice') {
console.log(`🏃 Player progress: ${this.correctAnswers}/${this.raceGoal} (Visual: ${visualProgress.toFixed(1)}%)`);
}
}
resetInfiniteTrack() {
// Prevent multiple resets at once
if (this.isResetting) return;
this.isResetting = true;
console.log('🔄 Resetting infinite track cycle...');
const raceTrackContainer = document.querySelector('.race-track-container');
const allRacers = document.querySelectorAll('.racer');
if (!raceTrackContainer) return;
// Add smooth scrolling animation
raceTrackContainer.classList.add('infinite-runner');
// After a brief scroll animation, reset all positions
setTimeout(() => {
// Reset all racer positions back by 40% to continue the journey
const resetAmount = 40;
allRacers.forEach((racer, index) => {
const currentLeft = parseFloat(racer.style.left || '0');
const newLeft = Math.max(10, currentLeft - resetAmount); // Don't go below 10%
racer.style.left = newLeft + '%';
console.log(`🏃 Racer ${index} reset: ${currentLeft}% → ${newLeft}%`);
});
// Also adjust AI tracking positions
this.aiRacers.forEach(ai => {
const resetInUnits = (resetAmount / 100) * 50; // Convert % to position units
ai.position = Math.max(2, ai.position - resetInUnits);
});
// Remove animation and reset flag
raceTrackContainer.classList.remove('infinite-runner');
this.isResetting = false;
console.log('✅ Infinite track reset complete - journey continues!');
}, 1500); // Short animation duration
}
checkForPassingEvents() {
const currentPlayerProgress = this.correctAnswers;
const previousPlayerProgress = this.previousPlayerProgress;
this.aiRacers.forEach(ai => {
const currentAIProgress = ai.position;
const previousAIProgress = ai.previousPosition;
// Add tolerance for floating point comparison and ensure meaningful position changes
const tolerance = 0.1;
const playerProgressed = currentPlayerProgress > previousPlayerProgress;
const aiProgressed = currentAIProgress > previousAIProgress + tolerance;
// Check if player just passed AI (player progressed and now ahead)
if (playerProgressed && previousPlayerProgress <= previousAIProgress && currentPlayerProgress > currentAIProgress) {
// Player passed AI - AI should react with "player_passed" context
const message = this.getAICommentary(ai, 'player_passed');
if (message) {
setTimeout(() => {
this.showAICommentary(ai, message, 'player_passed');
}, Math.random() * 500 + 300); // Slight delay with randomness
}
}
// Check if AI just passed player (AI progressed and now ahead)
if (aiProgressed && previousAIProgress <= previousPlayerProgress && currentAIProgress > currentPlayerProgress) {
// AI passed player - AI should react with "ai_passed" context
const message = this.getAICommentary(ai, 'ai_passed');
if (message) {
setTimeout(() => {
this.showAICommentary(ai, message, 'ai_passed');
}, Math.random() * 500 + 300); // Slight delay with randomness
}
}
});
}
updateCircularPosition(racer, progress) {
// Get track size dynamically to support responsive design
const track = document.querySelector('.race-track.circular');
const trackSize = track ? track.offsetWidth : 400;
const trackRadius = (trackSize / 2) - 20; // Track radius minus racer size
// Each point of progress moves the player around the track
// Complete one full circle every 50 progress points
const progressPerLap = 50;
const currentLap = Math.floor(progress / progressPerLap);
const totalRotation = (progress / progressPerLap) * 360;
const angleInRadians = (totalRotation * Math.PI) / 180;
// Calculate position on circle (centered at track center)
const x = trackRadius * Math.cos(angleInRadians - Math.PI/2); // Start at top
const y = trackRadius * Math.sin(angleInRadians - Math.PI/2);
// Position racer relative to track center
const trackCenter = trackSize / 2;
racer.style.left = (trackCenter + x) + 'px';
racer.style.top = (trackCenter + y) + 'px';
// Add slight rotation to racer to face direction of travel
racer.style.transform = `rotate(${totalRotation}deg)`;
// Set counter-rotation for speech bubbles to keep them upright
racer.style.setProperty('--counter-rotation', `${-totalRotation}deg`);
// Set rotation variable for circular bounce animation
racer.style.setProperty('--racer-rotation', `${totalRotation}deg`);
// Return lap information for celebration detection
return {
lap: currentLap,
progress: progress,
position: { x: 200 + x, y: 200 + y }
};
}
checkForLappingCelebration() {
// Check if player has lapped any AI opponents
this.aiRacers.forEach(ai => {
const aiLap = this.aiLaps.get(ai.id) || 0;
const lapDifference = this.playerLap - aiLap;
// If player is a full lap ahead and we haven't celebrated this yet
if (lapDifference >= 1) {
const celebrationKey = `${ai.id}_lap_${this.playerLap}`;
if (!this.lapCelebrationCooldown.has(celebrationKey)) {
this.lapCelebrationCooldown.add(celebrationKey);
// Trigger celebration effects
this.celebrateLapping(ai.name);
// Show AI commentary about being lapped
const message = this.getAICommentary(ai, 'lapped');
if (message) {
setTimeout(() => {
this.showAICommentary(ai, message, 'lapped');
}, 800); // Delay to let celebration play
}
console.log(`🎉 Player lapped ${ai.name}! Player lap: ${this.playerLap}, AI lap: ${aiLap}`);
}
}
});
}
celebrateLapping(aiName) {
// Visual celebration for lapping an opponent
const playerRacer = document.getElementById('player-racer');
if (playerRacer) {
// Add special lapping celebration class
playerRacer.classList.add('lapping-celebration');
setTimeout(() => playerRacer.classList.remove('lapping-celebration'), 1500);
// Trigger particle effects
if (window.particleSystem) {
window.particleSystem.celebrateMajor(playerRacer);
}
// Show celebration message
this.showCelebrationMessage(`🏁 LAPPED ${aiName.toUpperCase()}! 🏁`);
// Play celebration sound (if available)
this.playSound('lap_celebration', 0.6);
}
}
showCelebrationMessage(message) {
// Create floating celebration message
const celebration = document.createElement('div');
celebration.className = 'lap-celebration-message';
celebration.textContent = message;
celebration.style.cssText = `
position: fixed;
top: 20%;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(135deg, #ffd700, #ffed4e);
color: #333;
padding: 20px 40px;
border-radius: 15px;
font-size: 1.5rem;
font-weight: 700;
text-align: center;
box-shadow: 0 10px 30px rgba(255, 215, 0, 0.5);
z-index: 10000;
animation: lapCelebrationBounce 1.5s ease-out forwards;
pointer-events: none;
`;
document.body.appendChild(celebration);
// Remove after animation
setTimeout(() => {
if (celebration.parentNode) {
celebration.parentNode.removeChild(celebration);
}
}, 1500);
}
// ===== TUNNEL DIGGING SYSTEM FOR SPRINT MODE =====
updateTunnelDepth(digger, correctAnswers) {
// Handle different digger ID formats
let tunnelId, foxId, depthId;
if (digger.startsWith('ai-tunnel-')) {
// AI tunnel format: ai-tunnel-1 -> ai-tunnel-1, ai-fox-1, ai-depth-1
const aiNum = digger.split('-')[2];
tunnelId = `ai-tunnel-${aiNum}`;
foxId = `ai-fox-${aiNum}`;
depthId = `ai-depth-${aiNum}`;
} else {
// Player format: player -> player-tunnel, player-fox, player-depth
tunnelId = `${digger}-tunnel`;
foxId = `${digger}-fox`;
depthId = `${digger}-depth`;
}
const tunnelElement = document.getElementById(tunnelId);
const foxElement = document.getElementById(foxId);
const depthElement = document.getElementById(depthId);
console.log(`🔍 Updating tunnel for ${digger}: tunnel=${tunnelId}, fox=${foxId}, depth=${depthId}, answers=${correctAnswers}`);
console.log(`Elements found: tunnel=${!!tunnelElement}, fox=${!!foxElement}, depth=${!!depthElement}`);
if (!tunnelElement || !depthElement) return;
// Create depth perspective - tunnel shaft stays same size but shows depth visually
const visualTunnelHeight = 180; // Fixed tunnel shaft height
const actualDepth = correctAnswers; // Real depth for comparison
// Calculate depth levels for visual effects
const depthLevel = Math.floor(actualDepth / 5); // Every 5 answers = deeper level
const isDeep = actualDepth > 10;
const isVeryDeep = actualDepth > 20;
// Update tunnel with fixed height but depth effects
tunnelElement.style.height = visualTunnelHeight + 'px';
// Apply depth effects and classes to show how deep we've gone
tunnelElement.classList.remove('depth-shallow', 'depth-deep', 'depth-very-deep');
if (isVeryDeep) {
// Very deep: dark tunnel with glowing fox
tunnelElement.classList.add('depth-very-deep');
tunnelElement.style.background = 'linear-gradient(to bottom, #2c3e50 0%, #1a1a1a 70%, #000000 100%)';
tunnelElement.style.boxShadow = 'inset 0 0 30px rgba(0,0,0,0.8), 0 0 20px rgba(255,215,0,0.3)';
if (foxElement) foxElement.style.filter = 'drop-shadow(0 0 8px #ffd700)';
} else if (isDeep) {
// Deep: darker tunnel
tunnelElement.classList.add('depth-deep');
tunnelElement.style.background = 'linear-gradient(to bottom, #34495e 0%, #2c3e50 100%)';
tunnelElement.style.boxShadow = 'inset 0 0 25px rgba(0,0,0,0.6)';
if (foxElement) foxElement.style.filter = 'drop-shadow(0 0 4px #ffa500)';
} else {
// Shallow: normal tunnel
tunnelElement.classList.add('depth-shallow');
tunnelElement.style.background = '#4a4a4a';
tunnelElement.style.boxShadow = 'inset 0 0 20px rgba(0,0,0,0.5)';
if (foxElement) foxElement.style.filter = 'none';
}
// Show depth counter with visual scaling
depthElement.textContent = correctAnswers;
if (isVeryDeep) {
depthElement.style.background = 'rgba(255, 215, 0, 0.95)';
depthElement.style.color = '#000';
depthElement.style.transform = 'translateX(-50%) scale(1.1)';
depthElement.style.boxShadow = '0 0 10px rgba(255, 215, 0, 0.5)';
} else if (isDeep) {
depthElement.style.background = 'rgba(255, 152, 0, 0.9)';
depthElement.style.transform = 'translateX(-50%) scale(1.05)';
}
// Add realistic digging animation to fox
if (foxElement) {
// Stop any existing animations
foxElement.classList.remove('digging', 'idle', 'foxDigDeep');
// Add appropriate digging animation based on depth
if (isVeryDeep) {
foxElement.classList.add('foxDigDeep');
setTimeout(() => {
foxElement.classList.remove('foxDigDeep');
foxElement.classList.add('idle');
}, 800);
} else {
foxElement.classList.add('digging');
setTimeout(() => {
foxElement.classList.remove('digging');
foxElement.classList.add('idle');
}, 600);
}
// Create dirt particles effect (more particles for deeper tunnels)
const particleCount = Math.min(5 + depthLevel, 15);
this.createDirtParticles(tunnelElement, particleCount);
// Add screen shake for very deep digging
if (isVeryDeep) {
this.addScreenShake();
}
// Higher treasure chance for deeper tunnels!
const treasureChance = Math.min(0.15 + (depthLevel * 0.05), 0.4); // Up to 40% for very deep
if (correctAnswers > 0 && Math.random() < treasureChance) {
this.foxFindsMemory(digger, correctAnswers, isVeryDeep);
}
}
console.log(`🦊 ${digger} tunnel depth: ${correctAnswers} answers (Level ${depthLevel}, ${isVeryDeep ? 'VERY DEEP' : isDeep ? 'DEEP' : 'SHALLOW'})`)
}
stopAllSounds() {
// Aggressive sound stopping - multiple approaches to ensure silence
try {
console.log('🔇 Stopping all sounds...');
// Method 1: Close all tracked audio contexts
if (window.audioContexts && window.audioContexts.length > 0) {
console.log(`🔇 Closing ${window.audioContexts.length} tracked audio contexts`);
window.audioContexts.forEach((context, index) => {
try {
if (context && context.state !== 'closed') {
console.log(`🔇 Closing audio context ${index + 1}`);
context.close();
}
} catch (closeError) {
console.log(`🔇 Error closing context ${index + 1}:`, closeError);
}
});
window.audioContexts = [];
}
// Method 2: Try to find and stop any remaining AudioContext instances
// This is a more aggressive approach for "zombie" sounds
try {
// Force garbage collection of audio contexts if possible
if (window.gc) window.gc();
} catch (gcError) {
// GC not available, ignore
}
// Method 3: Clear any remaining audio-related intervals/timeouts
for (let i = 1; i < 1000; i++) {
clearTimeout(i);
clearInterval(i);
}
console.log('🔇 All sounds stopped (aggressive cleanup complete)');
} catch (e) {
console.log('🔇 Sound cleanup error:', e);
console.log('🔇 Sound cleanup attempted with errors');
}
}
addScreenShake() {
// Subtle screen shake for deep digging
const container = document.querySelector('.race-track-section.tunnel-digging');
if (container) {
container.style.animation = 'screenShake 0.4s ease-in-out';
setTimeout(() => {
container.style.animation = '';
}, 400);
}
}
initializeSteamJourney() {
// Initialize train journey systems
this.momentum = 0;
this.trainPosition = 0; // Position along the route (0-100%)
this.passengers = [];
this.lastCorrectAnswerTime = Date.now(); // Track when we last shoveled coal
this.stations = [
{ name: 'Depot', position: 0, emoji: '🏭' },
{ name: 'Hillview', position: 20, emoji: '🏔️' },
{ name: 'Riverside', position: 40, emoji: '🌊' },
{ name: 'Summit', position: 60, emoji: '⛰️' },
{ name: 'Goldfield', position: 80, emoji: '⚱️' },
{ name: 'Capital', position: 100, emoji: '🏛️' }
];
this.gameStartTime = Date.now();
this.totalScore = 0;
// Start the continuous game timer for Lightning Sprint
this.startGameTimer();
// Initialize day/night cycle
this.updateTimeOfDay();
// Start momentum decay (friction) - calibrated for different skill levels
this.momentumDecayInterval = setInterval(() => {
if (this.momentum > 0) {
const decayConfig = this.getMomentumConfig();
const timeSinceLastCoal = (Date.now() - this.lastCorrectAnswerTime) / 1000; // seconds
// Base decay rate increases with higher speeds, but adjusted for skill level
let decayRate = this.momentum > 75 ? decayConfig.highSpeedDecay :
this.momentum > 50 ? decayConfig.mediumSpeedDecay :
decayConfig.baseDecay;
// Accelerate decay if no correct answers for a while (but be gentler for kids)
if (timeSinceLastCoal > decayConfig.starvationThreshold) {
const extraDecay = Math.min(decayConfig.maxExtraDecay, timeSinceLastCoal / decayConfig.starvationRate);
decayRate += extraDecay;
}
this.momentum = Math.max(0, this.momentum - decayRate);
this.updateMomentumDisplay();
if (timeSinceLastCoal > decayConfig.warningThreshold && this.momentum > 0) {
console.log(`🚂 [${this.timeoutSetting}] Train needs coal! ${Math.round(timeSinceLastCoal)}s without fuel, decay rate: ${decayRate.toFixed(1)}%/s`);
}
}
}, 1000);
// Start continuous train movement based on momentum
this.trainMovementInterval = setInterval(() => {
this.updateTrainPosition();
}, 200); // Update train position 5 times per second for smooth movement
// Generate initial passengers
this.generatePassengers();
}
updateTimeOfDay() {
const elapsed = Date.now() - this.gameStartTime;
const gameProgress = Math.min(elapsed / (60 * 1000), 1); // 60 seconds = full day
let timeString, gradient;
if (gameProgress < 0.17) {
timeString = 'Dawn - 5:30 AM';
gradient = 'var(--dawn-gradient)';
} else if (gameProgress < 0.33) {
timeString = 'Morning - 8:00 AM';
gradient = 'var(--morning-gradient)';
} else if (gameProgress < 0.67) {
timeString = 'Midday - 12:00 PM';
gradient = 'var(--midday-gradient)';
} else if (gameProgress < 0.83) {
timeString = 'Afternoon - 4:00 PM';
gradient = 'var(--afternoon-gradient)';
} else if (gameProgress < 0.92) {
timeString = 'Dusk - 7:00 PM';
gradient = 'var(--dusk-gradient)';
} else {
timeString = 'Night - 10:00 PM';
gradient = 'var(--night-gradient)';
}
const timeDisplay = document.getElementById('time-of-day');
const steamJourney = document.querySelector('.race-track-section.steam-journey');
if (timeDisplay) timeDisplay.textContent = timeString;
if (steamJourney) steamJourney.style.setProperty('--sky-gradient', gradient);
}
updateMomentumDisplay() {
const momentumBar = document.getElementById('momentum-bar');
const momentumLabel = document.getElementById('momentum-label');
if (momentumBar) {
momentumBar.style.width = this.momentum + '%';
}
if (momentumLabel) {
let status = 'Crawling';
if (this.momentum > 70) status = 'Full Steam!';
else if (this.momentum > 40) status = 'Good Speed';
else if (this.momentum > 15) status = 'Building Steam';
momentumLabel.textContent = `Momentum: ${Math.round(this.momentum)}% - ${status}`;
}
// Update pressure gauge
this.updatePressureGauge();
}
updatePressureGauge() {
const pressureArc = document.getElementById('pressure-arc');
const pressureNeedle = document.getElementById('pressure-needle');
const pressureValue = document.getElementById('pressure-value');
if (pressureArc && pressureNeedle && pressureValue) {
// Convert momentum (0-100) to gauge values
const pressure = Math.round(this.momentum);
const psi = Math.round(pressure * 1.5); // Scale to 0-150 PSI range
// Update arc progress (circumference is ~251.2 pixels)
const circumference = 251.2;
const offset = circumference - (pressure / 100) * circumference;
pressureArc.style.strokeDashoffset = offset;
// Update needle rotation (-90 to +90 degrees for half-circle)
const rotation = -90 + (pressure / 100) * 180;
pressureNeedle.style.transform = `rotate(${rotation}deg)`;
// Update pressure value display
pressureValue.textContent = `${psi} PSI`;
// Change gauge color based on pressure (playful colors)
let gaugeColor = '#ff6b6b'; // Coral red for low pressure
if (pressure > 70) gaugeColor = '#4ecdc4'; // Turquoise for high pressure
else if (pressure > 40) gaugeColor = '#feca57'; // Sunny yellow for medium pressure
pressureArc.style.stroke = gaugeColor;
}
}
generatePassengers() {
// Generate 3-5 random passengers with destinations
const passengerCount = 3 + Math.floor(Math.random() * 3);
this.passengers = [];
const names = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Henry'];
const usedNames = new Set();
for (let i = 0; i < passengerCount; i++) {
let name;
do {
name = names[Math.floor(Math.random() * names.length)];
} while (usedNames.has(name));
usedNames.add(name);
// Pick a random destination station (not depot)
const destinationStations = this.stations.slice(1); // Skip depot
const destination = destinationStations[Math.floor(Math.random() * destinationStations.length)];
this.passengers.push({
name: name,
destination: destination.name,
emoji: destination.emoji,
urgent: Math.random() < 0.3 // 30% chance of urgent passenger
});
}
this.updatePassengerDisplay();
}
updatePassengerDisplay() {
const passengerList = document.getElementById('passenger-list');
if (!passengerList) return;
passengerList.innerHTML = '';
this.passengers.forEach(passenger => {
const passengerDiv = document.createElement('div');
passengerDiv.className = 'passenger' + (passenger.urgent ? ' urgent' : '');
passengerDiv.textContent = `${passenger.name} → ${passenger.emoji}`;
passengerList.appendChild(passengerDiv);
});
}
shovelsCoal() {
// Called when player gets correct answer - adds momentum
const coalWorker = document.getElementById('coal-worker');
if (coalWorker) {
coalWorker.classList.remove('shoveling');
coalWorker.classList.add('shoveling');
setTimeout(() => coalWorker.classList.remove('shoveling'), 800);
}
// Reset the coal timer - fresh fuel for the train!
this.lastCorrectAnswerTime = Date.now();
// Create coal particle effects
this.createCoalParticles();
// Create steam puffs from train
this.createSteamPuffs();
// Increase momentum (coal gives energy) - adaptive based on difficulty
const config = this.getMomentumConfig();
this.momentum = Math.min(100, this.momentum + config.momentumGain);
this.updateMomentumDisplay();
// Flash momentum bar to show boost
this.flashMomentumBar();
// Update time of day
this.updateTimeOfDay();
// Play train whistle for celebrations and milestones
if (this.streak >= 5 && this.streak % 3 === 0) {
// Major milestone - play train whistle
setTimeout(() => {
this.playSound('train_whistle', 0.4);
}, 200);
console.log(`🚂💨 TOOT TOOT! Milestone whistle for streak of ${this.streak}!`);
} else if (this.momentum >= 90) {
// High momentum celebration - occasional whistle
if (Math.random() < 0.3) {
setTimeout(() => {
this.playSound('train_whistle', 0.25);
}, 150);
}
}
this.playSound('correct');
}
createCoalParticles() {
// Create flying coal particles from shoveler to train
const routeMap = document.querySelector('.route-map');
if (!routeMap) return;
for (let i = 0; i < 5; i++) {
const particle = document.createElement('div');
particle.className = 'coal-particle';
// Random size between 4-8px
const size = 4 + Math.random() * 4;
particle.style.width = size + 'px';
particle.style.height = size + 'px';
// Start position near coal worker
particle.style.left = (60 + Math.random() * 20) + 'px';
particle.style.top = (350 + Math.random() * 20) + 'px';
// Apply animation with delay
particle.style.animation = `coalFly 0.8s ease-out ${i * 0.1}s forwards`;
routeMap.appendChild(particle);
// Remove particle after animation
setTimeout(() => {
if (particle.parentNode) {
particle.parentNode.removeChild(particle);
}
}, 800 + (i * 100));
}
}
createSteamPuffs() {
// Create steam puffs from the locomotive
const routeMap = document.querySelector('.route-map');
const locomotive = document.getElementById('train-position');
if (!routeMap || !locomotive) return;
const trainRect = locomotive.getBoundingClientRect();
const mapRect = routeMap.getBoundingClientRect();
for (let i = 0; i < 3; i++) {
const puff = document.createElement('div');
puff.className = 'steam-puff';
// Random size between 15-25px
const size = 15 + Math.random() * 10;
puff.style.width = size + 'px';
puff.style.height = size + 'px';
// Position near locomotive chimney
const relativeLeft = trainRect.left - mapRect.left - 10 + Math.random() * 20;
const relativeTop = trainRect.top - mapRect.top - 15 + Math.random() * 10;
puff.style.left = relativeLeft + 'px';
puff.style.top = relativeTop + 'px';
// Apply animation with delay
puff.style.animation = `steamRise 1.2s ease-out ${i * 0.2}s forwards`;
routeMap.appendChild(puff);
// Remove puff after animation
setTimeout(() => {
if (puff.parentNode) {
puff.parentNode.removeChild(puff);
}
}, 1200 + (i * 200));
}
}
flashMomentumBar() {
// Flash the momentum bar to show boost
const momentumBar = document.getElementById('momentum-bar');
if (momentumBar) {
momentumBar.classList.remove('momentum-boost');
momentumBar.classList.add('momentum-boost');
setTimeout(() => momentumBar.classList.remove('momentum-boost'), 800);
}
}
getMomentumConfig() {
// Return momentum settings based on difficulty level (timeout setting)
switch (this.timeoutSetting) {
case 'preschool': // 4-year-olds like Fern - forgiving but still challenging
return {
momentumGain: 15, // More momentum per correct answer
baseDecay: 1.5, // Noticeable decay to keep it interesting
mediumSpeedDecay: 2.0, // Momentum matters at medium speed
highSpeedDecay: 2.5, // High speed requires attention
wrongAnswerPenalty: 3, // Small penalty for mistakes
starvationThreshold: 25, // 25 seconds before starvation kicks in
starvationRate: 15, // Very slow starvation rate
maxExtraDecay: 1.5, // Gentle extra decay
warningThreshold: 30
};
case 'kindergarten': // 5-6 year olds - gentle but engaging
return {
momentumGain: 12,
baseDecay: 1.8, // Increased from 0.8
mediumSpeedDecay: 2.2, // Increased from 1.2
highSpeedDecay: 2.8, // Increased from 1.8
wrongAnswerPenalty: 4,
starvationThreshold: 20,
starvationRate: 12,
maxExtraDecay: 2, // Increased from 1.5
warningThreshold: 25
};
case 'relaxed': // Casual players - should still feel momentum pressure
return {
momentumGain: 10,
baseDecay: 2.0, // Increased from 1.2
mediumSpeedDecay: 2.5, // Increased from 1.8
highSpeedDecay: 3.0, // Increased from 2.2
wrongAnswerPenalty: 5,
starvationThreshold: 15,
starvationRate: 10,
maxExtraDecay: 2.5, // Increased from 2
warningThreshold: 20
};
case 'slow': // Learning mode
return {
momentumGain: 8,
baseDecay: 1.5,
mediumSpeedDecay: 2.0,
highSpeedDecay: 2.5,
wrongAnswerPenalty: 6,
starvationThreshold: 12,
starvationRate: 8,
maxExtraDecay: 2.5,
warningThreshold: 18
};
case 'normal': // Default balanced
return {
momentumGain: 8,
baseDecay: 2,
mediumSpeedDecay: 2.5,
highSpeedDecay: 3,
wrongAnswerPenalty: 8,
starvationThreshold: 10,
starvationRate: 5,
maxExtraDecay: 3,
warningThreshold: 15
};
case 'fast': // Challenge mode
return {
momentumGain: 6,
baseDecay: 2.5,
mediumSpeedDecay: 3.2,
highSpeedDecay: 4,
wrongAnswerPenalty: 10,
starvationThreshold: 8,
starvationRate: 4,
maxExtraDecay: 4,
warningThreshold: 12
};
case 'expert': // Expert mode - original aggressive settings
return {
momentumGain: 5,
baseDecay: 3,
mediumSpeedDecay: 4,
highSpeedDecay: 5,
wrongAnswerPenalty: 12,
starvationThreshold: 6,
starvationRate: 3,
maxExtraDecay: 5,
warningThreshold: 10
};
default:
return this.getMomentumConfig.call({timeoutSetting: 'normal'});
}
}
loseMomentum() {
// Called when player gets wrong answer or times out - DRAMATIC CONSEQUENCES!
// Wrong answers cause complete momentum loss and coal spilling
const previousMomentum = this.momentum;
this.momentum = 0; // Complete momentum loss for wrong answers!
// Trigger dramatic coal spilling animation
this.triggerCoalSpilling();
// Update displays
this.updateMomentumDisplay();
this.updatePressureGauge();
console.log(`💥 COAL SPILLING! Train momentum dropped from ${previousMomentum}% to 0% (DRAMATIC CONSEQUENCES)`);
// Play coal spilling sound with multiple chunks
this.playCoalSpillingSounds();
}
triggerCoalSpilling() {
// Create dramatic coal spilling visual effects
const routeContainer = document.querySelector('.route-path');
if (!routeContainer) return;
// Get train position for coal spilling location
const locomotive = document.getElementById('train-position');
if (!locomotive) return;
const trainRect = locomotive.getBoundingClientRect();
const containerRect = routeContainer.getBoundingClientRect();
// Create multiple coal chunks that spill from the train
for (let i = 0; i < 8; i++) {
const coalChunk = document.createElement('div');
coalChunk.className = 'coal-chunk';
coalChunk.textContent = '⬛';
coalChunk.style.position = 'absolute';
coalChunk.style.fontSize = '1.2rem';
coalChunk.style.zIndex = '50';
coalChunk.style.pointerEvents = 'none';
// Start position near the train
const startX = (trainRect.left - containerRect.left) + (Math.random() - 0.5) * 30;
const startY = (trainRect.top - containerRect.top) + (Math.random() - 0.5) * 20;
coalChunk.style.left = startX + 'px';
coalChunk.style.top = startY + 'px';
// Add dramatic spilling animation
coalChunk.style.animation = `coalSpill ${0.8 + Math.random() * 0.4}s ease-out forwards`;
coalChunk.style.animationDelay = `${i * 0.1}s`;
routeContainer.appendChild(coalChunk);
// Remove chunk after animation
setTimeout(() => {
if (coalChunk.parentNode) {
coalChunk.parentNode.removeChild(coalChunk);
}
}, 1500 + (i * 100));
}
// Flash pressure gauge red to show dramatic failure
const pressureGauge = document.querySelector('.pressure-gauge-container');
if (pressureGauge) {
pressureGauge.style.animation = 'pressureAlarm 0.8s ease-in-out';
setTimeout(() => {
pressureGauge.style.animation = '';
}, 800);
}
}
playCoalSpillingSounds() {
// Play realistic coal chunk falling sounds
for (let i = 0; i < 4; i++) {
setTimeout(() => {
this.playSound('coal_spill', 0.3);
}, i * 150); // Staggered coal chunks falling
}
// Add a final "clatter" with incorrect sound
setTimeout(() => {
this.playSound('incorrect', 0.2);
}, 600);
console.log('🔊 Playing realistic coal spilling sound effects');
}
updateTrainPosition() {
// Move train along the route based on momentum
if (this.momentum > 0) {
const speed = this.momentum / 100; // Convert to 0-1 speed multiplier
this.trainPosition += speed * 0.4; // Continuous movement rate (called 5x per second)
// Check for route completion before capping at 100
if (this.trainPosition >= 100) {
this.handleRouteCompletion();
return; // Route completion will reset position
}
// Update visual position along the path
this.updateTrainVisualization();
// Play momentum-based train sounds (chuffing varies with speed)
this.updateMomentumBasedAudio(speed);
// Check for station arrivals and passenger deliveries
this.checkStationArrivals();
}
}
handleRouteCompletion() {
// Prevent multiple calls during transition
if (this.isRouteSwitching) return;
this.isRouteSwitching = true;
console.log(`🎉 ROUTE COMPLETED! Moving to new world (Route ${this.currentRoute + 1})`);
// CRITICAL: Clean up all timers to prevent chaos
this.cleanupRouteTimers();
// Play celebration whistle
this.playSound('train_whistle', 0.6);
setTimeout(() => {
this.playSound('celebration', 0.4);
}, 800);
// Initialize route progression if not set
if (!this.currentRoute) {
this.currentRoute = 1;
this.totalDistance = 0;
}
// Track progress
this.currentRoute++;
this.totalDistance += 100; // Each route is 100 units
// Show route completion celebration
this.showRouteCompletionCelebration();
// Generate new route after brief celebration
setTimeout(() => {
this.generateNewRoute();
}, 3000);
}
cleanupRouteTimers() {
// Stop all running intervals to prevent chaos during route transition
console.log('🧹 Cleaning up route timers...');
if (this.momentumDecayInterval) {
clearInterval(this.momentumDecayInterval);
this.momentumDecayInterval = null;
}
if (this.trainMovementInterval) {
clearInterval(this.trainMovementInterval);
this.trainMovementInterval = null;
}
if (this.displaySwitchTimeout) {
clearTimeout(this.displaySwitchTimeout);
this.displaySwitchTimeout = null;
}
// Clear any auto-submit timeouts
this.clearAutoSubmitTimeout();
// Clear celebration timeouts to prevent overlapping
if (this.celebrationTimeout) {
clearTimeout(this.celebrationTimeout);
this.celebrationTimeout = null;
}
}
showRouteCompletionCelebration() {
// Create celebration overlay
const routeContainer = document.querySelector('.route-path');
if (!routeContainer) return;
const celebration = document.createElement('div');
celebration.className = 'route-completion-celebration';
celebration.innerHTML = `
<div class="celebration-content">
<h2>🎉 ROUTE COMPLETED! 🎉</h2>
<p>Excellent work! Moving to Route ${this.currentRoute}</p>
<div class="progress-stats">
<div>📍 Total Distance: ${this.totalDistance} km</div>
<div>🚂 Routes Completed: ${this.currentRoute - 1}</div>
<div>✅ Correct Answers: ${this.correctAnswers}</div>
</div>
</div>
`;
routeContainer.appendChild(celebration);
// Remove celebration after animation
setTimeout(() => {
if (celebration.parentNode) {
celebration.parentNode.removeChild(celebration);
}
}, 2800);
}
generateNewRoute() {
// Reset train position to start of new route
this.trainPosition = 0;
// Generate new track layout with different theme
this.generateDynamicTrack();
// Update route theme/scenery
this.updateRouteTheme();
// Update route progress display
this.updateRouteProgressDisplay();
// Generate new passengers for this route
this.generatePassengers();
// Restart essential timers for new route
this.restartRouteTimers();
console.log(`🌟 NEW ROUTE GENERATED! Welcome to Route ${this.currentRoute} - ${this.getRouteThemeName()}`);
// Play "all aboard" whistle
setTimeout(() => {
this.playSound('train_whistle', 0.4);
}, 500);
// Clear route switching flag
this.isRouteSwitching = false;
}
restartRouteTimers() {
// Restart momentum decay interval
this.momentumDecayInterval = setInterval(() => {
if (this.momentum > 0) {
const decayConfig = this.getMomentumConfig();
const timeSinceLastCoal = (Date.now() - this.lastCorrectAnswerTime) / 1000;
// Base decay rate increases with higher speeds, but adjusted for skill level
let decayRate = this.momentum > 75 ? decayConfig.highSpeedDecay :
this.momentum > 50 ? decayConfig.mediumSpeedDecay :
decayConfig.lowSpeedDecay;
// Exponential penalty after coal starvation threshold
if (timeSinceLastCoal > decayConfig.starvationThreshold) {
const starvationMultiplier = Math.pow(1.5, Math.floor(timeSinceLastCoal / decayConfig.starvationThreshold));
decayRate *= Math.min(starvationMultiplier, 10); // Cap at 10x
}
this.momentum = Math.max(0, this.momentum - decayRate);
this.updateMomentumDisplay();
}
}, 1000);
// Restart train movement interval
this.trainMovementInterval = setInterval(() => {
this.updateTrainPosition();
}, 200); // Update train position 5 times per second for smooth movement
console.log('🔄 Route timers restarted successfully');
}
updateRouteProgressDisplay() {
const routeProgressEl = document.getElementById('route-progress');
if (routeProgressEl) {
const routeName = this.getRouteThemeName();
routeProgressEl.textContent = `Route ${this.currentRoute} - ${routeName}`;
}
}
getRouteThemeName() {
const themes = [
"Mountain Valley Express",
"Coastal Railway Adventure",
"Forest Journey Trail",
"Desert Oasis Line",
"Prairie Meadow Route",
"Riverside Scenic Railway",
"Highland Express",
"Lakeside Loop",
"Countryside Connection",
"Sunset Mesa Line"
];
return themes[(this.currentRoute - 1) % themes.length] || "Mystery Route";
}
updateRouteTheme() {
// Cycle through different time-of-day themes for variety
const themes = ['dawn', 'morning', 'midday', 'afternoon', 'dusk'];
const themeIndex = (this.currentRoute - 1) % themes.length;
const themeName = themes[themeIndex];
const steamJourney = document.querySelector('.race-track-section.steam-journey');
if (steamJourney) {
steamJourney.style.setProperty('--sky-gradient', `var(--${themeName}-gradient)`);
console.log(`🎨 Route theme set to: ${themeName}`);
}
}
updateTrainVisualization() {
const locomotive = document.getElementById('train-position');
if (!locomotive) return;
// Get the actual SVG path and use it for precise positioning
const routePath = document.querySelector('.train-route');
if (!routePath) return;
const progress = this.trainPosition / 100;
const pathLength = routePath.getTotalLength();
const targetLength = progress * pathLength;
// Get the exact point on the curved path
const point = routePath.getPointAtLength(targetLength);
// Calculate train rotation based on path direction
const lookAheadDistance = Math.min(5, pathLength - targetLength); // Look 5 units ahead (or to end)
const nextPoint = routePath.getPointAtLength(targetLength + lookAheadDistance);
// Calculate angle between current point and next point
const deltaX = nextPoint.x - point.x;
const deltaY = nextPoint.y - point.y;
const angleRadians = Math.atan2(deltaY, deltaX);
const angleDegrees = angleRadians * (180 / Math.PI);
// Apply position and rotation to locomotive
locomotive.style.left = point.x + 'px';
locomotive.style.top = point.y + 'px';
locomotive.style.transform = `translate(-50%, -50%) rotate(${angleDegrees}deg)`;
locomotive.style.transformOrigin = 'center center';
// Update floating math display position to follow the train
// Position train math display above the locomotive if following mode is active
if (this.isDisplayMoving) {
let trainMath = document.getElementById('train-math-display');
if (!trainMath) {
// Create train math display element
trainMath = document.createElement('div');
trainMath.id = 'train-math-display';
trainMath.className = 'train-math-display';
// Add to same container as the train
const journeyContainer = document.querySelector('.journey-container');
if (journeyContainer) {
journeyContainer.appendChild(trainMath);
console.log('🎈 Created train math display attached to train');
}
}
if (trainMath) {
// Update content and position
const challengeNum = document.getElementById('challenge-number').textContent;
const targetNum = document.getElementById('target-number').textContent;
trainMath.textContent = `${challengeNum} + ? = ${targetNum}`;
trainMath.style.left = point.x + 'px';
trainMath.style.top = (point.y - 60) + 'px';
}
} else {
// Remove train math display when not following train
const trainMath = document.getElementById('train-math-display');
if (trainMath) {
trainMath.remove();
}
}
console.log(`🚂 Train at position ${Math.round(this.trainPosition)}% - ${Math.round(point.x)}, ${Math.round(point.y)} (on track)`);
}
updateMomentumBasedAudio(speed) {
// Play chuffing sounds that vary with train speed/momentum
const now = Date.now();
// Initialize chuff timing if not set
if (!this.lastChuffTime) {
this.lastChuffTime = now;
this.chuffInterval = 1000; // Start with slow chuffing
}
// Calculate chuffing interval based on speed (faster train = faster chuffs)
// Speed range: 0-1, chuff interval: 2000ms (slow) to 300ms (fast)
this.chuffInterval = Math.max(300, 2000 - (speed * 1700));
// Play chuff sound if enough time has passed
if (now - this.lastChuffTime >= this.chuffInterval) {
// Higher speed = louder chuffs (more steam)
const chuffVolume = Math.min(0.4, 0.1 + (speed * 0.3));
this.playSound('train_chuff', chuffVolume);
// Occasionally add steam hiss at high speeds
if (speed > 0.7 && Math.random() < 0.3) {
setTimeout(() => {
this.playSound('steam_hiss', chuffVolume * 0.5);
}, 100);
}
this.lastChuffTime = now;
console.log(`🚂 Chuff! Speed: ${Math.round(speed * 100)}%, Interval: ${Math.round(this.chuffInterval)}ms`);
}
}
handleDisplaySwitching() {
// Only apply display switching logic in Lightning Sprint train mode
if (!document.querySelector('.route-path')) return;
// After 2 seconds, enable train-following mode for math display
if (this.displaySwitchTimeout) {
clearTimeout(this.displaySwitchTimeout);
}
this.displaySwitchTimeout = setTimeout(() => {
this.isDisplayMoving = true;
console.log('🎈 Display switching activated - math will follow train in 2 seconds');
}, 2000);
}
checkStationArrivals() {
// Check if we've reached any stations where passengers want to get off
this.stations.forEach(station => {
const stationReached = this.trainPosition >= station.position;
if (stationReached) {
// Deliver passengers to this station
const deliveredPassengers = this.passengers.filter(p => p.destination === station.name);
if (deliveredPassengers.length > 0) {
deliveredPassengers.forEach(passenger => {
const points = passenger.urgent ? 20 : 10;
this.totalScore += points;
console.log(`🎯 Delivered ${passenger.name} to ${station.name} for ${points} points!`);
});
// Remove delivered passengers
this.passengers = this.passengers.filter(p => p.destination !== station.name);
this.updatePassengerDisplay();
this.playSound('milestone');
}
}
});
}
checkTrainJourneyCompletion() {
// Don't check completion in the first 10 seconds to allow game to get started
const elapsed = (Date.now() - this.gameStartTime) / 1000;
if (elapsed < 10) return;
// For Lightning Sprint train mode, ONLY end when train reaches the end of the route
const journeyComplete = this.trainPosition >= 100; // Reached the final station (Capital)
// Only consider ending early if train is truly stuck (very strict conditions)
const config = this.getMomentumConfig();
const timeSinceLastCoal = (Date.now() - this.lastCorrectAnswerTime) / 1000;
const trainStuck = this.momentum <= 0 &&
this.trainPosition < 5 &&
this.correctAnswers > 5 &&
timeSinceLastCoal > (config.starvationThreshold * 2); // Much longer grace period
if (journeyComplete) {
// Check if we're in endless steam journey mode or regular race mode
const isEndlessSteamJourney = document.querySelector('.route-path'); // Steam journey has route-path
if (isEndlessSteamJourney) {
// Don't end race - let the endless route progression handle this
console.log('🚂 Journey complete detected but endless mode active - route progression will handle');
return;
} else {
// Regular race mode - end normally
const score = this.totalScore;
const deliveredCount = this.correctAnswers;
this.endRace(`🎉 Journey Complete! Reached Capital Station! Score: ${score} points from ${deliveredCount} deliveries!`);
}
} else if (trainStuck) {
// Train is truly stuck - but this should be very rare now with adaptive difficulty
const isEndlessSteamJourney = document.querySelector('.route-path');
if (isEndlessSteamJourney) {
console.log('🚂 Train stuck detected but endless mode active - generating momentum boost');
// In endless mode, give a momentum boost instead of ending
this.momentum = Math.max(10, this.momentum + 5); // Small boost to keep going
this.updateMomentumDisplay();
return;
} else {
// Regular race mode - end normally
const progress = Math.round(this.trainPosition);
console.log(`🚂 Train considered stuck: momentum=${this.momentum}, position=${this.trainPosition}%, answers=${this.correctAnswers}, time since coal=${Math.round(timeSinceLastCoal)}s`);
this.endRace(`🚂 Train ran out of steam at ${progress}% of the journey! Try an easier difficulty mode for more forgiving momentum!`);
}
}
// If all passengers delivered but train hasn't finished route, generate more passengers!
if (this.passengers.length === 0 && this.trainPosition < 90) {
console.log(`🚂 Generating more passengers - journey continues! Position: ${Math.round(this.trainPosition)}%`);
this.generatePassengers();
}
}
createDirtParticles(tunnelElement, particleCount = 5) {
// Create flying dirt particles when digging (more for deeper tunnels)
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.className = 'dirt-particle';
// Random trajectory
const flyX = (Math.random() - 0.5) * 100;
const flyY = -30 - Math.random() * 40;
particle.style.setProperty('--fly-x', flyX + 'px');
particle.style.setProperty('--fly-y', flyY + 'px');
// Position at tunnel opening
const rect = tunnelElement.getBoundingClientRect();
particle.style.left = (rect.left + rect.width / 2) + 'px';
particle.style.top = rect.top + 'px';
particle.style.position = 'fixed';
particle.style.zIndex = '100';
document.body.appendChild(particle);
// Remove after animation
setTimeout(() => {
if (particle.parentNode) {
particle.parentNode.removeChild(particle);
}
}, 600);
}
}
foxFindsMemory(digger, correctAnswers, isVeryDeep = false) {
// Different treasures for different depths
const shallowTreasures = ['🟡', '🔶', '🟠', '🔸', '✨'];
const deepTreasures = ['💎', '⭐', '💰', '🏆', '🎯'];
const veryDeepTreasures = ['💎', '👑', '🏆', '⚡', '🌟', '🔥'];
const shallowMemories = [
'Found a shiny pebble!',
'Discovered a small gem!',
'Unearthed a crystal!',
'Found a gold flake!'
];
const deepMemories = [
'Discovered an ancient coin!',
'Found a golden nugget!',
'Discovered buried treasure!',
'Unearthed a precious stone!',
'Discovered a lucky charm!'
];
const veryDeepMemories = [
'FOUND A LEGENDARY DIAMOND!',
'DISCOVERED ANCIENT TREASURE!',
'UNEARTHED A ROYAL CROWN!',
'FOUND THE ULTIMATE GEM!',
'DISCOVERED MAGICAL ARTIFACT!'
];
// Select appropriate treasure and memory based on depth
let treasures, memories;
if (isVeryDeep) {
treasures = veryDeepTreasures;
memories = veryDeepMemories;
} else if (correctAnswers > 10) {
treasures = deepTreasures;
memories = deepMemories;
} else {
treasures = shallowTreasures;
memories = shallowMemories;
}
const treasure = treasures[Math.floor(Math.random() * treasures.length)];
const memory = memories[Math.floor(Math.random() * memories.length)];
// Create floating treasure with depth-based effects
const treasureElement = document.createElement('div');
treasureElement.textContent = treasure;
const treasureSize = isVeryDeep ? '2rem' : correctAnswers > 10 ? '1.7rem' : '1.5rem';
const glowEffect = isVeryDeep ? 'filter: drop-shadow(0 0 10px #ffd700);' : '';
treasureElement.style.cssText = `
position: fixed;
font-size: ${treasureSize};
z-index: 10000;
pointer-events: none;
animation: treasureFloat 2s ease-out forwards;
${glowEffect}
`;
// Position at tunnel
const tunnelElement = document.getElementById(`${digger}-tunnel`);
if (tunnelElement) {
const rect = tunnelElement.getBoundingClientRect();
treasureElement.style.left = (rect.left + rect.width / 2) + 'px';
treasureElement.style.top = rect.top + 'px';
document.body.appendChild(treasureElement);
// Show discovery message
setTimeout(() => {
if (digger === 'player') {
this.showTreasureMessage(`🦊 ${memory}`);
this.playSound('milestone', 0.25);
}
}, 200);
// Remove treasure element
setTimeout(() => {
if (treasureElement.parentNode) {
treasureElement.parentNode.removeChild(treasureElement);
}
}, 2000);
}
}
showTreasureMessage(message) {
const treasureMsg = document.createElement('div');
treasureMsg.className = 'treasure-message';
treasureMsg.textContent = message;
treasureMsg.style.cssText = `
position: fixed;
top: 15%;
right: 20px;
background: rgba(255, 215, 0, 0.95);
color: #333;
padding: 12px 18px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
box-shadow: 0 6px 20px rgba(255, 215, 0, 0.4);
z-index: 9999;
animation: treasureSlideIn 1.5s ease-out forwards;
max-width: 200px;
text-align: center;
`;
document.body.appendChild(treasureMsg);
// Remove after display
setTimeout(() => {
if (treasureMsg.parentNode) {
treasureMsg.parentNode.removeChild(treasureMsg);
}
}, 1500);
}
startAIRacers() {
if (this.aiUpdateInterval) {
clearInterval(this.aiUpdateInterval);
}
this.aiUpdateInterval = setInterval(() => {
if (!this.isGameActive) return;
this.aiRacers.forEach(ai => {
// Store previous position for passing detection
ai.previousPosition = ai.position;
// AI progress based on their speed and some randomness
let baseProgress = ai.speed * (Math.random() * 0.8 + 0.6); // Some variance
// Rubber band catchup system - AI speeds up when behind player
const playerProgress = this.correctAnswers;
const gapBehindPlayer = playerProgress - ai.position;
if (gapBehindPlayer > 0) {
// AI is behind - apply catchup boost
let catchupMultiplier = 1.0;
if (gapBehindPlayer >= 8) {
catchupMultiplier = 2.5; // Massive catchup for big gaps
} else if (gapBehindPlayer >= 5) {
catchupMultiplier = 2.0; // Strong catchup for medium gaps
} else if (gapBehindPlayer >= 3) {
catchupMultiplier = 1.6; // Moderate catchup for small gaps
} else if (gapBehindPlayer >= 1) {
catchupMultiplier = 1.3; // Light catchup for tiny gaps
}
// Apply the catchup boost
baseProgress *= catchupMultiplier;
// Add extra urgency if player is on a streak
if (this.streak >= 5) {
baseProgress *= 1.3; // AI gets desperate when player is hot
}
// Debug logging for catchup
if (gapBehindPlayer >= 3 && this.totalQuestions % 3 === 0) {
console.log(`🏃 ${ai.name} Catchup: Gap=${Math.round(gapBehindPlayer)} Boost=${Math.round(catchupMultiplier * 100)}% Progress=${Math.round(baseProgress * 100)/100}`);
}
}
ai.position += baseProgress;
// Calculate visual progress
let visualProgress;
if (this.style === 'practice') {
// Traditional race to finish line
const progress = (ai.position / this.raceGoal) * 100;
visualProgress = Math.min(progress, 100);
} else {
// Sprint and survival: visual progress based on position relative to reasonable target
const visualTarget = 30;
const progress = (ai.position / visualTarget) * 100;
visualProgress = Math.min(progress, 95); // Cap at 95% so no one "wins" visually
}
const aiElement = document.getElementById(ai.id);
if (aiElement) {
if (this.style === 'survival') {
// Use circular positioning for survival mode and track laps
const lapInfo = this.updateCircularPosition(aiElement, ai.position);
// Update AI's lap count
const previousLap = this.aiLaps.get(ai.id) || 0;
if (lapInfo.lap > previousLap) {
this.aiLaps.set(ai.id, lapInfo.lap);
}
} else if (this.style === 'sprint') {
// Use tunnel digging for sprint mode
const aiNumber = ai.id.includes('1') ? '1' : '2';
const targetTunnel = `ai-tunnel-${aiNumber}`;
const aiDepth = Math.floor(ai.position);
console.log(`🦝 ${ai.name} (ID: ${ai.id}) digging tunnel ${targetTunnel} to depth ${aiDepth}`);
this.updateTunnelDepth(targetTunnel, aiDepth);
} else {
// Use linear positioning for practice mode
aiElement.style.left = visualProgress + '%';
}
}
// Check if AI wins - only for practice mode with finish line
if (this.style === 'practice' && visualProgress >= 100 && this.isGameActive) {
this.handleRaceWin('ai', ai.name);
}
});
}, 1200); // Update AI every 1200ms (slower for balance)
}
handleRaceWin(winner, aiName = null) {
if (!this.isGameActive) return;
// Store the race winner for medal calculation
this.raceWinner = winner;
this.winningAI = aiName;
// Stop all race activity
if (this.aiUpdateInterval) {
clearInterval(this.aiUpdateInterval);
this.aiUpdateInterval = null;
}
const winnerElement = winner === 'player'
? document.getElementById('player-racer')
: document.getElementById(this.aiRacers.find(ai => ai.name === aiName)?.id);
if (winnerElement) {
winnerElement.classList.add('winner');
winnerElement.style.left = '95%'; // Move to finish line
}
// Play celebration sound and show message
if (winner === 'player') {
this.playSound('celebration');
setTimeout(() => {
// Check if we're in endless steam journey mode
const isEndlessSteamJourney = document.querySelector('.route-path');
if (isEndlessSteamJourney) {
console.log('🚂 AI race completion detected but endless mode active - route progression will handle');
return;
}
this.endRace();
}, 1500);
} else {
this.playSound('gameOver');
setTimeout(() => {
// Check if we're in endless steam journey mode
const isEndlessSteamJourney = document.querySelector('.route-path');
if (isEndlessSteamJourney) {
console.log('🚂 AI race completion detected but endless mode active - route progression will handle');
return;
}
// Show "try again" modal or end game
alert(`🏁 ${aiName} wins the race! You got ${this.correctAnswers}/${this.raceGoal} correct. Great effort!`);
this.endRace();
}, 800);
}
}
stopRace() {
if (this.aiUpdateInterval) {
clearInterval(this.aiUpdateInterval);
this.aiUpdateInterval = null;
}
// Stop all sounds when stopping race
this.stopAllSounds();
}
playSound(type, volume = 0.15) {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Track audio contexts for cleanup
if (!window.audioContexts) {
window.audioContexts = [];
}
window.audioContexts.push(audioContext);
if (type === 'correct') {
// Classic 90s "power-up" sound - ascending beeps
this.play90sSound(audioContext, [
{freq: 523, time: 0, duration: 0.08}, // C5
{freq: 659, time: 0.08, duration: 0.08}, // E5
{freq: 784, time: 0.16, duration: 0.12} // G5
], volume, 'sawtooth');
} else if (type === 'incorrect') {
// Classic arcade "error" sound - descending buzz
this.play90sSound(audioContext, [
{freq: 400, time: 0, duration: 0.15},
{freq: 300, time: 0.05, duration: 0.15},
{freq: 200, time: 0.1, duration: 0.2}
], volume * 0.8, 'square');
} else if (type === 'timeout') {
// Classic "time's up" alarm
this.play90sSound(audioContext, [
{freq: 800, time: 0, duration: 0.1},
{freq: 600, time: 0.1, duration: 0.1},
{freq: 800, time: 0.2, duration: 0.1},
{freq: 600, time: 0.3, duration: 0.15}
], volume, 'square');
} else if (type === 'countdown') {
// Classic arcade countdown beep
this.play90sSound(audioContext, [
{freq: 800, time: 0, duration: 0.15}
], volume * 0.6, 'sine');
} else if (type === 'race_start') {
// Epic race start fanfare
this.play90sSound(audioContext, [
{freq: 523, time: 0, duration: 0.1}, // C5
{freq: 659, time: 0.1, duration: 0.1}, // E5
{freq: 784, time: 0.2, duration: 0.1}, // G5
{freq: 1046, time: 0.3, duration: 0.3} // C6 - triumphant!
], volume * 1.2, 'sawtooth');
} else if (type === 'celebration') {
// Classic victory fanfare - like completing a level
this.play90sSound(audioContext, [
{freq: 523, time: 0, duration: 0.12}, // C5
{freq: 659, time: 0.12, duration: 0.12}, // E5
{freq: 784, time: 0.24, duration: 0.12}, // G5
{freq: 1046, time: 0.36, duration: 0.24}, // C6
{freq: 1318, time: 0.6, duration: 0.3} // E6 - epic finish!
], volume * 1.5, 'sawtooth');
} else if (type === 'lap_celebration') {
// Radical "bonus achieved" sound
this.play90sSound(audioContext, [
{freq: 1046, time: 0, duration: 0.08}, // C6
{freq: 1318, time: 0.08, duration: 0.08}, // E6
{freq: 1568, time: 0.16, duration: 0.08}, // G6
{freq: 2093, time: 0.24, duration: 0.15} // C7 - totally rad!
], volume * 1.3, 'sawtooth');
} else if (type === 'gameOver') {
// Classic "game over" descending tones
this.play90sSound(audioContext, [
{freq: 400, time: 0, duration: 0.2},
{freq: 350, time: 0.2, duration: 0.2},
{freq: 300, time: 0.4, duration: 0.2},
{freq: 250, time: 0.6, duration: 0.3},
{freq: 200, time: 0.9, duration: 0.4}
], volume, 'triangle');
} else if (type === 'ai_turbo') {
// Sound when AI goes into turbo mode
this.play90sSound(audioContext, [
{freq: 200, time: 0, duration: 0.05},
{freq: 400, time: 0.05, duration: 0.05},
{freq: 600, time: 0.1, duration: 0.05},
{freq: 800, time: 0.15, duration: 0.1}
], volume * 0.7, 'sawtooth');
} else if (type === 'milestone') {
// Rad milestone sound - like collecting a power-up
this.play90sSound(audioContext, [
{freq: 659, time: 0, duration: 0.1}, // E5
{freq: 784, time: 0.1, duration: 0.1}, // G5
{freq: 880, time: 0.2, duration: 0.1}, // A5
{freq: 1046, time: 0.3, duration: 0.15} // C6 - awesome!
], volume * 1.1, 'sawtooth');
} else if (type === 'streak') {
// Epic streak sound - getting hot!
this.play90sSound(audioContext, [
{freq: 880, time: 0, duration: 0.06}, // A5
{freq: 1046, time: 0.06, duration: 0.06}, // C6
{freq: 1318, time: 0.12, duration: 0.08}, // E6
{freq: 1760, time: 0.2, duration: 0.1} // A6 - on fire!
], volume * 1.2, 'sawtooth');
} else if (type === 'combo') {
// Gnarly combo sound - for rapid correct answers
this.play90sSound(audioContext, [
{freq: 1046, time: 0, duration: 0.04}, // C6
{freq: 1175, time: 0.04, duration: 0.04}, // D6
{freq: 1318, time: 0.08, duration: 0.04}, // E6
{freq: 1480, time: 0.12, duration: 0.06} // F#6
], volume * 0.9, 'square');
} else if (type === 'whoosh') {
// Cool whoosh sound for fast responses
const whooshOsc = audioContext.createOscillator();
const whooshGain = audioContext.createGain();
const whooshFilter = audioContext.createBiquadFilter();
whooshOsc.connect(whooshFilter);
whooshFilter.connect(whooshGain);
whooshGain.connect(audioContext.destination);
whooshOsc.type = 'sawtooth';
whooshFilter.type = 'highpass';
whooshFilter.frequency.setValueAtTime(1000, audioContext.currentTime);
whooshFilter.frequency.exponentialRampToValueAtTime(100, audioContext.currentTime + 0.3);
whooshOsc.frequency.setValueAtTime(400, audioContext.currentTime);
whooshOsc.frequency.exponentialRampToValueAtTime(800, audioContext.currentTime + 0.15);
whooshOsc.frequency.exponentialRampToValueAtTime(200, audioContext.currentTime + 0.3);
whooshGain.gain.setValueAtTime(0, audioContext.currentTime);
whooshGain.gain.exponentialRampToValueAtTime(volume * 0.6, audioContext.currentTime + 0.02);
whooshGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.3);
whooshOsc.start(audioContext.currentTime);
whooshOsc.stop(audioContext.currentTime + 0.3);
} else if (type === 'train_chuff') {
// Realistic steam train chuffing sound
const chuffOsc = audioContext.createOscillator();
const chuffGain = audioContext.createGain();
const chuffFilter = audioContext.createBiquadFilter();
chuffOsc.connect(chuffFilter);
chuffFilter.connect(chuffGain);
chuffGain.connect(audioContext.destination);
chuffOsc.type = 'sawtooth';
chuffFilter.type = 'bandpass';
chuffFilter.frequency.setValueAtTime(150, audioContext.currentTime);
chuffFilter.Q.setValueAtTime(5, audioContext.currentTime);
chuffOsc.frequency.setValueAtTime(80, audioContext.currentTime);
chuffOsc.frequency.exponentialRampToValueAtTime(120, audioContext.currentTime + 0.05);
chuffOsc.frequency.exponentialRampToValueAtTime(60, audioContext.currentTime + 0.2);
chuffGain.gain.setValueAtTime(0, audioContext.currentTime);
chuffGain.gain.exponentialRampToValueAtTime(volume * 0.8, audioContext.currentTime + 0.01);
chuffGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.2);
chuffOsc.start(audioContext.currentTime);
chuffOsc.stop(audioContext.currentTime + 0.2);
} else if (type === 'train_whistle') {
// Classic steam train whistle
this.play90sSound(audioContext, [
{freq: 523, time: 0, duration: 0.3}, // C5 - long whistle
{freq: 659, time: 0.1, duration: 0.4}, // E5 - harmony
{freq: 523, time: 0.3, duration: 0.2} // C5 - fade out
], volume * 1.2, 'sine');
} else if (type === 'coal_spill') {
// Coal chunks spilling sound effect
const coalOsc = audioContext.createOscillator();
const coalGain = audioContext.createGain();
const coalFilter = audioContext.createBiquadFilter();
coalOsc.connect(coalFilter);
coalFilter.connect(coalGain);
coalGain.connect(audioContext.destination);
coalOsc.type = 'square';
coalFilter.type = 'lowpass';
coalFilter.frequency.setValueAtTime(300, audioContext.currentTime);
// Simulate coal chunks falling with random frequency bursts
coalOsc.frequency.setValueAtTime(200 + Math.random() * 100, audioContext.currentTime);
coalOsc.frequency.exponentialRampToValueAtTime(100 + Math.random() * 50, audioContext.currentTime + 0.1);
coalOsc.frequency.exponentialRampToValueAtTime(80 + Math.random() * 40, audioContext.currentTime + 0.3);
coalGain.gain.setValueAtTime(0, audioContext.currentTime);
coalGain.gain.exponentialRampToValueAtTime(volume * 0.6, audioContext.currentTime + 0.01);
coalGain.gain.exponentialRampToValueAtTime(volume * 0.3, audioContext.currentTime + 0.15);
coalGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.4);
coalOsc.start(audioContext.currentTime);
coalOsc.stop(audioContext.currentTime + 0.4);
} else if (type === 'steam_hiss') {
// Steam hissing sound for locomotive
const steamOsc = audioContext.createOscillator();
const steamGain = audioContext.createGain();
const steamFilter = audioContext.createBiquadFilter();
steamOsc.connect(steamFilter);
steamFilter.connect(steamGain);
steamGain.connect(audioContext.destination);
steamOsc.type = 'triangle';
steamFilter.type = 'highpass';
steamFilter.frequency.setValueAtTime(2000, audioContext.currentTime);
steamOsc.frequency.setValueAtTime(4000 + Math.random() * 1000, audioContext.currentTime);
steamGain.gain.setValueAtTime(0, audioContext.currentTime);
steamGain.gain.exponentialRampToValueAtTime(volume * 0.4, audioContext.currentTime + 0.02);
steamGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.6);
steamOsc.start(audioContext.currentTime);
steamOsc.stop(audioContext.currentTime + 0.6);
}
} catch (e) {
console.log('🎵 Web Audio not supported - missing out on rad 90s sounds!');
}
}
play90sSound(audioContext, notes, volume = 0.15, waveType = 'sine') {
notes.forEach(note => {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
const filterNode = audioContext.createBiquadFilter();
// Create that classic 90s arcade sound chain
oscillator.connect(filterNode);
filterNode.connect(gainNode);
gainNode.connect(audioContext.destination);
// Set wave type for that retro flavor
oscillator.type = waveType;
// Add some 90s-style filtering
filterNode.type = 'lowpass';
filterNode.frequency.setValueAtTime(2000, audioContext.currentTime + note.time);
filterNode.Q.setValueAtTime(1, audioContext.currentTime + note.time);
// Set frequency and add vibrato for that classic arcade wobble
oscillator.frequency.setValueAtTime(note.freq, audioContext.currentTime + note.time);
if (waveType === 'sawtooth' || waveType === 'square') {
// Add slight vibrato for extra 90s flavor
oscillator.frequency.exponentialRampToValueAtTime(
note.freq * 1.02,
audioContext.currentTime + note.time + note.duration * 0.5
);
oscillator.frequency.exponentialRampToValueAtTime(
note.freq,
audioContext.currentTime + note.time + note.duration
);
}
// Classic arcade envelope - quick attack, moderate decay
gainNode.gain.setValueAtTime(0, audioContext.currentTime + note.time);
gainNode.gain.exponentialRampToValueAtTime(volume, audioContext.currentTime + note.time + 0.01);
gainNode.gain.exponentialRampToValueAtTime(volume * 0.7, audioContext.currentTime + note.time + note.duration * 0.7);
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + note.time + note.duration);
oscillator.start(audioContext.currentTime + note.time);
oscillator.stop(audioContext.currentTime + note.time + note.duration);
});
}
// ===== ADAPTIVE DIFFICULTY SYSTEM =====
trackPerformance(isCorrect) {
const responseTime = Date.now() - this.questionStartTime;
const pairKey = `${this.currentNumber}_${this.correctAnswer}_${this.targetSum}`;
// Get or create performance data for this pair
let pairData = this.difficultyTracker.pairPerformance.get(pairKey) || {
attempts: 0,
correct: 0,
totalTime: 0,
avgTime: 0,
difficulty: 1,
recentPerformance: [] // Track last 5 attempts
};
// Update performance data
pairData.attempts++;
if (isCorrect) pairData.correct++;
pairData.totalTime += responseTime;
pairData.avgTime = pairData.totalTime / pairData.attempts;
// Track recent performance (sliding window of 5)
pairData.recentPerformance.push({
correct: isCorrect,
time: responseTime,
timestamp: Date.now()
});
if (pairData.recentPerformance.length > 5) {
pairData.recentPerformance.shift();
}
// Calculate dynamic difficulty for this pair
this.calculatePairDifficulty(pairData);
// Update global tracking
if (isCorrect) {
this.difficultyTracker.consecutiveCorrect++;
this.difficultyTracker.consecutiveIncorrect = 0;
} else {
this.difficultyTracker.consecutiveIncorrect++;
this.difficultyTracker.consecutiveCorrect = 0;
}
// Adapt overall difficulty
this.adaptGlobalDifficulty();
// Adapt AI speeds based on player performance
this.adaptAISpeeds();
// Store updated data
this.difficultyTracker.pairPerformance.set(pairKey, pairData);
// Exit learning mode after sufficient data
if (this.difficultyTracker.pairPerformance.size >= 5 &&
Array.from(this.difficultyTracker.pairPerformance.values())
.some(data => data.attempts >= 3)) {
this.difficultyTracker.learningMode = false;
}
}
calculatePairDifficulty(pairData) {
if (pairData.attempts < 2) return; // Need more data
const accuracyRate = pairData.correct / pairData.attempts;
const avgTime = pairData.avgTime;
const targetTime = 2000; // Target 2 seconds
// Recent performance weight (last 5 attempts matter more)
const recentAccuracy = pairData.recentPerformance.length > 0 ?
pairData.recentPerformance.filter(p => p.correct).length / pairData.recentPerformance.length :
accuracyRate;
const recentAvgTime = pairData.recentPerformance.length > 0 ?
pairData.recentPerformance.reduce((sum, p) => sum + p.time, 0) / pairData.recentPerformance.length :
avgTime;
// Calculate difficulty (1-5 scale)
let newDifficulty = pairData.difficulty;
if (recentAccuracy >= 0.8 && recentAvgTime < targetTime) {
// Performing well - increase difficulty
newDifficulty = Math.min(5, pairData.difficulty + 0.2);
} else if (recentAccuracy < 0.5 || recentAvgTime > targetTime * 1.5) {
// Struggling - decrease difficulty
newDifficulty = Math.max(1, pairData.difficulty - 0.3);
}
pairData.difficulty = newDifficulty;
}
adaptGlobalDifficulty() {
const tracker = this.difficultyTracker;
// Adapt time limits based on recent performance
if (tracker.consecutiveCorrect >= 3) {
// Reduce time limit (increase difficulty)
tracker.currentTimeLimit = Math.max(1000,
tracker.currentTimeLimit - (tracker.currentTimeLimit * tracker.adaptationRate));
} else if (tracker.consecutiveIncorrect >= 2) {
// Increase time limit (decrease difficulty)
tracker.currentTimeLimit = Math.min(5000,
tracker.currentTimeLimit + (tracker.baseTimeLimit * tracker.adaptationRate));
}
// Update overall difficulty level
const avgDifficulty = Array.from(tracker.pairPerformance.values())
.reduce((sum, data) => sum + data.difficulty, 0) /
Math.max(1, tracker.pairPerformance.size);
tracker.difficultyLevel = Math.round(avgDifficulty);
}
adaptAISpeeds() {
// Don't adapt during learning mode - let player get comfortable first
if (this.difficultyTracker.learningMode) return;
const tracker = this.difficultyTracker;
const playerSuccessRate = this.calculateRecentSuccessRate();
const avgResponseTime = this.calculateAverageResponseTime();
// Base speed multipliers for each race mode
let baseSpeedMultiplier;
switch (this.style) {
case 'practice': baseSpeedMultiplier = 0.7; break;
case 'sprint': baseSpeedMultiplier = 0.9; break;
case 'survival': baseSpeedMultiplier = this.speedMultiplier * (this.survivalMultiplier || 1.0); break;
default: baseSpeedMultiplier = 0.7;
}
// Calculate adaptive multiplier based on player performance
let adaptiveMultiplier = 1.0;
// Success rate factor (0.5x to 1.8x based on 40%-95% success rate)
if (playerSuccessRate > 0.85) {
adaptiveMultiplier *= 1.6; // Player doing great - speed up AI significantly
} else if (playerSuccessRate > 0.75) {
adaptiveMultiplier *= 1.3; // Player doing well - speed up AI moderately
} else if (playerSuccessRate > 0.60) {
adaptiveMultiplier *= 1.0; // Player doing okay - keep AI at base speed
} else if (playerSuccessRate > 0.45) {
adaptiveMultiplier *= 0.75; // Player struggling - slow down AI
} else {
adaptiveMultiplier *= 0.5; // Player really struggling - significantly slow AI
}
// Response time factor - faster players get faster AI
if (avgResponseTime < 1500) {
adaptiveMultiplier *= 1.2; // Very fast player
} else if (avgResponseTime < 2500) {
adaptiveMultiplier *= 1.1; // Fast player
} else if (avgResponseTime > 4000) {
adaptiveMultiplier *= 0.9; // Slow player
}
// Streak bonus - players on hot streaks get more challenge
if (this.streak >= 8) {
adaptiveMultiplier *= 1.3;
} else if (this.streak >= 5) {
adaptiveMultiplier *= 1.15;
}
// Apply bounds to prevent extreme values
adaptiveMultiplier = Math.max(0.3, Math.min(2.0, adaptiveMultiplier));
// Update AI speeds with adaptive multiplier
const finalSpeedMultiplier = baseSpeedMultiplier * adaptiveMultiplier;
if (this.aiRacers && this.aiRacers.length >= 2) {
// Swift AI (more aggressive, varies more)
this.aiRacers[0].speed = 0.25 * finalSpeedMultiplier;
// Math Bot (more consistent, varies less)
this.aiRacers[1].speed = 0.15 * finalSpeedMultiplier;
// Show player feedback for significant AI speed changes
this.showAIAdaptationFeedback(adaptiveMultiplier, playerSuccessRate);
// Debug logging for AI adaptation
if (this.totalQuestions % 5 === 0) {
console.log('🤖 AI Speed Adaptation:', {
playerSuccessRate: Math.round(playerSuccessRate * 100) + '%',
avgResponseTime: Math.round(avgResponseTime) + 'ms',
streak: this.streak,
adaptiveMultiplier: Math.round(adaptiveMultiplier * 100) / 100,
swiftAISpeed: Math.round(this.aiRacers[0].speed * 1000) / 1000,
mathBotSpeed: Math.round(this.aiRacers[1].speed * 1000) / 1000
});
}
}
}
calculateRecentSuccessRate() {
// Calculate success rate from last 10 questions, or all questions if less than 10
const recentQuestions = Math.min(10, this.totalQuestions);
if (recentQuestions === 0) return 0.5; // Default for first question
// Use global tracking for recent performance
const recentCorrect = Math.max(0, this.correctAnswers - Math.max(0, this.totalQuestions - recentQuestions));
return recentCorrect / recentQuestions;
}
calculateAverageResponseTime() {
// Calculate average response time from recent pair performance data
const recentPairs = Array.from(this.difficultyTracker.pairPerformance.values())
.filter(data => data.attempts >= 1)
.slice(-5); // Last 5 different pairs encountered
if (recentPairs.length === 0) return 3000; // Default for learning mode
const totalTime = recentPairs.reduce((sum, data) => sum + data.avgTime, 0);
return totalTime / recentPairs.length;
}
showAIAdaptationFeedback(adaptiveMultiplier, playerSuccessRate) {
// Only show feedback for significant changes and not too frequently
if (this.totalQuestions < 8) return; // Wait for some data
if (this.totalQuestions % 7 !== 0) return; // Don't show too often
let message = '';
let type = '';
if (adaptiveMultiplier >= 1.4) {
const messages = [
"🔥 You're crushing it! AI opponents stepping up their game!",
"⚡ Impressive speed! The AI is now in competition mode!",
"🚀 You're on fire! AI racers are feeling the pressure!",
"💪 Amazing performance! AI opponents are now racing seriously!"
];
message = messages[Math.floor(Math.random() * messages.length)];
type = 'ai_speeding_up';
} else if (adaptiveMultiplier <= 0.7) {
const messages = [
"🤗 Take your time! AI opponents are giving you space to learn!",
"📚 Learning mode active! AI racers are being more patient!",
"🎯 No rush! AI opponents are matching your learning pace!",
"🌟 Perfect! AI racers are helping you build confidence!"
];
message = messages[Math.floor(Math.random() * messages.length)];
type = 'ai_slowing_down';
}
if (message) {
this.showAdaptiveFeedback(message, type);
}
}
getAdaptiveTimeLimit() {
// Get base adaptive time limit
let adaptiveTime;
if (this.difficultyTracker.learningMode) {
adaptiveTime = Math.max(2000, this.difficultyTracker.currentTimeLimit);
} else {
const pairKey = `${this.currentNumber}_${this.correctAnswer}_${this.targetSum}`;
const pairData = this.difficultyTracker.pairPerformance.get(pairKey);
if (pairData && pairData.attempts >= 2) {
// Use pair-specific difficulty
const baseTime = this.difficultyTracker.baseTimeLimit;
const difficultyMultiplier = (6 - pairData.difficulty) / 5; // Invert: difficulty 1 = more time
adaptiveTime = Math.max(1000, baseTime * difficultyMultiplier);
} else {
// Default for new pairs
adaptiveTime = this.difficultyTracker.currentTimeLimit;
}
}
// Apply user timeout setting override
return this.applyTimeoutSetting(adaptiveTime);
}
applyTimeoutSetting(baseTime) {
// Apply timeout setting multiplier
switch (this.timeoutSetting) {
case 'preschool':
return Math.max(baseTime * 4, 20000); // At least 20 seconds - perfect for 4-year-olds!
case 'kindergarten':
return Math.max(baseTime * 3, 15000); // At least 15 seconds - great for 5-6 year olds
case 'relaxed':
return Math.max(baseTime * 2.4, 12000); // At least 12 seconds - comfortable pace
case 'slow':
return Math.max(baseTime * 1.6, 8000); // At least 8 seconds, or 60% longer
case 'normal':
return Math.max(baseTime, 5000); // At least 5 seconds (default)
case 'fast':
return Math.max(baseTime * 0.6, 3000); // At least 3 seconds, or 40% shorter
case 'expert':
return Math.max(baseTime * 0.4, 2000); // At least 2 seconds, or 60% shorter
default:
return baseTime;
}
}
getDifficultyInsights() {
const tracker = this.difficultyTracker;
const insights = {
overallLevel: tracker.difficultyLevel,
currentTimeLimit: tracker.currentTimeLimit,
learningMode: tracker.learningMode,
strugglingPairs: [],
masteredPairs: [],
totalPairsTested: tracker.pairPerformance.size
};
// Analyze pair-specific performance
for (const [pairKey, data] of tracker.pairPerformance) {
const [num1, num2, sum] = pairKey.split('_').map(Number);
const accuracy = data.correct / data.attempts;
if (accuracy < 0.6 && data.attempts >= 3) {
insights.strugglingPairs.push({
pair: `${num1} + ${num2} = ${sum}`,
accuracy: Math.round(accuracy * 100),
avgTime: Math.round(data.avgTime),
difficulty: data.difficulty
});
} else if (accuracy >= 0.9 && data.avgTime < 1500 && data.attempts >= 5) {
insights.masteredPairs.push({
pair: `${num1} + ${num2} = ${sum}`,
accuracy: Math.round(accuracy * 100),
avgTime: Math.round(data.avgTime),
difficulty: data.difficulty
});
}
}
return insights;
}
}
// Particle Effects System
class ParticleSystem {
constructor() {
this.container = document.getElementById('particle-container');
this.particles = [];
}
createParticle(x, y, type = 'star', animation = 'burst') {
const particle = document.createElement('div');
particle.className = `particle ${type} ${animation}`;
// Random size between 8-20px
const size = 8 + Math.random() * 12;
particle.style.width = size + 'px';
particle.style.height = size + 'px';
// Position
particle.style.left = x + 'px';
particle.style.top = y + 'px';
// Add random velocity for trail effects
if (animation === 'trail') {
const dx = (Math.random() - 0.5) * 200;
const dy = (Math.random() - 0.5) * 200;
particle.style.setProperty('--dx', dx + 'px');
particle.style.setProperty('--dy', dy + 'px');
}
this.container.appendChild(particle);
this.particles.push(particle);
// Remove particle after animation
const duration = animation === 'floating' ? 2000 : animation === 'firework' ? 1200 : 800;
setTimeout(() => this.removeParticle(particle), duration);
}
removeParticle(particle) {
if (particle.parentNode) {
particle.parentNode.removeChild(particle);
}
this.particles = this.particles.filter(p => p !== particle);
}
// Success celebration - burst of stars
celebrateSuccess(sourceElement) {
const rect = sourceElement.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// Create burst of particles
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
const distance = 30 + Math.random() * 20;
const x = centerX + Math.cos(angle) * distance;
const y = centerY + Math.sin(angle) * distance;
const types = ['star', 'circle', 'diamond'];
const type = types[Math.floor(Math.random() * types.length)];
setTimeout(() => {
this.createParticle(x, y, type, 'burst');
}, i * 50);
}
}
// Major celebration - fireworks and confetti
celebrateMajor(sourceElement) {
const rect = sourceElement.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// Central firework
this.createParticle(centerX, centerY, 'star', 'firework');
// Surrounding smaller fireworks
for (let i = 0; i < 6; i++) {
const angle = (i / 6) * Math.PI * 2;
const distance = 80 + Math.random() * 40;
const x = centerX + Math.cos(angle) * distance;
const y = centerY + Math.sin(angle) * distance;
setTimeout(() => {
this.createParticle(x, y, 'heart', 'firework');
}, i * 100);
}
// Confetti shower
setTimeout(() => {
for (let i = 0; i < 20; i++) {
const x = centerX + (Math.random() - 0.5) * 200;
const y = centerY - 50 + Math.random() * 100;
setTimeout(() => {
this.createParticle(x, y, 'confetti', 'floating');
}, i * 30);
}
}, 300);
}
// Streak celebration - trail effects
celebrateStreak(sourceElement, streakCount) {
const rect = sourceElement.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const particleCount = Math.min(streakCount * 2, 16);
for (let i = 0; i < particleCount; i++) {
const x = centerX + (Math.random() - 0.5) * 100;
const y = centerY + (Math.random() - 0.5) * 100;
setTimeout(() => {
this.createParticle(x, y, 'circle', 'trail');
}, i * 25);
}
}
// Sparkle effect for hovering or highlighting
sparkle(sourceElement) {
const rect = sourceElement.getBoundingClientRect();
const x = rect.left + Math.random() * rect.width;
const y = rect.top + Math.random() * rect.height;
this.createParticle(x, y, 'star', 'sparkle');
}
// Game complete celebration
celebrateGameComplete(sourceElement) {
// Multiple waves of celebration
this.celebrateMajor(sourceElement);
setTimeout(() => {
this.celebrateSuccess(sourceElement);
}, 400);
setTimeout(() => {
this.celebrateStreak(sourceElement, 10);
}, 800);
}
}
// Global particle system instance
let particleSystem;
// Tag filtering system
function initializeTagFiltering() {
const tagFilters = document.querySelectorAll('.tag-filter');
const challengeCards = document.querySelectorAll('.challenge-card');
tagFilters.forEach(filter => {
filter.addEventListener('click', () => {
// Remove active class from all filters
tagFilters.forEach(f => f.classList.remove('active'));
// Add active class to clicked filter
filter.classList.add('active');
const selectedTag = filter.dataset.tag;
// Show/hide challenge cards based on selected tag
challengeCards.forEach(card => {
if (selectedTag === 'all') {
card.style.display = 'flex';
} else {
const cardTags = card.dataset.tags ? card.dataset.tags.split(',') : [];
if (cardTags.includes(selectedTag)) {
card.style.display = 'flex';
} else {
card.style.display = 'none';
}
}
});
// Update the visible count in the grid
updateVisibleChallengeLayout();
});
});
}
function updateVisibleChallengeLayout() {
// Force the grid to recalculate layout for visible items
const challengesGrid = document.querySelector('.challenges-grid');
if (challengesGrid) {
challengesGrid.style.display = 'none';
challengesGrid.offsetHeight; // Force reflow
challengesGrid.style.display = 'grid';
}
}
document.addEventListener('DOMContentLoaded', () => {
// Initialize particle system first
particleSystem = new ParticleSystem();
// Set bead shape class based on data attribute
const beadSample = document.querySelector('.bead-sample[data-shape]');
if (beadSample) {
const shape = beadSample.getAttribute('data-shape').toLowerCase();
beadSample.classList.add(shape);
}
new ModalManager();
new SectionNavigator();
new SorobanQuiz();
new SortingChallenge();
new MatchingChallenge();
new SpeedComplementRace();
// Initialize tag filtering system
initializeTagFiltering();
// 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>
<!-- Race Countdown Overlay -->
<div id="race-countdown" class="race-countdown">
<div class="countdown-display">
<div id="countdown-text" class="countdown-text">Get Ready!</div>
<div id="countdown-number" class="countdown-number">3</div>
</div>
</div>
<!-- Particle Effects Container -->
<div id="particle-container" class="particle-container"></div>
</body>
</html>