|
7 | 7 | <meta name="viewport" content="width=device-width,initial-scale=1.0"> |
8 | 8 | <script src="https://cdn.tailwindcss.com"></script> |
9 | 9 | <script src="https://cdnjs.cloudflare.com/ajax/libs/json5/2.2.3/index.min.js"></script> |
10 | | - <title>Grammar Checker</title> |
| 10 | + <title>GrammarPro</title> |
11 | 11 | <style>.error-highlight { |
12 | 12 | position: relative; |
13 | 13 | display: inline-block; |
|
82 | 82 | 20%, 40%, 60%, 80% { |
83 | 83 | transform: translateX(5px); |
84 | 84 | } |
| 85 | + } |
| 86 | + |
| 87 | + .fade-in { |
| 88 | + animation: fadeIn 0.3s ease-in; |
| 89 | + } |
| 90 | + |
| 91 | + @keyframes fadeIn { |
| 92 | + from { |
| 93 | + opacity: 0; |
| 94 | + } |
| 95 | + to { |
| 96 | + opacity: 1; |
| 97 | + } |
85 | 98 | }</style> |
86 | 99 | </head> |
87 | | -<body class="bg-gray-100 flex justify-center items-center min-h-screen"> |
88 | | -<div class="container mx-auto max-w-4xl p-5 bg-white rounded-lg shadow-lg"> |
89 | | - <div class="flex items-center justify-between mb-6"><h1 class="text-3xl font-bold">Grammar Checker</h1> |
90 | | - <div class="flex gap-2"><select id="mode-select" class="px-3 py-1 border rounded-md text-sm dark:text-gray-900"> |
| 100 | +<body class="bg-gray-100 dark:bg-gray-900 transition-colors duration-200"> |
| 101 | +<div class="container mx-auto max-w-4xl p-5 bg-white dark:bg-gray-800 rounded-lg shadow-lg transition-colors duration-200"> |
| 102 | + <div class="flex items-center justify-between mb-6"> |
| 103 | + <div class="flex items-center gap-2"><h1 |
| 104 | + class="text-3xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 text-transparent bg-clip-text dark:from-indigo-400 dark:to-purple-400"> |
| 105 | + GrammarPro</h1><span |
| 106 | + class="px-2 py-1 text-xs font-medium bg-indigo-100 text-indigo-800 rounded-full dark:bg-indigo-900 dark:text-indigo-200">Beta</span> |
| 107 | + </div> |
| 108 | + <select id="mode-select" |
| 109 | + class="px-3 py-1 border rounded-md text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-400 transition-colors duration-200"> |
91 | 110 | <option value="standard">Standard Mode</option> |
92 | 111 | <option value="academic">Academic Writing</option> |
93 | 112 | <option value="business">Business Writing</option> |
94 | 113 | <option value="casual">Casual Writing</option> |
95 | | - </select> |
96 | | - <button id="theme-toggle" class="p-2 rounded-md bg-gray-200 hover:bg-gray-300">🌙</button> |
97 | | - </div> |
98 | | - </div> |
99 | | - <div class="mb-4"> |
100 | | - <div class="flex justify-between items-center mb-2"><label for="input" |
101 | | - class="block text-sm font-medium text-gray-700 dark:text-gray-300">Enter |
| 114 | + </select></div> |
| 115 | + <div class="mb-4 space-y-2"> |
| 116 | + <div class="flex justify-between items-center"><label for="input" |
| 117 | + class="block text-sm font-medium text-gray-700 dark:text-gray-300">Enter |
102 | 118 | Text:</label> |
103 | | - <div id="word-count" class="text-sm text-gray-500 dark:text-gray-400">0 words | 0 characters</div> |
| 119 | + <div id="word-count" class="text-sm text-gray-500 dark:text-gray-400 font-mono">0 words | 0 characters</div> |
104 | 120 | </div> |
105 | 121 | <textarea id="input" rows="6" |
106 | | - class="w-full p-3 border rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:text-gray-900" |
107 | | - placeholder="Type or paste your text here..."></textarea></div> |
108 | | - <div class="flex flex-wrap gap-4 mb-6"> |
109 | | - <button id="check-grammar" |
110 | | - class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500"> |
111 | | - Check Grammar |
112 | | - </button> |
113 | | - <button id="clear-text" |
114 | | - class="px-6 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500"> |
115 | | - Clear Text |
116 | | - </button> |
117 | | - <button id="copy-corrected" |
118 | | - class="px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500"> |
119 | | - Copy Corrected |
120 | | - </button> |
| 122 | + class="w-full p-3 border rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 border-gray-300 dark:border-gray-600 transition-colors duration-200 resize-y" |
| 123 | + placeholder="Type or paste your text here..."></textarea> |
| 124 | + <div class="flex flex-wrap gap-3"> |
| 125 | + <button id="check-grammar" |
| 126 | + class="px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 text-white rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-all duration-200 flex items-center gap-2"> |
| 127 | + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 128 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| 129 | + d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path> |
| 130 | + </svg> |
| 131 | + Check Grammar |
| 132 | + </button> |
| 133 | + <button id="clear-text" |
| 134 | + class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md focus:outline-none focus:ring-2 focus:ring-gray-500 transition-colors duration-200 flex items-center gap-2"> |
| 135 | + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 136 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| 137 | + d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path> |
| 138 | + </svg> |
| 139 | + Clear |
| 140 | + </button> |
| 141 | + <button id="copy-corrected" |
| 142 | + class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 transition-colors duration-200 flex items-center gap-2"> |
| 143 | + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 144 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| 145 | + d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path> |
| 146 | + </svg> |
| 147 | + Copy |
| 148 | + </button> |
| 149 | + </div> |
121 | 150 | </div> |
122 | | - <div id="result" class="p-4 border rounded-md min-h-[100px] bg-gray-50 dark:bg-gray-800 dark:text-white"></div> |
123 | | - <div id="history" class="mt-4"><h3 class="text-lg font-semibold mb-2 dark:text-white">Recent Corrections</h3> |
| 151 | + <div id="result" |
| 152 | + class="p-4 border rounded-md min-h-[100px] bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100 border-gray-300 dark:border-gray-600 transition-colors duration-200"></div> |
| 153 | + <div id="history" class="mt-4 space-y-2"> |
| 154 | + <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2"> |
| 155 | + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 156 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
| 157 | + d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> |
| 158 | + </svg> |
| 159 | + Recent Corrections |
| 160 | + </h3> |
124 | 161 | <div id="history-list" class="space-y-2"></div> |
125 | 162 | </div> |
126 | 163 | </div> |
127 | | -<script>let darkMode = false; |
128 | | -let errorHistory = []; |
| 164 | +<script>let errorHistory = []; |
129 | 165 | let currentText = ''; |
130 | 166 | let lastCheckTimestamp = 0; |
131 | | -document.getElementById('theme-toggle').addEventListener('click', toggleTheme); |
132 | 167 | document.getElementById('check-grammar').addEventListener('click', checkGrammar); |
133 | 168 | document.getElementById('clear-text').addEventListener('click', clearText); |
134 | 169 | document.getElementById('copy-corrected').addEventListener('click', copyCorrectText); |
135 | 170 | document.getElementById('input').addEventListener('input', updateWordCount); |
136 | 171 |
|
137 | | -function toggleTheme() { |
138 | | - darkMode = !darkMode; |
139 | | - const body = document.body; |
140 | | - const container = document.querySelector('.container'); |
141 | | - const themeToggle = document.getElementById('theme-toggle'); |
142 | | - if (darkMode) { |
143 | | - body.classList.add('bg-gray-900'); |
144 | | - container.classList.remove('bg-white'); |
145 | | - container.classList.add('bg-gray-800'); |
146 | | - themeToggle.textContent = '☀️'; |
147 | | - } else { |
148 | | - body.classList.remove('bg-gray-900'); |
149 | | - container.classList.remove('bg-gray-800'); |
150 | | - container.classList.add('bg-white'); |
151 | | - themeToggle.textContent = '🌙'; |
152 | | - } |
153 | | -} |
154 | | - |
155 | 172 | function updateWordCount() { |
156 | 173 | const text = document.getElementById('input').value; |
157 | 174 | const words = text.trim().split(/\s+/).filter(word => word.length > 0).length; |
|
180 | 197 |
|
181 | 198 | function showNotification(message, type = 'success') { |
182 | 199 | const notification = document.createElement('div'); |
183 | | - notification.className = `fixed top-4 right-4 p-4 rounded-md ${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white`; |
| 200 | + notification.className = `fixed top-4 right-4 p-4 rounded-md ${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white fade-in`; |
184 | 201 | notification.textContent = message; |
185 | 202 | document.body.appendChild(notification); |
186 | 203 | setTimeout(() => notification.remove(), 3000); |
|
204 | 221 | return; |
205 | 222 | } |
206 | 223 | button.disabled = true; |
207 | | - button.innerHTML = 'Checking<span class="loading-dots"></span>'; |
| 224 | + button.innerHTML = '<svg class="animate-spin h-4 w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Checking'; |
208 | 225 | const botMessage = `I am a grammar checking API. I analyze text for grammar errors with a focus on ${mode} writing. I always respond in this JSON format:{"errors":[{"type":"grammar","text":"original text","suggestion":"suggested correction","description":"error description"}]}`; |
209 | 226 | try { |
210 | 227 | const response = await fetch('https://chatgpt.tobiasmue91.workers.dev/', { |
|
234 | 251 | showNotification('Error checking grammar', 'error'); |
235 | 252 | } finally { |
236 | 253 | button.disabled = false; |
237 | | - button.textContent = 'Check Grammar'; |
| 254 | + button.innerHTML = '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg>Check Grammar'; |
238 | 255 | } |
239 | 256 | } |
240 | 257 |
|
|
247 | 264 |
|
248 | 265 | function updateHistoryDisplay() { |
249 | 266 | const historyList = document.getElementById('history-list'); |
250 | | - historyList.innerHTML = errorHistory.map((entry, index) => `<div class="p-2 bg-gray-50 dark:bg-gray-700 rounded border cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600" onclick="restoreHistoryEntry(${index})"><div class="text-sm text-gray-500 dark:text-gray-300">${entry.timestamp}</div><div class="text-sm truncate dark:text-white">${entry.text.substring(0, 50)}...</div><div class="text-xs text-gray-600 dark:text-gray-400">${entry.errors.length} errors found</div></div>`).join(''); |
| 267 | + historyList.innerHTML = errorHistory.map((entry, index) => `<div class="p-3 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors duration-200 group" onclick="restoreHistoryEntry(${index})"><div class="flex justify-between items-center"><div class="text-sm text-gray-500 dark:text-gray-400">${entry.timestamp}</div><div class="text-xs px-2 py-1 bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200 rounded-full">${entry.errors.length} errors</div></div><div class="text-sm text-gray-900 dark:text-gray-100 mt-1 truncate">${entry.text.substring(0, 50)}...</div><div class="text-xs text-gray-500 dark:text-gray-400 mt-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">Click to restore</div></div>`).join(''); |
251 | 268 | } |
252 | 269 |
|
253 | 270 | function restoreHistoryEntry(index) { |
|
0 commit comments