-
-
Save creativeidiot123/e05a57611ca5d93a7bc27ea7bdb6e6cf to your computer and use it in GitHub Desktop.
| Front Template | |
| <div id="card-container"> | |
| <!-- FRONT CARD --> | |
| <div class="phone-container" id="front-card"> | |
| <div class="scroll-view"> | |
| <div class="profile-area"> | |
| <div class="german-word-big">{{German Word}}</div> | |
| <div class="german-extra">{{German Word Extra}}</div> | |
| </div> | |
| <div class="info-section"> | |
| <div class="label-meaning context-label">Context</div> | |
| <div class="context-text"> | |
| {{Deutsch Example}} | |
| </div> | |
| <div class="tap-hint"> | |
| Tap to reveal | |
| </div> | |
| </div> | |
| </div> | |
| <div class="actions" style="opacity:0.25; pointer-events:none;"> | |
| <div class="btn-circle btn-pass">✕</div> | |
| <div class="btn-circle btn-hard">?</div> | |
| <div class="btn-circle btn-like">♥</div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // ═══════════════════════════════════════════════════════════════ | |
| // ANKI API INITIALIZATION | |
| // ═══════════════════════════════════════════════════════════════ | |
| var api = null; | |
| try { | |
| if (typeof AnkiDroidJS !== 'undefined') { | |
| var jsApiContract = { version: "0.0.3", developer: "anki_tinder_ui" }; | |
| api = new AnkiDroidJS(jsApiContract); | |
| } | |
| } catch(e) {} | |
| // Track when card is shown for time-based ease selection | |
| // Using localStorage instead of window variables to survive Anki's page replacement | |
| localStorage.setItem('ankiCardShownTime', Date.now().toString()); | |
| // Initialize fallback (in case user uses keyboard/button instead of clicking) | |
| localStorage.setItem('ankiFrontCardTimeSpent', '0'); | |
| var flipped = false; | |
| // ═══════════════════════════════════════════════════════════════ | |
| // CARD REVEAL INTERACTION | |
| // ═══════════════════════════════════════════════════════════════ | |
| window.addEventListener('click', function() { | |
| if (flipped) return; | |
| flipped = true; | |
| // Calculate time spent on front card (for smart ease selection) | |
| var cardShownTime = parseInt(localStorage.getItem('ankiCardShownTime') || Date.now().toString()); | |
| var timeSpent = Date.now() - cardShownTime; | |
| localStorage.setItem('ankiFrontCardTimeSpent', timeSpent.toString()); | |
| console.log("=== FRONT CARD FLIP ==="); | |
| console.log("Time spent on front:", timeSpent, "ms"); | |
| // Subtle haptic feedback on reveal (30ms for better feel) | |
| if (navigator.vibrate) navigator.vibrate(30); | |
| // Show answer immediately | |
| if (api) { | |
| api.ankiShowAnswer(); | |
| } else if (typeof pycmd !== 'undefined') { | |
| pycmd('ans'); | |
| } | |
| }); | |
| // ═══════════════════════════════════════════════════════════════ | |
| // KEYBOARD SHORTCUTS (PC Support) | |
| // ═══════════════════════════════════════════════════════════════ | |
| window.addEventListener('keydown', function(e) { | |
| // Only handle arrow keys: Down, Left, Right | |
| if (e.key !== 'ArrowDown' && e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') { | |
| return; | |
| } | |
| // If card already flipped, let back card handle it | |
| if (flipped) return; | |
| // Prevent default arrow key behavior (scrolling) | |
| e.preventDefault(); | |
| // Flip the card (same logic as click handler) | |
| flipped = true; | |
| // Calculate time spent on front card (for smart ease selection) | |
| var cardShownTime = parseInt(localStorage.getItem('ankiCardShownTime') || Date.now().toString()); | |
| var timeSpent = Date.now() - cardShownTime; | |
| localStorage.setItem('ankiFrontCardTimeSpent', timeSpent.toString()); | |
| console.log("=== FRONT CARD FLIP (KEYBOARD) ==="); | |
| console.log("Time spent on front:", timeSpent, "ms"); | |
| // Subtle haptic feedback on reveal (30ms for better feel) | |
| if (navigator.vibrate) navigator.vibrate(30); | |
| // Show answer immediately | |
| if (api) { | |
| api.ankiShowAnswer(); | |
| } else if (typeof pycmd !== 'undefined') { | |
| pycmd('ans'); | |
| } | |
| }); | |
| </script> | |
| <div style="opacity:0; height:0;">{{Audio}}</div> | |
| <div style="height:20px;"></div> | |
| BacK Template | |
| <!-- Feedback Overlays --> | |
| <div class="feedback-overlay overlay-bad" id="overlay-bad"></div> | |
| <div class="feedback-overlay overlay-hard" id="overlay-hard"></div> | |
| <div class="feedback-overlay overlay-good" id="overlay-good"></div> | |
| <div id="card-container"> | |
| <!-- BACK CARD --> | |
| <div class="phone-container" id="back-card"> | |
| <div class="scroll-view"> | |
| <div class="profile-area"> | |
| <div class="german-word-big">{{German Word}}</div> | |
| <div class="german-extra">{{German Word Extra}}</div> | |
| </div> | |
| <div class="info-section"> | |
| <div class="label-meaning">Meaning</div> | |
| <div class="answer-big">{{English Word}}</div> | |
| <div class="type-tag">{{Type}}</div> | |
| <hr style="border:0; border-top:1px solid #222222; margin:20px 0;"> | |
| {{#Usage Advice}} | |
| <div class="usage-box">💡 {{Usage Advice}}</div> | |
| {{/Usage Advice}} | |
| <div class="label-meaning" style="text-align:left; margin-bottom:15px;">Examples</div> | |
| <div class="example-block"> | |
| <div class="ex-de">{{Deutsch Example}}</div> | |
| <div class="ex-en">{{English Example}}</div> | |
| </div> | |
| {{#German Example 2}} | |
| <div class="example-block"> | |
| <div class="ex-de">{{German Example 2}}</div> | |
| <div class="ex-en">{{English Example 2}}</div> | |
| </div> | |
| {{/German Example 2}} | |
| </div> | |
| </div> | |
| <div class="actions"> | |
| <div class="btn-circle btn-pass" id="btn-again">✕</div> | |
| <div class="btn-circle btn-hard" id="btn-hard">?</div> | |
| <div class="btn-circle btn-like" id="btn-good">♥</div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // ═══════════════════════════════════════════════════════════════ | |
| // CONFIGURATION CONSTANTS | |
| // ═══════════════════════════════════════════════════════════════ | |
| var SWIPE_THRESHOLD = 115; // Minimum swipe distance to trigger answer | |
| var ROTATION_FACTOR = 0.048; // Card rotation intensity during swipe | |
| var GLOW_DIVISOR = 0.8; // Overlay opacity calculation factor | |
| var MAX_GLOW = 0.9; // Maximum overlay opacity | |
| var TIME_THRESHOLD = 7000; // 7 seconds - threshold for smart Hard/Good selection | |
| // Animation Timing (all values in milliseconds) | |
| var TIMING = { | |
| HAPTIC_DELAY: 25, // Delay between audio and haptic for premium feel | |
| OVERLAY_FADE_START: 50, // When overlay starts fading after answer | |
| CARD_ANIMATION: 350, // Card fly-off animation duration | |
| OVERLAY_FADE: 100, // Overlay fade-out duration | |
| SNAP_BACK: 200, // Card snap-back animation when swipe fails | |
| SNAP_OVERLAY_FADE: 150, // Overlay fade on snap-back | |
| ANSWER_DELAY: 350 // Total delay before sending answer (audio + buffer) | |
| }; | |
| // Haptic Patterns | |
| var HAPTICS = { | |
| GOOD: 50, // Single pulse for correct answer | |
| BAD: [50, 30, 80] // Triple pulse for wrong answer | |
| }; | |
| // Animation End Positions | |
| var ANIMATION_END = { | |
| X: 1200, // Horizontal distance for fly-off | |
| Y: 1200, // Vertical distance for fly-off (swipe down) | |
| ROTATION: 22 // Rotation angle at end of fly-off | |
| }; | |
| // ═══════════════════════════════════════════════════════════════ | |
| // ANKI API INITIALIZATION | |
| // ═══════════════════════════════════════════════════════════════ | |
| var api = null; | |
| try { | |
| if (typeof AnkiDroidJS !== 'undefined') { | |
| var jsApiContract = { version: "0.0.3", developer: "anki_tinder_ui" }; | |
| api = new AnkiDroidJS(jsApiContract); | |
| } | |
| } catch(e) { console.log("API Init Failed: " + e); } | |
| // ═══════════════════════════════════════════════════════════════ | |
| // TIME TRACKING FOR SMART EASE SELECTION | |
| // ═══════════════════════════════════════════════════════════════ | |
| // Read from localStorage (survives Anki's page replacement) | |
| // Fallback: If user used keyboard/button instead of clicking, calculate now | |
| var storedTimeSpent = localStorage.getItem('ankiFrontCardTimeSpent'); | |
| var storedCardShownTime = localStorage.getItem('ankiCardShownTime'); | |
| var timeSpent = storedTimeSpent ? parseInt(storedTimeSpent) : | |
| (storedCardShownTime ? (Date.now() - parseInt(storedCardShownTime)) : 0); | |
| var tookTooLong = timeSpent >= TIME_THRESHOLD; | |
| // Debug logging | |
| console.log("=== TIME DEBUG ==="); | |
| console.log("storedCardShownTime:", storedCardShownTime); | |
| console.log("storedTimeSpent:", storedTimeSpent); | |
| console.log("Current time:", Date.now()); | |
| console.log("Calculated timeSpent:", timeSpent, "ms"); | |
| console.log("Threshold:", TIME_THRESHOLD, "ms"); | |
| console.log("tookTooLong:", tookTooLong); | |
| console.log("=================="); | |
| // ═══════════════════════════════════════════════════════════════ | |
| // AUDIO PRELOADING | |
| // ═══════════════════════════════════════════════════════════════ | |
| // Create and preload audio objects immediately for instant playback | |
| var sndGood = new Audio('_tinder_correct.mp3'); | |
| var sndBad = new Audio('_tinder_wrong.mp3'); | |
| sndGood.load(); | |
| sndBad.load(); | |
| // ═══════════════════════════════════════════════════════════════ | |
| // ANSWER SUBMISSION | |
| // ═══════════════════════════════════════════════════════════════ | |
| function sendAnswer(ease) { | |
| try { | |
| if (api) { | |
| if (ease === 1) ankiAnswerEase1(); | |
| else if (ease === 2) ankiAnswerEase2(); | |
| else if (ease === 3) ankiAnswerEase3(); | |
| } else if (typeof pycmd !== 'undefined') { | |
| pycmd('ease' + ease); | |
| } | |
| } catch(e) { | |
| try { | |
| if (ease === 1 && typeof buttonAnswerEase1 !== 'undefined') buttonAnswerEase1(); | |
| if (ease === 2 && typeof buttonAnswerEase2 !== 'undefined') buttonAnswerEase2(); | |
| if (ease === 3 && typeof buttonAnswerEase3 !== 'undefined') buttonAnswerEase3(); | |
| } catch(e2) {} | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════════ | |
| // DOM ELEMENTS & STATE | |
| // ═══════════════════════════════════════════════════════════════ | |
| var card = document.getElementById('back-card'); | |
| var overlayGood = document.getElementById('overlay-good'); | |
| var overlayHard = document.getElementById('overlay-hard'); | |
| var overlayBad = document.getElementById('overlay-bad'); | |
| var startX = 0; | |
| var startY = 0; | |
| var isDragging = false; | |
| var isScrolling = false; | |
| var answered = false; | |
| // ─── TOUCH START ──────────────────────────────────────────────── | |
| card.addEventListener('touchstart', function(e) { | |
| if (answered) return; | |
| startX = e.touches[0].clientX; | |
| startY = e.touches[0].clientY; | |
| isDragging = false; | |
| isScrolling = false; | |
| card.style.transition = 'none'; | |
| }, { passive: true }); | |
| // ─── TOUCH MOVE ───────────────────────────────────────────────── | |
| card.addEventListener('touchmove', function(e) { | |
| if (answered) return; | |
| var dx = e.touches[0].clientX - startX; | |
| var dy = e.touches[0].clientY - startY; | |
| if (!isDragging && !isScrolling) { | |
| // Detect direction: vertical down, vertical up (scroll), or horizontal | |
| if (Math.abs(dy) > Math.abs(dx)) { | |
| if (dy > 0) isDragging = true; // Swipe down - treat as gesture | |
| else isScrolling = true; // Swipe up - treat as scroll | |
| } else { | |
| isDragging = true; // Horizontal - treat as gesture | |
| } | |
| } | |
| if (isScrolling) return; | |
| if (isDragging) { | |
| if (e.cancelable) e.preventDefault(); | |
| // Determine if horizontal or vertical swipe | |
| if (Math.abs(dx) > Math.abs(dy)) { | |
| // Horizontal swipe | |
| var rot = dx * ROTATION_FACTOR; | |
| card.style.transform = 'translateX(' + dx + 'px) rotate(' + rot + 'deg)'; | |
| // Progressive overlay glow based on swipe distance | |
| var glow = Math.min(Math.abs(dx) / (SWIPE_THRESHOLD * GLOW_DIVISOR), MAX_GLOW); | |
| if (dx > 0) { | |
| overlayGood.style.opacity = glow; | |
| overlayHard.style.opacity = 0; | |
| overlayBad.style.opacity = 0; | |
| } else { | |
| overlayBad.style.opacity = glow; | |
| overlayGood.style.opacity = 0; | |
| overlayHard.style.opacity = 0; | |
| } | |
| } else { | |
| // Vertical swipe down (Hard) - only positive dy | |
| if (dy > 0) { | |
| card.style.transform = 'translateY(' + dy + 'px)'; | |
| // Progressive yellow overlay for swipe down | |
| var glow = Math.min(dy / (SWIPE_THRESHOLD * GLOW_DIVISOR), MAX_GLOW); | |
| overlayHard.style.opacity = glow; | |
| overlayGood.style.opacity = 0; | |
| overlayBad.style.opacity = 0; | |
| } | |
| } | |
| } | |
| }, { passive: false }); | |
| // ─── UNIFIED ANIMATION & ANSWER FUNCTION ──────────────────────── | |
| function animateAndAnswer(ease, direction) { | |
| if (answered) return; | |
| answered = true; | |
| // Determine animation type based on direction parameter | |
| // direction: 'left', 'right', 'down', or undefined (default) | |
| var isGood = (ease === 2 || ease === 3); | |
| var isBad = (ease === 1); | |
| var isHard = (ease === 2); | |
| // ─── SLEEK SEQUENCE: Audio → Haptics → Visual ─── | |
| // 1. Audio starts IMMEDIATELY (most critical for AnkiDroid) | |
| if (isGood) { | |
| sndGood.currentTime = 0; | |
| sndGood.play().catch(function(e) { console.log("Audio failed: " + e); }); | |
| } else { | |
| sndBad.currentTime = 0; | |
| sndBad.play().catch(function(e) { console.log("Audio failed: " + e); }); | |
| } | |
| // 2. Refined haptic feedback (staggered for premium feel) | |
| setTimeout(function() { | |
| if (isGood) { | |
| if (navigator.vibrate) navigator.vibrate(HAPTICS.GOOD); | |
| } else { | |
| if (navigator.vibrate) navigator.vibrate(HAPTICS.BAD); | |
| } | |
| }, TIMING.HAPTIC_DELAY); | |
| // 3. Visual overlay pulses in smoothly | |
| if (isHard) { | |
| overlayHard.style.opacity = MAX_GLOW; | |
| } else if (isGood) { | |
| overlayGood.style.opacity = MAX_GLOW; | |
| } else { | |
| overlayBad.style.opacity = MAX_GLOW; | |
| } | |
| // 4. Card flies off with smooth animation | |
| card.style.transition = 'transform ' + TIMING.CARD_ANIMATION + 'ms cubic-bezier(0.4, 0, 0.2, 1), opacity ' + TIMING.CARD_ANIMATION + 'ms ease-out'; | |
| if (direction === 'down') { | |
| // Swipe down animation (Hard) | |
| card.style.transform = 'translateY(' + ANIMATION_END.Y + 'px)'; | |
| } else { | |
| // Horizontal animation (Again/Good) | |
| var endX = isGood ? ANIMATION_END.X : -ANIMATION_END.X; | |
| var endRot = isGood ? ANIMATION_END.ROTATION : -ANIMATION_END.ROTATION; | |
| card.style.transform = 'translateX(' + endX + 'px) rotate(' + endRot + 'deg)'; | |
| } | |
| card.style.opacity = '0'; | |
| // 5. Fade overlays smoothly | |
| overlayGood.style.transition = 'opacity ' + TIMING.OVERLAY_FADE + 'ms ease-out'; | |
| overlayHard.style.transition = 'opacity ' + TIMING.OVERLAY_FADE + 'ms ease-out'; | |
| overlayBad.style.transition = 'opacity ' + TIMING.OVERLAY_FADE + 'ms ease-out'; | |
| setTimeout(function() { | |
| overlayGood.style.opacity = '0'; | |
| overlayHard.style.opacity = '0'; | |
| overlayBad.style.opacity = '0'; | |
| }, TIMING.OVERLAY_FADE_START); | |
| // 6. Send answer after audio completes (350ms for 300ms audio + 50ms buffer) | |
| setTimeout(function() { | |
| sendAnswer(ease); | |
| }, TIMING.ANSWER_DELAY); | |
| } | |
| // ─── TOUCH END ────────────────────────────────────────────────── | |
| card.addEventListener('touchend', function(e) { | |
| if (isScrolling || answered) return; | |
| var dx = e.changedTouches[0].clientX - startX; | |
| var dy = e.changedTouches[0].clientY - startY; | |
| // Check if horizontal or vertical swipe | |
| if (Math.abs(dx) > Math.abs(dy)) { | |
| // Horizontal swipe | |
| if (Math.abs(dx) > SWIPE_THRESHOLD) { | |
| // Smart ease selection: left=1 (Again), right=3 (Good) or 2 (Hard if slow) | |
| var ease = (dx > 0) ? (tookTooLong ? 2 : 3) : 1; | |
| var direction = (dx > 0) ? 'right' : 'left'; | |
| animateAndAnswer(ease, direction); | |
| } else { | |
| snapBack(); | |
| } | |
| } else { | |
| // Vertical swipe (only down triggers Hard) | |
| if (dy > SWIPE_THRESHOLD) { | |
| // Swipe down = Hard (ease 2) | |
| animateAndAnswer(2, 'down'); | |
| } else { | |
| snapBack(); | |
| } | |
| } | |
| }); | |
| // ─── SNAP BACK HELPER ─────────────────────────────────────────── | |
| function snapBack() { | |
| card.style.transition = 'transform ' + TIMING.SNAP_BACK + 'ms cubic-bezier(0.34, 1.56, 0.64, 1)'; | |
| card.style.transform = 'translateX(0) translateY(0) rotate(0deg)'; | |
| card.style.opacity = '1'; | |
| overlayGood.style.transition = 'opacity ' + TIMING.SNAP_OVERLAY_FADE + 'ms ease-out'; | |
| overlayHard.style.transition = 'opacity ' + TIMING.SNAP_OVERLAY_FADE + 'ms ease-out'; | |
| overlayBad.style.transition = 'opacity ' + TIMING.SNAP_OVERLAY_FADE + 'ms ease-out'; | |
| overlayGood.style.opacity = '0'; | |
| overlayHard.style.opacity = '0'; | |
| overlayBad.style.opacity = '0'; | |
| } | |
| // ─── BUTTON HANDLERS (with haptics & animations) ─────────────── | |
| document.getElementById('btn-again').onclick = function() { | |
| animateAndAnswer(1, 'left'); // Always "Again" | |
| }; | |
| document.getElementById('btn-hard').onclick = function() { | |
| animateAndAnswer(2, 'down'); // Always "Hard" | |
| }; | |
| document.getElementById('btn-good').onclick = function() { | |
| // Smart: "Hard" (down animation) if slow, "Good" (right animation) if quick | |
| var ease = tookTooLong ? 2 : 3; | |
| var direction = tookTooLong ? 'down' : 'right'; | |
| animateAndAnswer(ease, direction); | |
| }; | |
| // ─── KEYBOARD SHORTCUTS (PC Support) ──────────────────────────── | |
| window.addEventListener('keydown', function(e) { | |
| // Only handle arrow keys: Down, Left, Right | |
| if (e.key !== 'ArrowDown' && e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') { | |
| return; | |
| } | |
| // Prevent default arrow key behavior (scrolling) | |
| e.preventDefault(); | |
| // Map arrow keys to answers | |
| if (e.key === 'ArrowDown') { | |
| // Down Arrow → Hard (ease 2) | |
| animateAndAnswer(2, 'down'); | |
| } else if (e.key === 'ArrowLeft') { | |
| // Left Arrow → Again (ease 1) | |
| animateAndAnswer(1, 'left'); | |
| } else if (e.key === 'ArrowRight') { | |
| // Right Arrow → Smart Good/Hard based on time spent | |
| var ease = tookTooLong ? 2 : 3; | |
| var direction = tookTooLong ? 'down' : 'right'; | |
| animateAndAnswer(ease, direction); | |
| } | |
| }); | |
| </script> | |
| {{Audio Example}} | |
| Styling | |
| /* --- FONTS --- */ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Montserrat:wght@400;700;800&display=swap'); | |
| /* --- GLOBAL RESET --- */ | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| html, body { | |
| width: 100%; | |
| height: 100%; | |
| overflow: hidden; | |
| background: #1a1f1a; | |
| font-family: "Inter", sans-serif; | |
| } | |
| /* --- CONTAINER --- */ | |
| #card-container { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| background: #1a1f1a; | |
| padding: 12px; | |
| } | |
| /* --- CARD --- */ | |
| .phone-container { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| background: #232823; | |
| display: flex; | |
| flex-direction: column; | |
| will-change: transform; | |
| z-index: 10; | |
| overflow: hidden; | |
| border-radius: 30px; | |
| } | |
| /* --- HEADER (fixed height, doesn't shrink) --- */ | |
| .profile-area { | |
| width: 100%; | |
| height: 28vh; | |
| min-height: 160px; | |
| flex-shrink: 0; | |
| background: #3a4235; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| padding-bottom: 25px; | |
| position: relative; | |
| } | |
| .german-word-big { | |
| font-family: 'Montserrat', sans-serif; | |
| font-size: clamp(24px, 6vw, 40px); | |
| font-weight: 600; | |
| color: #f0ebe0; | |
| text-align: center; | |
| line-height: 1.1; | |
| padding: 0 20px; | |
| margin-bottom: 10px; | |
| } | |
| .german-extra { | |
| font-family: "Inter", sans-serif; | |
| font-size: 13px; | |
| color: #d4c8ae; | |
| font-weight: 500; | |
| background: #2e342a; | |
| padding: 4px 14px; | |
| border-radius: 20px; | |
| } | |
| /* --- SCROLL AREA (fills ALL remaining space between header and footer) --- */ | |
| .scroll-view { | |
| flex: 1; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| -webkit-overflow-scrolling: touch; | |
| position: relative; | |
| background: #232823; | |
| min-height: 0; | |
| } | |
| /* --- CONTENT --- */ | |
| .info-section { | |
| background: #232823; | |
| width: 100%; | |
| margin-top: -25px; | |
| border-radius: 25px 25px 0 0; | |
| position: relative; | |
| z-index: 2; | |
| padding: 25px 25px 20px 25px; | |
| } | |
| /* Text Styles */ | |
| .label-meaning { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 13px; | |
| font-weight: 500; | |
| letter-spacing: 1.2px; | |
| color: #9a9278; | |
| text-transform: uppercase; | |
| text-align: center; | |
| margin-bottom: 8px; | |
| } | |
| .answer-big { | |
| font-family: 'Montserrat', sans-serif; | |
| font-size: 34px; | |
| font-weight: 500; | |
| color: #dcd0b8; | |
| text-align: center; | |
| margin-bottom: 10px; | |
| line-height: 1.2; | |
| } | |
| .type-tag { | |
| text-align: center; | |
| font-size: 13px; | |
| font-weight: 500; | |
| color: #8a8668; | |
| margin-bottom: 20px; | |
| } | |
| .usage-box { | |
| font-family: 'Inter', sans-serif; | |
| background: #2e342a; | |
| color: #c8c0a8; | |
| padding: 12px; | |
| border-radius: 10px; | |
| font-size: 16px; | |
| line-height: 1.7; | |
| margin-bottom: 20px; | |
| border-left: 4px solid #5a7a52; | |
| } | |
| .example-block { | |
| margin-bottom: 25px; | |
| padding-bottom: 15px; | |
| border-bottom: 1px solid #3a4235; | |
| } | |
| .ex-de { | |
| font-family: 'Inter', sans-serif; | |
| font-weight: 500; | |
| color: #dcd8c8; | |
| font-size: 17px; | |
| margin-bottom: 8px; | |
| line-height: 1.6; | |
| } | |
| .ex-en { | |
| font-family: 'Inter', sans-serif; | |
| font-style: italic; | |
| color: #b8b4a0; | |
| font-size: 16px; | |
| line-height: 1.6; | |
| } | |
| /* --- BOTTOM ACTION BAR (always pinned to bottom) --- */ | |
| .actions { | |
| width: 100%; | |
| height: 80px; | |
| flex-shrink: 0; | |
| background: #232823; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 25px; | |
| border-top: 1px solid #3a4235; | |
| z-index: 10; | |
| } | |
| .btn-circle { | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 50%; | |
| background: #2e342a; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-size: 28px; | |
| transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1); | |
| cursor: pointer; | |
| user-select: none; | |
| } | |
| .btn-circle:hover { | |
| transform: scale(1.05); | |
| } | |
| .btn-circle:active { | |
| transform: scale(0.9); | |
| } | |
| .btn-pass { | |
| color: #ff8888; | |
| border: 1px solid #4a5243; | |
| } | |
| .btn-pass:hover { | |
| color: #ffa0a0; | |
| border-color: #5a6a52; | |
| } | |
| .btn-hard { | |
| color: #f8d858; | |
| border: 1px solid #4a5243; | |
| } | |
| .btn-hard:hover { | |
| color: #ffeb78; | |
| border-color: #5a6a52; | |
| } | |
| .btn-like { | |
| color: #6b9b6b; | |
| border: 1px solid #4a5243; | |
| } | |
| .btn-like:hover { | |
| color: #7fb87f; | |
| border-color: #5a6a52; | |
| } | |
| /* --- FRONT CARD SPECIFIC STYLES --- */ | |
| .context-label { | |
| font-family: 'Inter', sans-serif; | |
| text-align: left; | |
| color: #9a9278; | |
| } | |
| .context-text { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 24px; | |
| font-weight: 400; | |
| color: #dcd8c8; | |
| line-height: 1.7; | |
| margin-top: 10px; | |
| } | |
| .tap-hint { | |
| text-align: center; | |
| color: #9a9278; | |
| font-size: 13px; | |
| padding: 40px 0; | |
| opacity: 0.7; | |
| transition: opacity 0.2s ease; | |
| } | |
| /* --- FEEDBACK OVERLAYS --- */ | |
| .feedback-overlay { | |
| position: fixed; | |
| top: 1px; left: 1px; right: 1px; bottom: 1px; | |
| z-index: 99; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.1s; | |
| contain: layout style paint; | |
| } | |
| .overlay-good { | |
| background: rgba(100, 160, 100, 0.18); | |
| } | |
| .overlay-hard { | |
| background: rgba(248, 216, 88, 0.20); | |
| } | |
| .overlay-bad { | |
| background: rgba(255, 120, 120, 0.20); | |
| } | |
| /* --- PERFORMANCE OPTIMIZATIONS --- */ | |
| .phone-container { | |
| contain: layout style paint; | |
| } | |
| .scroll-view { | |
| contain: layout style; | |
| } | |
To use my anki template with your template,
copy all of my back, front and styling templates and your own deck's back/front/styling templates.
give both to chatgpt and ask it to use your deck's structure with above given templates and you want a final working template with features, UI, from above given template but structure and material from yours.
or you can just search for {{XXXX}} styled text in my front and back templates and replace the text inside with your own field names
Anki Settings to Use with this
Disable any gestures you use with anki, these have their own inbuilt and they will handle it all their own,
Use Card zoom at 80% size, available in Anki>settings>accessibility
Turn on Fullscreen mode in Anki>Settings>Appearance and hide system bars and answer buttons
If you use anki on PC/Laptop too then
Copy paste the above given audio files to your collection.media folder in PC too.
PC/Laptop also have feature to work with keyboard shorcut arrow keys.
In back/answer page
Left Means Again
Down means Hard
Right means good
In front/question page pressing any of left/down/right will result in flipping of page to back page.
How to get Audio working
_tinder_correct.mp4
_tinder_wrong.mp4
These arent mp4 files, download them and rename them from mp4 to mp3 to make them usable Audio files.
their name should exactly be (_tinder_correct.mp3) and (_tinder_wrong.mp3).
then paste them in your phone's "\Internal shared storage\Android\data\com.ichi2.anki\files\AnkiDroid\collection.media" folder to make them accessible to anki. (Use laptop pc to access that folder its not accessible via phone's files app)