|
|
| Line 1: |
Line 1: |
| <!doctype html> | | <iframe loading="lazy" src="https://www.canva.com/design/XXXXXXXX/view?embed" |
| <html lang="en">
| | allowfullscreen="allowfullscreen" |
| <head>
| | allow="fullscreen" |
| <meta charset="UTF-8">
| | style="width:100%; height:600px; border:0;"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
| | </iframe> |
| <title>Vocabulary Matching Game</title>
| |
| <script src="/_sdk/element_sdk.js"></script>
| |
| <style>
| |
| body {
| |
| box-sizing: border-box;
| |
| margin: 0;
| |
| padding: 0;
| |
| font-family: 'Arial', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
| |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
| |
| min-height: 100%;
| |
| display: flex;
| |
| align-items: center;
| |
| justify-content: center;
| |
| }
| |
| | |
| * {
| |
| box-sizing: border-box;
| |
| }
| |
| | |
| .container {
| |
| width: 90%;
| |
| max-width: 900px;
| |
| padding: 32px;
| |
| background: white;
| |
| border-radius: 24px;
| |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
| |
| }
| |
| | |
| .header {
| |
| text-align: center;
| |
| margin-bottom: 32px;
| |
| }
| |
| | |
| h1 {
| |
| font-size: 40px;
| |
| margin: 0 0 12px 0;
| |
| background: linear-gradient(135deg, #667eea, #764ba2);
| |
| -webkit-background-clip: text;
| |
| -webkit-text-fill-color: transparent;
| |
| background-clip: text;
| |
| }
| |
| | |
| .instructions {
| |
| font-size: 16px;
| |
| color: #64748b;
| |
| margin: 0;
| |
| }
| |
| | |
| .game-area {
| |
| display: grid;
| |
| grid-template-columns: 1fr 1fr;
| |
| gap: 24px;
| |
| margin-bottom: 24px;
| |
| }
| |
| | |
| .column h2 {
| |
| font-size: 20px;
| |
| margin: 0 0 16px 0;
| |
| text-align: center;
| |
| color: #1e293b;
| |
| }
| |
| | |
| .term-card, .definition-card {
| |
| padding: 16px 20px;
| |
| border-radius: 12px;
| |
| margin-bottom: 12px;
| |
| cursor: pointer;
| |
| transition: all 0.3s ease;
| |
| border: 3px solid transparent;
| |
| font-size: 15px;
| |
| position: relative;
| |
| overflow: hidden;
| |
| }
| |
| | |
| .term-card {
| |
| background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
| |
| color: #78350f;
| |
| font-weight: bold;
| |
| }
| |
| | |
| .definition-card {
| |
| background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
| |
| color: #1e3a8a;
| |
| }
| |
| | |
| .term-card:hover:not(.matched):not(.showing-hint),
| |
| .definition-card:hover:not(.matched):not(.showing-hint) {
| |
| transform: translateY(-4px);
| |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
| |
| }
| |
| | |
| .term-card.selected, .definition-card.selected {
| |
| border-color: #8b5cf6;
| |
| transform: scale(1.05);
| |
| box-shadow: 0 8px 24px rgba(139, 92, 246, 0.4);
| |
| }
| |
| | |
| .term-card.matched, .definition-card.matched {
| |
| background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
| |
| color: #064e3b;
| |
| cursor: default;
| |
| animation: matchPulse 0.6s ease;
| |
| }
| |
| | |
| .term-card.showing-hint {
| |
| border-color: #8b5cf6;
| |
| animation: hintPulse 1s ease-in-out;
| |
| }
| |
| | |
| @keyframes matchPulse {
| |
| 0%, 100% { transform: scale(1); }
| |
| 50% { transform: scale(1.1); }
| |
| }
| |
| | |
| @keyframes hintPulse {
| |
| 0%, 100% { transform: scale(1); }
| |
| 50% { transform: scale(1.05); }
| |
| }
| |
| | |
| .checkmark {
| |
| position: absolute;
| |
| top: 8px;
| |
| right: 8px;
| |
| font-size: 20px;
| |
| animation: checkmarkPop 0.5s ease;
| |
| }
| |
| | |
| @keyframes checkmarkPop {
| |
| 0% { transform: scale(0) rotate(-45deg); }
| |
| 50% { transform: scale(1.2) rotate(10deg); }
| |
| 100% { transform: scale(1) rotate(0deg); }
| |
| }
| |
| | |
| .controls {
| |
| display: flex;
| |
| gap: 12px;
| |
| justify-content: center;
| |
| margin-bottom: 24px;
| |
| }
| |
| | |
| button {
| |
| padding: 12px 24px;
| |
| border: none;
| |
| border-radius: 12px;
| |
| font-size: 16px;
| |
| font-weight: bold;
| |
| cursor: pointer;
| |
| transition: all 0.3s ease;
| |
| }
| |
| | |
| .hint-btn {
| |
| background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
| |
| color: white;
| |
| }
| |
| | |
| .hint-btn:hover:not(:disabled) {
| |
| transform: translateY(-2px);
| |
| box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
| |
| }
| |
| | |
| .hint-btn:disabled {
| |
| opacity: 0.5;
| |
| cursor: not-allowed;
| |
| }
| |
| | |
| .reset-btn {
| |
| background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
| |
| color: white;
| |
| }
| |
| | |
| .reset-btn:hover {
| |
| transform: translateY(-2px);
| |
| box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
| |
| }
| |
| | |
| .progress {
| |
| text-align: center;
| |
| font-size: 18px;
| |
| color: #475569;
| |
| margin-bottom: 16px;
| |
| }
| |
| | |
| .progress-bar {
| |
| width: 100%;
| |
| height: 12px;
| |
| background: #e2e8f0;
| |
| border-radius: 6px;
| |
| overflow: hidden;
| |
| margin-bottom: 24px;
| |
| }
| |
| | |
| .progress-fill {
| |
| height: 100%;
| |
| background: linear-gradient(90deg, #34d399, #10b981);
| |
| transition: width 0.5s ease;
| |
| border-radius: 6px;
| |
| }
| |
| | |
| .success-message {
| |
| text-align: center;
| |
| padding: 24px;
| |
| background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
| |
| border-radius: 16px;
| |
| color: #064e3b;
| |
| font-size: 24px;
| |
| font-weight: bold;
| |
| display: none;
| |
| animation: successSlide 0.6s ease;
| |
| }
| |
| | |
| @keyframes successSlide {
| |
| from {
| |
| opacity: 0;
| |
| transform: translateY(-20px);
| |
| }
| |
| to {
| |
| opacity: 1;
| |
| transform: translateY(0);
| |
| }
| |
| }
| |
| | |
| .confetti {
| |
| position: fixed;
| |
| width: 10px;
| |
| height: 10px;
| |
| border-radius: 50%;
| |
| animation: confettiFall 3s ease-out forwards;
| |
| pointer-events: none;
| |
| }
| |
| | |
| @keyframes confettiFall {
| |
| to {
| |
| transform: translateY(100vh) rotate(720deg);
| |
| opacity: 0;
| |
| }
| |
| }
| |
| | |
| @media (max-width: 768px) {
| |
| .game-area {
| |
| grid-template-columns: 1fr;
| |
| }
| |
|
| |
| h1 {
| |
| font-size: 32px;
| |
| }
| |
|
| |
| .container {
| |
| padding: 24px;
| |
| }
| |
| }
| |
| </style>
| |
| <style>@view-transition { navigation: auto; }</style>
| |
| <script src="/_sdk/data_sdk.js" type="text/javascript"></script>
| |
| <script src="https://cdn.tailwindcss.com" type="text/javascript"></script>
| |
| </head>
| |
| <body>
| |
| <div class="container">
| |
| <div class="header">
| |
| <h1 id="game-title">Geometry Match Challenge</h1>
| |
| <p class="instructions" id="instructions">Match each term with its correct definition</p>
| |
| </div>
| |
| <div class="progress" id="progress-text">
| |
| Matched: 0 / 6
| |
| </div>
| |
| <div class="progress-bar">
| |
| <div class="progress-fill" id="progress-fill" style="width: 0%"></div>
| |
| </div>
| |
| <div class="game-area">
| |
| <div class="column">
| |
| <h2>📐 Terms</h2>
| |
| <div id="terms-container"></div>
| |
| </div>
| |
| <div class="column">
| |
| <h2>📝 Definitions</h2>
| |
| <div id="definitions-container"></div>
| |
| </div>
| |
| </div>
| |
| <div class="controls"><button class="hint-btn" id="hint-btn">💡 <span id="hint-btn-text">Need a Hint?</span></button> <button class="reset-btn" id="reset-btn">🔄 Start Over</button>
| |
| </div>
| |
| <div class="success-message" id="success-message">
| |
| 🎉 <span id="success-text">Perfect! You matched all terms!</span> 🎉
| |
| </div>
| |
| </div>
| |
| <script>
| |
| const defaultConfig = {
| |
| game_title: "Geometry Match Challenge",
| |
| hint_text: "Need a Hint?",
| |
| success_message: "Perfect! You matched all terms!",
| |
| primary_color: "#667eea",
| |
| secondary_color: "#fbbf24",
| |
| text_color: "#1e293b",
| |
| button_color: "#8b5cf6",
| |
| success_color: "#10b981",
| |
| font_family: "Arial",
| |
| font_size: 16
| |
| };
| |
| | |
| const vocabularyPairs = [
| |
| { id: 1, term: "Radius", definition: "The distance from the center of a circle to any point on its edge" },
| |
| { id: 2, term: "Diameter", definition: "A straight line passing through the center connecting two points on a circle" },
| |
| { id: 3, term: "Perimeter", definition: "The total distance around the outside of a shape" },
| |
| { id: 4, term: "Angle", definition: "The space between two lines or surfaces that meet at a point" },
| |
| { id: 5, term: "Polygon", definition: "A closed shape with straight sides" },
| |
| { id: 6, term: "Parallel", definition: "Lines that never intersect and remain the same distance apart" }
| |
| ];
| |
| | |
| let selectedTerm = null;
| |
| let selectedDefinition = null;
| |
| let matchedPairs = [];
| |
| let hintUsed = false;
| |
| | |
| function shuffleArray(array) {
| |
| const shuffled = [...array];
| |
| for (let i = shuffled.length - 1; i > 0; i--) {
| |
| const j = Math.floor(Math.random() * (i + 1));
| |
| [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
| |
| }
| |
| return shuffled;
| |
| }
| |
| | |
| function initGame() {
| |
| const termsContainer = document.getElementById('terms-container');
| |
| const definitionsContainer = document.getElementById('definitions-container');
| |
|
| |
| termsContainer.innerHTML = '';
| |
| definitionsContainer.innerHTML = '';
| |
|
| |
| const shuffledTerms = shuffleArray(vocabularyPairs);
| |
| const shuffledDefinitions = shuffleArray(vocabularyPairs);
| |
|
| |
| shuffledTerms.forEach(pair => {
| |
| const card = document.createElement('div');
| |
| card.className = 'term-card';
| |
| card.dataset.id = pair.id;
| |
| card.textContent = pair.term;
| |
| card.addEventListener('click', () => selectTerm(card, pair.id));
| |
| termsContainer.appendChild(card);
| |
| });
| |
|
| |
| shuffledDefinitions.forEach(pair => {
| |
| const card = document.createElement('div');
| |
| card.className = 'definition-card';
| |
| card.dataset.id = pair.id;
| |
| card.textContent = pair.definition;
| |
| card.addEventListener('click', () => selectDefinition(card, pair.id));
| |
| definitionsContainer.appendChild(card);
| |
| });
| |
|
| |
| matchedPairs = [];
| |
| selectedTerm = null;
| |
| selectedDefinition = null;
| |
| hintUsed = false;
| |
| updateProgress();
| |
| document.getElementById('hint-btn').disabled = false;
| |
| document.getElementById('success-message').style.display = 'none';
| |
| }
| |
| | |
| function selectTerm(card, id) {
| |
| if (matchedPairs.includes(id)) return;
| |
|
| |
| if (selectedTerm) {
| |
| selectedTerm.classList.remove('selected');
| |
| }
| |
|
| |
| selectedTerm = card;
| |
| card.classList.add('selected');
| |
|
| |
| checkMatch(id, selectedDefinition ? parseInt(selectedDefinition.dataset.id) : null);
| |
| }
| |
| | |
| function selectDefinition(card, id) {
| |
| if (matchedPairs.includes(id)) return;
| |
|
| |
| if (selectedDefinition) {
| |
| selectedDefinition.classList.remove('selected');
| |
| }
| |
|
| |
| selectedDefinition = card;
| |
| card.classList.add('selected');
| |
|
| |
| checkMatch(selectedTerm ? parseInt(selectedTerm.dataset.id) : null, id);
| |
| }
| |
| | |
| function checkMatch(termId, definitionId) {
| |
| if (termId && definitionId && termId === definitionId) {
| |
| matchedPairs.push(termId);
| |
|
| |
| selectedTerm.classList.remove('selected');
| |
| selectedDefinition.classList.remove('selected');
| |
| selectedTerm.classList.add('matched');
| |
| selectedDefinition.classList.add('matched');
| |
|
| |
| const checkmark1 = document.createElement('span');
| |
| checkmark1.className = 'checkmark';
| |
| checkmark1.textContent = '✓';
| |
| selectedTerm.appendChild(checkmark1);
| |
|
| |
| const checkmark2 = document.createElement('span');
| |
| checkmark2.className = 'checkmark';
| |
| checkmark2.textContent = '✓';
| |
| selectedDefinition.appendChild(checkmark2);
| |
|
| |
| selectedTerm = null;
| |
| selectedDefinition = null;
| |
|
| |
| updateProgress();
| |
|
| |
| if (matchedPairs.length === vocabularyPairs.length) {
| |
| setTimeout(() => {
| |
| showSuccess();
| |
| }, 500);
| |
| }
| |
| }
| |
| }
| |
| | |
| function updateProgress() {
| |
| const progressText = document.getElementById('progress-text');
| |
| const progressFill = document.getElementById('progress-fill');
| |
| const percentage = (matchedPairs.length / vocabularyPairs.length) * 100;
| |
|
| |
| progressText.textContent = `Matched: ${matchedPairs.length} / ${vocabularyPairs.length}`;
| |
| progressFill.style.width = percentage + '%';
| |
| }
| |
| | |
| function showHint() {
| |
| if (hintUsed || matchedPairs.length === vocabularyPairs.length) return;
| |
|
| |
| const unmatchedPairs = vocabularyPairs.filter(pair => !matchedPairs.includes(pair.id));
| |
| if (unmatchedPairs.length === 0) return;
| |
|
| |
| const hintPair = unmatchedPairs[0];
| |
|
| |
| const termCards = document.querySelectorAll('.term-card');
| |
| const definitionCards = document.querySelectorAll('.definition-card');
| |
|
| |
| termCards.forEach(card => {
| |
| if (parseInt(card.dataset.id) === hintPair.id) {
| |
| card.classList.add('showing-hint');
| |
| setTimeout(() => card.classList.remove('showing-hint'), 2000);
| |
| }
| |
| });
| |
|
| |
| definitionCards.forEach(card => {
| |
| if (parseInt(card.dataset.id) === hintPair.id) {
| |
| card.classList.add('showing-hint');
| |
| setTimeout(() => card.classList.remove('showing-hint'), 2000);
| |
| }
| |
| });
| |
|
| |
| hintUsed = true;
| |
| document.getElementById('hint-btn').disabled = true;
| |
| }
| |
| | |
| function showSuccess() {
| |
| document.getElementById('success-message').style.display = 'block';
| |
| createConfetti();
| |
| }
| |
| | |
| function createConfetti() {
| |
| const colors = ['#fbbf24', '#60a5fa', '#34d399', '#a78bfa', '#f87171'];
| |
| for (let i = 0; i < 50; i++) {
| |
| setTimeout(() => {
| |
| const confetti = document.createElement('div');
| |
| confetti.className = 'confetti';
| |
| confetti.style.left = Math.random() * 100 + '%';
| |
| confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
| |
| confetti.style.animationDelay = Math.random() * 0.5 + 's';
| |
| document.body.appendChild(confetti);
| |
|
| |
| setTimeout(() => confetti.remove(), 3000);
| |
| }, i * 30);
| |
| }
| |
| }
| |
| | |
| document.getElementById('hint-btn').addEventListener('click', showHint);
| |
| document.getElementById('reset-btn').addEventListener('click', initGame);
| |
| | |
| const element = {
| |
| defaultConfig,
| |
|
| |
| onConfigChange: async (config) => {
| |
| const gameTitle = document.getElementById('game-title');
| |
| const hintBtnText = document.getElementById('hint-btn-text');
| |
| const successText = document.getElementById('success-text');
| |
| const body = document.body;
| |
| const container = document.querySelector('.container');
| |
| const buttons = document.querySelectorAll('button');
| |
| const heading1 = document.querySelector('h1');
| |
| const allText = document.querySelectorAll('p, .progress, h2, button, .term-card, .definition-card, .success-message');
| |
|
| |
| gameTitle.textContent = config.game_title || defaultConfig.game_title;
| |
| hintBtnText.textContent = config.hint_text || defaultConfig.hint_text;
| |
| successText.textContent = config.success_message || defaultConfig.success_message;
| |
|
| |
| const primaryColor = config.primary_color || defaultConfig.primary_color;
| |
| const secondaryColor = config.secondary_color || defaultConfig.secondary_color;
| |
| const textColor = config.text_color || defaultConfig.text_color;
| |
| const buttonColor = config.button_color || defaultConfig.button_color;
| |
| const successColor = config.success_color || defaultConfig.success_color;
| |
|
| |
| body.style.background = `linear-gradient(135deg, ${primaryColor} 0%, ${buttonColor} 100%)`;
| |
| heading1.style.background = `linear-gradient(135deg, ${primaryColor}, ${buttonColor})`;
| |
| heading1.style.webkitBackgroundClip = 'text';
| |
| heading1.style.webkitTextFillColor = 'transparent';
| |
| heading1.style.backgroundClip = 'text';
| |
|
| |
| document.querySelectorAll('.term-card:not(.matched)').forEach(card => {
| |
| card.style.background = `linear-gradient(135deg, ${secondaryColor} 0%, ${secondaryColor}dd 100%)`;
| |
| });
| |
|
| |
| document.querySelectorAll('.hint-btn').forEach(btn => {
| |
| btn.style.background = `linear-gradient(135deg, ${buttonColor}cc 0%, ${buttonColor} 100%)`;
| |
| });
| |
|
| |
| document.querySelector('.progress-fill').style.background = `linear-gradient(90deg, ${successColor}dd, ${successColor})`;
| |
|
| |
| const customFont = config.font_family || defaultConfig.font_family;
| |
| const baseFontStack = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
| |
| body.style.fontFamily = `${customFont}, ${baseFontStack}`;
| |
|
| |
| const baseSize = config.font_size || defaultConfig.font_size;
| |
| heading1.style.fontSize = `${baseSize * 2.5}px`;
| |
| document.querySelector('.instructions').style.fontSize = `${baseSize}px`;
| |
| document.querySelector('.progress').style.fontSize = `${baseSize * 1.125}px`;
| |
| document.querySelectorAll('h2').forEach(h2 => h2.style.fontSize = `${baseSize * 1.25}px`);
| |
| document.querySelectorAll('button').forEach(btn => btn.style.fontSize = `${baseSize}px`);
| |
| document.querySelectorAll('.term-card, .definition-card').forEach(card => card.style.fontSize = `${baseSize * 0.9375}px`);
| |
| document.querySelector('.success-message').style.fontSize = `${baseSize * 1.5}px`;
| |
| },
| |
|
| |
| mapToCapabilities: (config) => ({
| |
| recolorables: [
| |
| {
| |
| get: () => config.primary_color || defaultConfig.primary_color,
| |
| set: (value) => {
| |
| config.primary_color = value;
| |
| window.elementSdk.setConfig({ primary_color: value });
| |
| }
| |
| },
| |
| {
| |
| get: () => config.secondary_color || defaultConfig.secondary_color,
| |
| set: (value) => {
| |
| config.secondary_color = value;
| |
| window.elementSdk.setConfig({ secondary_color: value });
| |
| }
| |
| },
| |
| {
| |
| get: () => config.text_color || defaultConfig.text_color,
| |
| set: (value) => {
| |
| config.text_color = value;
| |
| window.elementSdk.setConfig({ text_color: value });
| |
| }
| |
| },
| |
| {
| |
| get: () => config.button_color || defaultConfig.button_color,
| |
| set: (value) => {
| |
| config.button_color = value;
| |
| window.elementSdk.setConfig({ button_color: value });
| |
| }
| |
| },
| |
| {
| |
| get: () => config.success_color || defaultConfig.success_color,
| |
| set: (value) => {
| |
| config.success_color = value;
| |
| window.elementSdk.setConfig({ success_color: value });
| |
| }
| |
| }
| |
| ],
| |
| borderables: [],
| |
| fontEditable: {
| |
| get: () => config.font_family || defaultConfig.font_family,
| |
| set: (value) => {
| |
| config.font_family = value;
| |
| window.elementSdk.setConfig({ font_family: value });
| |
| }
| |
| },
| |
| fontSizeable: {
| |
| get: () => config.font_size || defaultConfig.font_size,
| |
| set: (value) => {
| |
| config.font_size = value;
| |
| window.elementSdk.setConfig({ font_size: value });
| |
| }
| |
| }
| |
| }),
| |
|
| |
| mapToEditPanelValues: (config) => new Map([
| |
| ["game_title", config.game_title || defaultConfig.game_title],
| |
| ["hint_text", config.hint_text || defaultConfig.hint_text],
| |
| ["success_message", config.success_message || defaultConfig.success_message]
| |
| ])
| |
| };
| |
| | |
| if (window.elementSdk) {
| |
| window.elementSdk.init(element);
| |
| }
| |
| | |
| initGame();
| |
| </script>
| |
| <script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9a1a873237d4bc58',t:'MTc2MzY2ODc3MC4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
| |
| </html>
| |