commit b9f319436a63566e35992aca9569041cc70d5757 Author: rarmknecht Date: Fri Dec 19 17:21:22 2025 -0600 GitHub Migration diff --git a/forms.json b/forms.json new file mode 100644 index 0000000..a979048 --- /dev/null +++ b/forms.json @@ -0,0 +1,74 @@ +[ + { + "number": "1", + "name": "Kibon", + "handPosition": "Parallel Stance, Fists in Front", + "openingMove": "Turn Left forming a LEFT FOOT FORWARD FRONT STANCE while executing a HIGH FOREARM BLOCK" + }, + { + "number": "2", + "name": "Kicho", + "handPosition": "Parallel Stance, Fists in Front", + "openingMove": "LEFT FOOT FORWARD FRONT STANCE while executing a LOW FOREARM BLOCK" + }, + { + "number": "3", + "name": "Kyuki Il Chang", + "handPosition": "Closed Stance, Left Hand Straight Over Right Fist", + "openingMove": "Looking Left, Form a LEFT FOOT FOREWARD BACKSTANCE while executing a MIDDLE DOUBLE KNIFEHAND BLOCK" + }, + { + "number": "4", + "name": "Kyuki Yee Chang", + "handPosition": "Closed Stance, Left Hand Straight Over Right Fist", + "openingMove": "Step Left Foot Back at 45 to form a RIGHT FOOT FORWARD BACK STANCE while executing a MIDDLE INSIDE FOREARM BLOCK" + }, + { + "number": "5", + "name": "Kyuki Sam Chang", + "handPosition": "Closed Stance, Left Hand Straight Over Right Fist", + "openingMove": "Step Right Foot Back to form a LEFT FOOT FORWARD FRONT STANCE while executing a MIDDLE FOREARM BLOCK" + }, + { + "number": "6", + "name": "Guen Bon", + "handPosition": "Parallel Stance, Double Arc Hand Pressing Earth", + "openingMove": "Step Right Foot Back to form a LEFT FOOT FORWARD FRONT STANCE while executing a LEFT LOW FOREARM BLOCK & RIGHT REVERSE MIDDLE INSIDE FOREARM BLOCK" + }, + { + "number": "7", + "name": "Chonji In Il Chang", + "handPosition": "Parallel Stance, Double Arc Hand Pushing High", + "openingMove": "Stepping Left to form a LEFT FOOT FORWARD BACK STANCE while executing a LEFT CORKSCREW TRAP IN-TO-OUT" + }, + { + "number": "8", + "name": "Chonji In Yee Chang", + "handPosition": "Parallel Stance, Double Arc Hand Pushing Low", + "openingMove": "Move left foot to form a HORSE STANCE facing forward. Execute a RIGHT ROLLING VERTICLE PUNCH with left hand under the right elbow" + }, + { + "number": "9", + "name": "Chonji In Sam Chang", + "handPosition": "Parallel Stance, Double Arc Hand Pushing Middle", + "openingMove": "Stepping Back Left at 45 to form a CLASSICAL KYUKIDO STANCE while executing a LOW KYUKIDO BLOCK with left fist above the head" + }, + { + "number": "10", + "name": "Man Nam", + "handPosition": "Closed Stance, Staff on Right Side", + "openingMove": "Step left foot out to parallel stance while executing a SLOW HIGH HORIZONTAL BLOCK" + }, + { + "number": "11", + "name": "Ka Chi", + "handPosition": "Parallel Stance, Twin Verticle Spear Hands", + "openingMove": "Reach up with both hands executing a sleeve and lapel grab while executing a PROPPING ANKLE THROW with the right foot" + }, + { + "number": "12", + "name": "Sa Rang", + "handPosition": "Parallel Stance, Open Hands Crossed Over Chest - Left Over Right", + "openingMove": "Step forward to form a LEFT FOOT FORWARD BACK STANCE and execute a LEFT INSIDE FOREARM BLOCK" + } +] diff --git a/index.html b/index.html new file mode 100644 index 0000000..d013ae8 --- /dev/null +++ b/index.html @@ -0,0 +1,45 @@ + + + + + + Martial Arts Forms Flashcards + + + +
+

Martial Arts Forms Flashcards

+ +
+
+
+

Click to start

+
+
+

+
+

Starting Hand Position:

+

+
+
+

Opening Move:

+

