Home / IELTS / Writing / IELTS Writing Mock Test

IELTS Writing Mock Test

IELTS Writing Mock Test tailwind.config = { theme: { extend: { colors: { primary: '#0078FF', secondary: '#F0F2F5', dark: '#181818', } } }, darkMode: 'class' } @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); body { font-family: 'Inter', sans-serif; } .test-content::-webkit-scrollbar { width: 8px; } .test-content::-webkit-scrollbar-track { background: rgba(0,0,0,0.05); border-radius: 8px; } .test-content::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); border-radius: 8px; } .test-content::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.2); } .response-textarea { resize: vertical; min-height: 200px; border: 1px solid #e2e8f0; border-radius: 0.375rem; padding: 0.75rem; width: 100%; font-size: 1rem; line-height: 1.5; } .response-textarea:focus { outline: none; border-color: #0078FF; box-shadow: 0 0 0 3px rgba(0, 120, 255, 0.2); } .dark .response-textarea { background-color: #2d3748; color: #e2e8f0; border-color: #4a5568; } .dark .response-textarea:focus { border-color: #0078FF; box-shadow: 0 0 0 3px rgba(0, 120, 255, 0.2); } .evaluation-container { max-height: 0; overflow: hidden; transition: max-height 0.5s ease-out; } .evaluation-container.show { max-height: 2000px; } #testSelector { margin: 0px; padding: 15px; padding-right: 50px; background-color: white; color: #0078FF; border: none; font-size: 20px; border-radius: 200px; cursor: pointer; outline: none; } .dark #testSelector { background-color: #2d3748; color: #e2e8f0; } .chart-container { max-width: 100%; overflow-x: auto; } /* Word count badge styles */ .word-count { position: absolute; bottom: 10px; right: 10px; background-color: rgba(0, 120, 255, 0.1); color: #0078FF; padding: 2px 8px; border-radius: 12px; font-size: 12px; pointer-events: none; } .dark .word-count { background-color: rgba(0, 120, 255, 0.2); color: #a4caff; } /* Textarea container for positioning word count */ .textarea-container { position: relative; } /* Loading spinner */ .spinner { border: 3px solid rgba(0, 120, 255, 0.1); border-radius: 50%; border-top: 3px solid #0078FF; width: 24px; height: 24px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
IELTS Writing Mock Test
Set 1: Environment & Resources Set 2: Education & Technology Set 3: Health & Lifestyle Set 4: Society & Culture Set 5: Work & Career Set 6: Cities & Architecture Set 7: Economics & Business Set 8: Crime & Justice Set 9: Media & Communication Set 10: Science & Research
60:00
// Initialize dark mode if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { if (event.matches) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }); // Test data with 10 sets of IELTS Writing tasks const testQuestionSets = [ // Set 1: Environment & Resources { name: "Environment & Resources", tasks: [ { type: "Task 1", instruction: "The graph below shows global carbon dioxide emissions from 1970 to 2020 by region. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "lineChart", chartData: { title: "Global COâ‚‚ Emissions by Region (1970-2020)", xAxis: ["1970", "1980", "1990", "2000", "2010", "2020"], yAxisTitle: "COâ‚‚ Emissions (Billion Tonnes)", series: [ {name: "North America", data: [6.0, 6.5, 6.8, 7.5, 7.0, 5.8], color: "#1f77b4"}, {name: "Europe", data: [5.7, 5.5, 5.1, 5.0, 4.8, 4.0], color: "#ff7f0e"}, {name: "Asia", data: [2.1, 3.2, 5.0, 7.0, 11.2, 14.5], color: "#2ca02c"}, {name: "Africa", data: [0.5, 0.8, 1.0, 1.2, 1.5, 1.8], color: "#d62728"}, {name: "South America", data: [0.4, 0.7, 0.9, 1.3, 1.6, 1.7], color: "#9467bd"} ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that protecting the environment should be the top priority for governments, even if it means slower economic development. Others argue that economic growth should be prioritized. Discuss both these views and give your own opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 2: Education & Technology { name: "Education & Technology", tasks: [ { type: "Task 1", instruction: "The table below shows the percentage of university students who used different types of digital learning resources in 2010 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "table", chartData: { title: "University Students' Use of Digital Learning Resources (%)", headers: ["Resource Type", "2010", "2020", "Change"], rows: [ ["Digital textbooks", "32%", "78%", "+46%"], ["Online video lectures", "24%", "89%", "+65%"], ["Mobile learning apps", "11%", "73%", "+62%"], ["Online discussion forums", "38%", "67%", "+29%"], ["Virtual simulations", "14%", "51%", "+37%"], ["AI-based tutoring tools", "5%", "42%", "+37%"] ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "In many countries, online education is becoming increasingly popular. Some people believe traditional classroom learning will eventually be replaced by online learning. To what extent do you agree or disagree with this view?", wordCount: 250, timeLimit: 40 } ] }, // Set 3: Health & Lifestyle { name: "Health & Lifestyle", tasks: [ { type: "Task 1", instruction: "The diagrams below show the process of how yoga practice affects the human body and mind. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "processChart", chartData: { title: "How Yoga Practice Affects Body and Mind", steps: [ { title: "Initial Practice", description: "Regular breathing exercises and physical poses", effects: ["Increased oxygen intake", "Improved flexibility", "Enhanced focus"] }, { title: "Short-term Effects", description: "After 2-4 weeks of regular practice", effects: ["Reduced stress hormones", "Better posture", "Improved sleep quality"] }, { title: "Medium-term Effects", description: "After 2-6 months of practice", effects: ["Lower blood pressure", "Increased muscle strength", "Better concentration"] }, { title: "Long-term Effects", description: "After 1+ year of consistent practice", effects: ["Structural improvements in brain", "Enhanced immune function", "Improved emotional regulation"] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that the government should be responsible for improving public health, while others think individuals should take care of their own health. Discuss both views and give your opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 4: Society & Culture { name: "Society & Culture", tasks: [ { type: "Task 1", instruction: "The charts below show the average time spent on different leisure activities by people in a European country in 1990 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "barChart", chartData: { title: "Average Weekly Hours Spent on Leisure Activities", categories: ["Watching TV", "Social media", "Reading", "Exercise", "Socializing", "Hobbies"], series: [ {name: "1990", data: [18, 0, 7, 4, 8, 6], color: "#5470c6"}, {name: "2020", data: [12, 14, 3, 5, 5, 4], color: "#91cc75"} ], yAxisTitle: "Hours per week" }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "In many countries, traditional cultural practices are being lost as people adopt more globalized lifestyles. Is this a positive or negative development?", wordCount: 250, timeLimit: 40 } ] }, // Set 5: Work & Career { name: "Work & Career", tasks: [ { type: "Task 1", instruction: "The pie charts below show the percentages of different job types in a developed country in 2000 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "pieChart", chartData: { title: "Distribution of Job Types (2000 vs 2020)", charts: [ { title: "2000", data: [ {name: "Manufacturing", value: 28, color: "#5470c6"}, {name: "Services", value: 42, color: "#91cc75"}, {name: "Technology", value: 12, color: "#fac858"}, {name: "Agriculture", value: 8, color: "#ee6666"}, {name: "Other", value: 10, color: "#73c0de"} ] }, { title: "2020", data: [ {name: "Manufacturing", value: 15, color: "#5470c6"}, {name: "Services", value: 48, color: "#91cc75"}, {name: "Technology", value: 26, color: "#fac858"}, {name: "Agriculture", value: 4, color: "#ee6666"}, {name: "Other", value: 7, color: "#73c0de"} ] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that having a job with a high salary is more important than job satisfaction. To what extent do you agree or disagree?", wordCount: 250, timeLimit: 40 } ] }, // Set 6: Cities & Architecture { name: "Cities & Architecture", tasks: [ { type: "Task 1", instruction: "The maps below show the development of a small town between 1980 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "map", chartData: { title: "Town Development: 1980 vs 2020", description: "The maps show the transformation of Riverdale town over a 40-year period, with significant expansion of urban areas, new transportation infrastructure, and conversion of agricultural land to residential and commercial use." }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "As cities continue to grow, many governments are struggling to provide adequate housing for their populations. What are the causes of urban housing problems and what solutions could be implemented?", wordCount: 250, timeLimit: 40 } ] }, // Set 7: Economics & Business { name: "Economics & Business", tasks: [ { type: "Task 1", instruction: "The chart below shows the percentage of small businesses that failed within five years of starting, across different industries in a developed economy. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "horizontalBarChart", chartData: { title: "Small Business Failure Rates by Industry (Within 5 Years)", categories: ["Restaurants", "Retail", "Construction", "Technology", "Healthcare", "Professional Services", "Manufacturing"], data: [60, 53, 48, 63, 38, 41, 49], xAxisTitle: "Failure Rate (%)" }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that governments should provide financial support to artists such as painters, musicians and poets. Others believe that artists should be funded by alternative sources. Discuss both views and give your opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 8: Crime & Justice { name: "Crime & Justice", tasks: [ { type: "Task 1", instruction: "The graphs below show the number of reported crimes per 100,000 people in a developed country from 2000 to 2020, divided into different categories. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "lineChart", chartData: { title: "Crime Rates per 100,000 Population (2000-2020)", xAxis: ["2000", "2005", "2010", "2015", "2020"], yAxisTitle: "Number of crimes per 100,000 population", series: [ {name: "Violent crimes", data: [450, 425, 410, 380, 360], color: "#c23531"}, {name: "Property crimes", data: [3200, 2800, 2400, 2100, 1800], color: "#2f4554"}, {name: "Fraud", data: [200, 280, 420, 650, 820], color: "#61a0a8"}, {name: "Cyber crimes", data: [50, 120, 280, 560, 910], color: "#d48265"} ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that the best way to reduce crime is to give longer prison sentences. Others, however, believe there are better alternative ways of reducing crime. Discuss both these views and give your own opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 9: Media & Communication { name: "Media & Communication", tasks: [ { type: "Task 1", instruction: "The diagrams below show how people received news in 1990 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "pieChart", chartData: { title: "News Consumption Sources (1990 vs 2020)", charts: [ { title: "1990", data: [ {name: "Television", value: 45, color: "#5470c6"}, {name: "Newspapers", value: 35, color: "#91cc75"}, {name: "Radio", value: 18, color: "#fac858"}, {name: "Internet", value: 2, color: "#ee6666"} ] }, { title: "2020", data: [ {name: "Television", value: 26, color: "#5470c6"}, {name: "Newspapers", value: 8, color: "#91cc75"}, {name: "Radio", value: 6, color: "#fac858"}, {name: "Internet", value: 45, color: "#ee6666"}, {name: "Social Media", value: 15, color: "#73c0de"} ] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "The Internet has transformed the way information is shared and consumed. While some argue this has improved the world, others feel these changes have had negative consequences. Discuss both views and give your opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 10: Science & Research { name: "Science & Research", tasks: [ { type: "Task 1", instruction: "The diagram below shows the process of how a new pharmaceutical drug is developed and approved. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "processChart", chartData: { title: "Pharmaceutical Drug Development Process", steps: [ { title: "Discovery & Research", description: "3-6 years", effects: ["Identify target molecules", "Develop candidate compounds", "Test in laboratory"] }, { title: "Preclinical Testing", description: "1-3 years", effects: ["Laboratory tests", "Animal testing", "Safety assessment"] }, { title: "Clinical Trials", description: "6-7 years", effects: ["Phase I: 20-100 healthy volunteers", "Phase II: 100-500 patient volunteers", "Phase III: 1,000-5,000 patient volunteers"] }, { title: "FDA Review & Approval", description: "1-2 years", effects: ["New Drug Application (NDA) review", "Advisory committee review", "FDA decision"] }, { title: "Post-Marketing Surveillance", description: "Ongoing", effects: ["Monitor for side effects", "Additional studies", "Potential label changes"] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Scientific research should be carried out and controlled by governments rather than private companies. To what extent do you agree or disagree with this opinion?", wordCount: 250, timeLimit: 40 } ] } ]; let currentSetIndex = 0; let currentTaskIndex = 0; let timerInterval; let timeRemaining = 3600; // 60 minutes in seconds let testInProgress = false; let responses = { task1: "", task2: "" }; // DOM Elements const testContent = document.getElementById('testContent'); const startButton = document.getElementById('startButton'); const endButton = document.getElementById('endButton'); const timerDisplay = document.getElementById('timerDisplay'); const testSelector = document.getElementById('testSelector'); // Update question set when selector changes testSelector.addEventListener('change', function() { currentSetIndex = parseInt(this.value); resetTest(); displayWelcomeMessage(); }); // Format time for display (mm:ss) function formatTime(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`; } // Update timer display function updateTimer() { if (timeRemaining <= 0) { clearInterval(timerInterval); endTest(true); return; } timeRemaining--; timerDisplay.textContent = formatTime(timeRemaining); // Visual indicator when time is running low if (timeRemaining < 300) { // Less than 5 minutes timerDisplay.classList.add('text-red-600', 'dark:text-red-400'); if (timeRemaining % 60 === 0) { // Every minute in last 5 minutes showTimeWarning(timeRemaining / 60); } } } // Show time warning function showTimeWarning(minutes) { const warningMsg = document.createElement('div'); warningMsg.className = 'fixed top-4 right-4 bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 px-4 py-2 rounded-lg shadow-lg transition-all duration-500 opacity-0'; warningMsg.innerHTML = `Time Alert: ${minutes} minute${minutes > 1 ? 's' : ''} remaining!`; document.body.appendChild(warningMsg); // Fade in setTimeout(() => { warningMsg.classList.replace('opacity-0', 'opacity-100'); }, 100); // Fade out and remove setTimeout(() => { warningMsg.classList.replace('opacity-100', 'opacity-0'); setTimeout(() => { document.body.removeChild(warningMsg); }, 500); }, 5000); } // Reset test to beginning function resetTest() { currentTaskIndex = 0; testInProgress = false; timeRemaining = 3600; // Reset to 60 minutes timerDisplay.textContent = formatTime(timeRemaining); timerDisplay.classList.remove('text-red-600', 'dark:text-red-400'); if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } responses = { task1: "", task2: "" }; startButton.textContent = 'Start'; startButton.classList.remove('bg-red-500', 'hover:bg-red-600'); startButton.classList.add('bg-primary', 'hover:bg-blue-600'); } // Start test function startTest() { testInProgress = true; startButton.textContent = 'Pause'; // Initialize timer if (!timerInterval) { timerInterval = setInterval(updateTimer, 1000); } // Display tasks displayTasks(); } // Pause test function pauseTest() { testInProgress = false; startButton.textContent = 'Resume'; if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } } // End test function endTest(timeExpired = false) { testInProgress = false; if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } // Save any unsaved responses const task1Textarea = document.getElementById('task1-response'); const task2Textarea = document.getElementById('task2-response'); if (task1Textarea) { responses.task1 = task1Textarea.value; } if (task2Textarea) { responses.task2 = task2Textarea.value; } // Show completed message and results let completionMessage = timeExpired ? 'Time\'s up! Your test has been submitted.' : 'You\'ve completed the test!'; testContent.innerHTML = `

${completionMessage}

You can view your responses below and get AI evaluation of your writing.

Task 1 Response

${responses.task1 ? formatResponseText(responses.task1) : 'No response provided'}

Task 2 Response

${responses.task2 ? formatResponseText(responses.task2) : 'No response provided'}
`; // Add event listeners to evaluation buttons document.querySelectorAll('.evaluate-button').forEach(button => { button.addEventListener('click', function() { const task = this.getAttribute('data-task'); const responseText = responses[task]; if (responseText.trim() === '') { alert('No response to evaluate.'); return; } evaluateWriting(task, responseText); }); }); // Add event listeners to rewrite buttons document.querySelectorAll('.rewrite-button').forEach(button => { button.addEventListener('click', function() { const task = this.getAttribute('data-task'); const responseText = responses[task]; if (responseText.trim() === '') { alert('No response to rewrite.'); return; } rewriteResponse(task, responseText); }); }); // Add event listener to new test button document.getElementById('newTestButton').addEventListener('click', function() { resetTest(); displayWelcomeMessage(); }); // Reset button state startButton.textContent = 'Start'; startButton.classList.remove('bg-red-500', 'hover:bg-red-600'); startButton.classList.add('bg-primary', 'hover:bg-blue-600'); } // Format response text for display (preserving paragraphs) function formatResponseText(text) { if (!text) return 'No response provided'; // Replace newlines with paragraph tags return text.split('\n') .filter(para => para.trim() !== '') .map(para => `

${para}

`) .join(''); } // Display writing tasks function displayTasks() { const currentSet = testQuestionSets[currentSetIndex]; const task1 = currentSet.tasks[0]; const task2 = currentSet.tasks[1]; let task1ChartHTML = ''; // Generate appropriate chart/diagram based on type switch (task1.chartType) { case 'lineChart': task1ChartHTML = generateLineChart(task1.chartData); break; case 'barChart': task1ChartHTML = generateBarChart(task1.chartData); break; case 'horizontalBarChart': task1ChartHTML = generateHorizontalBarChart(task1.chartData); break; case 'pieChart': task1ChartHTML = generatePieChart(task1.chartData); break; case 'table': task1ChartHTML = generateTable(task1.chartData); break; case 'processChart': task1ChartHTML = generateProcessChart(task1.chartData); break; case 'map': task1ChartHTML = generateMap(task1.chartData); break; default: task1ChartHTML = '

Chart type not supported

'; } testContent.innerHTML = `

Writing Task 1

Spend about 20 minutes on this task. Write at least 150 words.

${task1.instruction}

${task1ChartHTML}
0 words

Writing Task 2

Spend about 40 minutes on this task. Write at least 250 words.

${task2.instruction}

0 words
`; // Add word count functionality to task 1 textarea const task1Textarea = document.getElementById('task1-response'); const wordCountTask1 = document.getElementById('word-count-task1'); updateWordCount(task1Textarea, wordCountTask1); task1Textarea.addEventListener('input', function() { updateWordCount(this, wordCountTask1); // Save response as user types responses.task1 = this.value; }); // Add word count functionality to task 2 textarea const task2Textarea = document.getElementById('task2-response'); const wordCountTask2 = document.getElementById('word-count-task2'); updateWordCount(task2Textarea, wordCountTask2); task2Textarea.addEventListener('input', function() { updateWordCount(this, wordCountTask2); // Save response as user types responses.task2 = this.value; }); } // Update word count function updateWordCount(textarea, wordCountElement) { const text = textarea.value.trim(); const wordCount = text ? text.split(/\s+/).length : 0; wordCountElement.textContent = `${wordCount} words`; // Update color based on word count requirement const minWords = textarea.id.includes('task1') ? 150 : 250; if (wordCount s.data)) * 1.1; const yScale = (height - padding * 2) / maxValue; let svg = ` ${data.title} ${data.yAxisTitle} ${data.xAxis.map((label, i) => ` ${label} `).join('')} ${Array.from({length: 6}, (_, i) => { const value = Math.round(maxValue * i / 5); return ` ${value} `; }).join('')} ${Array.from({length: 6}, (_, i) => { const value = Math.round(maxValue * i / 5); return ` `; }).join('')} ${data.series.map((series, seriesIndex) => { // Line const points = series.data.map((value, i) => `${padding + i * xScale},${height-padding-value*yScale}`).join(' '); return ` ${series.data.map((value, i) => ` `).join('')} `; }).join('')} ${data.series.map((series, i) => ` ${series.name} `).join('')} `; return svg; } function generateBarChart(data) { const width = 600; const height = 400; const padding = 60; const barWidth = (width - padding * 2) / (data.categories.length * (data.series.length + 1)); const maxValue = Math.max(...data.series.flatMap(s => s.data)) * 1.1; const yScale = (height - padding * 2) / maxValue; let svg = ` ${data.title} ${data.yAxisTitle} ${data.categories.map((label, i) => ` ${label} `).join('')} ${Array.from({length: 6}, (_, i) => { const value = Math.round(maxValue * i / 5); return ` ${value} `; }).join('')} ${Array.from({length: 6}, (_, i) => { const value = Math.round(maxValue * i / 5); return ` `; }).join('')} ${data.series.map((series, seriesIndex) => series.data.map((value, i) => ` `).join('') ).join('')} ${data.series.map((series, i) => ` ${series.name} `).join('')} `; return svg; } function generateHorizontalBarChart(data) { const width = 600; const height = 400; const padding = 60; const barHeight = (height - padding * 2) / data.categories.length; const maxValue = Math.max(...data.data) * 1.1; const xScale = (width - padding * 2) / maxValue; let svg = ` ${data.title} ${data.xAxisTitle} ${data.categories.map((label, i) => ` ${label} `).join('')} ${Array.from({length: 6}, (_, i) => { const value = Math.round(maxValue * i / 5); return ` ${value}% `; }).join('')} ${Array.from({length: 6}, (_, i) => { const value = Math.round(maxValue * i / 5); return ` `; }).join('')} ${data.data.map((value, i) => ` ${value}% `).join('')} `; return svg; } function generatePieChart(data) { const width = 300; const height = 300; const radius = 100; const centerX = width / 2; const centerY = height / 2; // Multiple pie charts if (data.charts) { let svg = `

${data.title}

`; data.charts.forEach(chart => { let startAngle = 0; let paths = ''; let legends = ''; chart.data.forEach((slice, i) => { const slicePercentage = slice.value / chart.data.reduce((sum, s) => sum + s.value, 0); const endAngle = startAngle + slicePercentage * 2 * Math.PI; const startX = centerX + radius * Math.cos(startAngle); const startY = centerY + radius * Math.sin(startAngle); const endX = centerX + radius * Math.cos(endAngle); const endY = centerY + radius * Math.sin(endAngle); const largeArcFlag = slicePercentage > 0.5 ? 1 : 0; paths += ` `; // Add label at the midpoint of the arc const midAngle = startAngle + (endAngle - startAngle) / 2; const labelRadius = radius * 0.7; // Position label at 70% of radius const labelX = centerX + labelRadius * Math.cos(midAngle); const labelY = centerY + labelRadius * Math.sin(midAngle); if (slicePercentage > 0.05) { // Only add label if slice is big enough paths += ` ${slice.value}% `; } // Add to legend legends += ` ${slice.name} (${slice.value}%) `; startAngle = endAngle; }); svg += `

${chart.title}

${paths} ${legends}
`; }); svg += `
`; return svg; } // Single pie chart let startAngle = 0; let paths = ''; let legends = ''; data.slices.forEach((slice, i) => { const slicePercentage = slice.value / data.slices.reduce((sum, s) => sum + s.value, 0); const endAngle = startAngle + slicePercentage * 2 * Math.PI; // Remaining code similar to multiple charts scenario // ... }); return ` ${data.title} ${paths} ${legends} `; } function generateTable(data) { return `
${data.headers.map(header => ` `).join('')} ${data.rows.map((row, i) => ` ${row.map((cell, j) => ` `).join('')} `).join('')}
${data.title}
${header}
${cell}
`; } function generateProcessChart(data) { return `

${data.title}

${data.steps.map((step, i) => `
${i + 1}

${step.title}

${step.description}

    ${step.effects.map(effect => `
  • ${effect}
  • `).join('')}
${i < data.steps.length - 1 ? `
` : ''}
`).join('')}
`; } function generateMap(data) { // Since we can't include external images, let's create a schematic map representation using SVG return `

${data.title}

1980

Town Center Town Center Residential Agricultural

2020

Town Center Town Center Residential Industrial Commercial Agricultural Park

${data.description}

`; } // Evaluate writing response using AI async function evaluateWriting(taskType, responseText) { const evaluationContainer = document.getElementById(`${taskType}-evaluation`); // Add a loading state and show container evaluationContainer.innerHTML = `

Evaluating your writing...

`; evaluationContainer.classList.add('show'); // Get task information const currentSet = testQuestionSets[currentSetIndex]; const task = taskType === 'task1' ? currentSet.tasks[0] : currentSet.tasks[1]; // Prepare prompt for AI const systemPrompt = ` You are an IELTS writing examiner with extensive experience. Evaluate the candidate's response for an IELTS ${task.type}. The task was: "${task.instruction}" Provide band scores (0-9, can use half bands like 6.5) and detailed comments for each of these criteria: 1. Task Achievement/Response: How well the candidate addresses all parts of the task with a fully developed position. 2. Coherence and Cohesion: How well the information and ideas are organized, using paragraphs and cohesive devices. 3. Lexical Resource: The range and precision of vocabulary used. 4. Grammatical Range and Accuracy: The range and accuracy of grammar used. Then provide an overall band score and 2-3 sentences of constructive feedback on how to improve. Only return a JSON object with this structure: { "taskAchievement": {"score": 0.0, "comment": ""}, "coherence": {"score": 0.0, "comment": ""}, "lexical": {"score": 0.0, "comment": ""}, "grammar": {"score": 0.0, "comment": ""}, "overall": 0.0, "feedback": "" } `; try { // Use direct API call to OpenRouter const response = await fetch("https://openrouter.ai/api/v1/chat/completions", { method: "POST", headers: { "Authorization": "Bearer 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", "HTTP-Referer": window.location.href, "X-Title": "IELTS Writing Mock Test", "Content-Type": "application/json" }, body: JSON.stringify({ "model": "qwen/qwen-2-7b-instruct:free", "messages": [ { role: "system", content: systemPrompt }, { role: "user", content: `Candidate's response for IELTS ${task.type}: ${responseText}` } ] }) }); const data = await response.json(); if (!data || !data.choices || !data.choices[0] || !data.choices[0].message) { throw new Error("Invalid response from API"); } const content = data.choices[0].message.content; try { // Try to parse the response as JSON let evaluation; // Extract JSON from message content const jsonMatch = content.match(/{[\s\S]*}/); if (jsonMatch) { evaluation = JSON.parse(jsonMatch[0]); } else { throw new Error("Could not parse evaluation JSON from response"); } displayEvaluation(evaluation, evaluationContainer); } catch (error) { console.error('Error parsing evaluation:', error); evaluationContainer.innerHTML = `

Error parsing evaluation. Please try again.

`; } } catch (error) { console.error('Error evaluating writing:', error); evaluationContainer.innerHTML = `

Error evaluating your writing: ${error.message || "Unknown error"}. Please try again.

`; } } // Display evaluation results function displayEvaluation(evaluation, container) { // Map scores to colors function getScoreColor(score) { if (score >= 8) return 'text-green-600 dark:text-green-400'; if (score >= 6.5) return 'text-blue-600 dark:text-blue-400'; if (score >= 5) return 'text-yellow-600 dark:text-yellow-400'; return 'text-red-600 dark:text-red-400'; } container.innerHTML = `

IELTS Writing Evaluation

Task Achievement

${evaluation.taskAchievement.score}

${evaluation.taskAchievement.comment}

Coherence & Cohesion

${evaluation.coherence.score}

${evaluation.coherence.comment}

Lexical Resource

${evaluation.lexical.score}

${evaluation.lexical.comment}

Grammatical Range & Accuracy

${evaluation.grammar.score}

${evaluation.grammar.comment}

Overall Band Score

${evaluation.overall}

${evaluation.feedback}

`; } // Rewrite response using AI async function rewriteResponse(taskType, responseText) { const evaluationContainer = document.getElementById(`${taskType}-evaluation`); // Add a loading state and show container evaluationContainer.innerHTML = `

Improving your writing...

`; evaluationContainer.classList.add('show'); // Get task information const currentSet = testQuestionSets[currentSetIndex]; const task = taskType === 'task1' ? currentSet.tasks[0] : currentSet.tasks[1]; try { // Prepare prompt for AI const systemPrompt = ` You are an IELTS writing expert who helps candidates improve their writing. Please improve the following IELTS ${task.type} response, making it more coherent, sophisticated, and accurate. Ensure it fully addresses the task: "${task.instruction}". Keep the same core ideas but enhance the vocabulary, grammar, and organization to achieve a band 8+ level. Only return the improved response directly, with no explanations or comments. `; // Use direct API call to OpenRouter const response = await fetch("https://openrouter.ai/api/v1/chat/completions", { method: "POST", headers: { "Authorization": "Bearer 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", "HTTP-Referer": window.location.href, "X-Title": "IELTS Writing Mock Test", "Content-Type": "application/json" }, body: JSON.stringify({ "model": "qwen/qwen-2-7b-instruct:free", "messages": [ { role: "system", content: systemPrompt }, { role: "user", content: responseText } ] }) }); const data = await response.json(); if (!data || !data.choices || !data.choices[0] || !data.choices[0].message) { throw new Error("Invalid response from API"); } const improvedText = data.choices[0].message.content; evaluationContainer.innerHTML = `

Improved Version

${formatResponseText(improvedText)}
`; // Add event listeners to buttons evaluationContainer.querySelector('.use-improved-version').addEventListener('click', function() { responses[taskType] = improvedText; evaluationContainer.innerHTML = `

The improved version has been saved as your response.

`; // Update the response display const responseDisplay = document.querySelector(`.bg-white.dark\\:bg-gray-800:nth-of-type(${taskType === 'task1' ? 1 : 2}) .bg-gray-50.dark\\:bg-gray-700`); if (responseDisplay) { responseDisplay.innerHTML = formatResponseText(improvedText); } // After a delay, hide the message setTimeout(() => { evaluationContainer.classList.remove('show'); }, 3000); }); evaluationContainer.querySelector('.close-improved-version').addEventListener('click', function() { evaluationContainer.classList.remove('show'); }); } catch (error) { console.error('Error rewriting response:', error); evaluationContainer.innerHTML = `

Error rewriting your response: ${error.message || "Unknown error"}. Please try again.

`; } } // Display welcome message function displayWelcomeMessage() { testContent.innerHTML = `

You've selected: ${testQuestionSets[currentSetIndex].name}

This test contains two writing tasks:

  • Task 1: Analyzing visual information (20 minutes, 150+ words)
  • Task 2: Essay writing (40 minutes, 250+ words)

You will have 60 minutes to complete both tasks. Your time will begin when you click "Start".

Test Tips:

  • Read the instructions carefully
  • Plan your response before writing
  • Aim for at least 150 words for Task 1 and 250 words for Task 2
  • Leave a few minutes at the end to review your work
  • You can end the test early by clicking the "End" button
  • After completing the test, you can get AI feedback on your writing

Click "Start" when you're ready to begin the test.

`; } // Event listeners startButton.addEventListener('click', function() { if (testInProgress) { pauseTest(); } else { startTest(); } }); endButton.addEventListener('click', function() { if (confirm('Are you sure you want to end the test? This will submit your current responses.')) { endTest(); } }); // Initialize with welcome message window.addEventListener('load', function() { displayWelcomeMessage(); });

Leave a Reply

Your email address will not be published. Required fields are marked *