+
+
+
+
+ +
+ + 0 / 0 + +
+ +
+
+
+
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..1de00e0 --- /dev/null +++ b/script.js @@ -0,0 +1,125 @@ +class FlashcardApp { + constructor() { + this.forms = []; + this.currentIndex = 0; + this.isFlipped = false; + + this.flashcard = document.getElementById('flashcard'); + this.formName = document.getElementById('form-name'); + this.formNumber = document.getElementById('form-number'); + this.handPosition = document.getElementById('hand-position'); + this.openingMove = document.getElementById('opening-move'); + this.prevBtn = document.getElementById('prev-btn'); + this.nextBtn = document.getElementById('next-btn'); + this.cardCounter = document.getElementById('card-counter'); + this.progressFill = document.getElementById('progress-fill'); + + this.init(); + } + + async init() { + try { + await this.loadForms(); + this.setupEventListeners(); + this.updateCard(); + } catch (error) { + console.error('Error initializing app:', error); + this.showError('Failed to load forms data. Please check that forms.json exists.'); + } + } + + async loadForms() { + try { + const response = await fetch('forms.json'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + this.forms = await response.json(); + + if (!Array.isArray(this.forms) || this.forms.length === 0) { + throw new Error('Invalid forms data'); + } + } catch (error) { + throw new Error(`Failed to load forms: ${error.message}`); + } + } + + setupEventListeners() { + this.flashcard.addEventListener('click', () => this.flipCard()); + this.prevBtn.addEventListener('click', () => this.previousCard()); + this.nextBtn.addEventListener('click', () => this.nextCard()); + + document.addEventListener('keydown', (e) => { + switch(e.key) { + case 'ArrowLeft': + this.previousCard(); + break; + case 'ArrowRight': + this.nextCard(); + break; + case ' ': + case 'Enter': + e.preventDefault(); + this.flipCard(); + break; + } + }); + } + + flipCard() { + if (this.forms.length === 0) return; + + this.isFlipped = !this.isFlipped; + this.flashcard.classList.toggle('flipped', this.isFlipped); + } + + previousCard() { + if (this.forms.length === 0) return; + + this.currentIndex = (this.currentIndex - 1 + this.forms.length) % this.forms.length; + this.updateCard(); + } + + nextCard() { + if (this.forms.length === 0) return; + + this.currentIndex = (this.currentIndex + 1) % this.forms.length; + this.updateCard(); + } + + updateCard() { + if (this.forms.length === 0) return; + + const currentForm = this.forms[this.currentIndex]; + + this.formNumber.textContent = currentForm.number; + this.formName.textContent = currentForm.name; + this.handPosition.textContent = currentForm.handPosition; + this.openingMove.textContent = currentForm.openingMove; + + this.cardCounter.textContent = `${this.currentIndex + 1} / ${this.forms.length}`; + + const progressPercent = ((this.currentIndex + 1) / this.forms.length) * 100; + this.progressFill.style.width = `${progressPercent}%`; + + this.prevBtn.disabled = this.forms.length <= 1; + this.nextBtn.disabled = this.forms.length <= 1; + + this.isFlipped = false; + this.flashcard.classList.remove('flipped'); + } + + showError(message) { + this.formName.textContent = 'Error'; + this.handPosition.textContent = message; + this.openingMove.textContent = 'Please ensure forms.json exists in the same directory.'; + this.cardCounter.textContent = '0 / 0'; + this.progressFill.style.width = '0%'; + this.prevBtn.disabled = true; + this.nextBtn.disabled = true; + } +} + +document.addEventListener('DOMContentLoaded', () => { + new FlashcardApp(); +}); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..c6c5bf7 --- /dev/null +++ b/styles.css @@ -0,0 +1,191 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Arial', sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.container { + width: 100%; + max-width: 600px; + text-align: center; +} + +h1 { + color: white; + margin-bottom: 30px; + font-size: 2.5rem; + text-shadow: 2px 2px 4px rgba(0,0,0,0.3); +} + +.flashcard-container { + perspective: 1000px; + margin-bottom: 30px; +} + +.flashcard { + width: 100%; + height: 400px; + position: relative; + transform-style: preserve-3d; + transition: transform 0.6s ease; + cursor: pointer; +} + +.flashcard.flipped { + transform: rotateY(180deg); +} + +.card-front, .card-back { + position: absolute; + width: 100%; + height: 100%; + backface-visibility: hidden; + border-radius: 15px; + box-shadow: 0 8px 32px rgba(0,0,0,0.3); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 40px; +} + +.card-front { + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + color: #333; +} + +.card-back { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + color: #333; + transform: rotateY(180deg); +} + +.card-front h2 { + font-size: 2.5rem; + font-weight: bold; + text-align: center; + line-height: 1.2; +} + +.info-section { + margin-bottom: 30px; + text-align: left; + width: 100%; +} + +.info-section:last-child { + margin-bottom: 0; +} + +.info-section h3 { + color: #495057; + font-size: 1.3rem; + margin-bottom: 10px; + border-bottom: 2px solid #007bff; + padding-bottom: 5px; +} + +.info-section p { + font-size: 1.1rem; + line-height: 1.5; + color: #666; +} + +.controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +button { + background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); + color: white; + border: none; + padding: 12px 24px; + border-radius: 25px; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(0,123,255,0.3); +} + +button:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0,123,255,0.4); +} + +button:disabled { + background: #6c757d; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +#card-counter { + color: white; + font-size: 1.1rem; + font-weight: bold; + background: rgba(255,255,255,0.2); + padding: 10px 20px; + border-radius: 20px; + backdrop-filter: blur(10px); +} + +.progress-bar { + width: 100%; + height: 8px; + background: rgba(255,255,255,0.3); + border-radius: 10px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #28a745 0%, #20c997 100%); + width: 0%; + transition: width 0.3s ease; + border-radius: 10px; +} + +@media (max-width: 768px) { + h1 { + font-size: 2rem; + } + + .flashcard { + height: 350px; + } + + .card-front h2 { + font-size: 2rem; + } + + .info-section h3 { + font-size: 1.1rem; + } + + .info-section p { + font-size: 1rem; + } + + .controls { + flex-direction: column; + gap: 15px; + } + + button { + width: 100%; + max-width: 200px; + } +} \ No newline at end of file