openfree commited on
Commit
80f8c29
ยท
verified ยท
1 Parent(s): b5d652f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +187 -336
index.html CHANGED
@@ -3,430 +3,281 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Interactive Calendar with LocalStorage</title>
7
  <style>
8
- /* ์ด์ „ ์Šคํƒ€์ผ ์ฝ”๋“œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ */
9
  * {
10
  margin: 0;
11
  padding: 0;
12
  box-sizing: border-box;
13
- font-family: 'Arial', sans-serif;
14
  }
15
 
16
  body {
17
- background: #f5f5f5;
18
- padding: 20px;
 
 
 
19
  }
20
 
21
- .calendar-container {
22
- max-width: 1200px;
23
- margin: 0 auto;
24
- background: white;
25
- border-radius: 10px;
26
- box-shadow: 0 5px 20px rgba(0,0,0,0.1);
27
- padding: 20px;
28
  }
29
 
30
- .calendar-header {
31
- display: flex;
32
- justify-content: space-between;
33
- align-items: center;
34
  margin-bottom: 20px;
35
  }
36
 
37
- .month-nav {
38
- display: flex;
39
- align-items: center;
40
- gap: 20px;
41
- }
42
-
43
- .nav-btn {
44
- background: none;
45
- border: none;
46
- font-size: 24px;
47
- cursor: pointer;
48
  color: #333;
49
- padding: 5px 10px;
50
  }
51
 
52
- .current-month {
53
- font-size: 24px;
54
- font-weight: bold;
 
 
 
 
 
55
  }
56
 
57
- .filter-controls {
58
- display: flex;
59
- gap: 10px;
60
  }
61
 
62
- .filter-btn {
63
- padding: 8px 15px;
 
 
 
64
  border: none;
65
- border-radius: 20px;
 
66
  cursor: pointer;
67
- background: #eee;
68
- transition: 0.3s;
69
  }
70
 
71
- .filter-btn.active {
72
- background: #007bff;
73
- color: white;
74
  }
75
 
76
- .calendar-grid {
77
- display: grid;
78
- grid-template-columns: repeat(7, 1fr);
79
- gap: 10px;
80
  }
81
 
82
- .day-header {
83
- text-align: center;
84
- font-weight: bold;
85
- padding: 10px;
86
- background: #f8f9fa;
87
- border-radius: 5px;
88
  }
89
 
90
- .day-cell {
91
- min-height: 120px;
92
  padding: 10px;
93
- background: #fff;
94
- border: 1px solid #eee;
95
- border-radius: 5px;
96
- transition: 0.3s;
97
  }
98
 
99
- .day-cell:hover {
100
- box-shadow: 0 0 10px rgba(0,0,0,0.1);
 
 
101
  }
102
 
103
- .day-cell.different-month {
104
- background: #f8f9fa;
105
- color: #999;
 
106
  }
107
 
108
- .event {
109
- margin: 5px 0;
110
- padding: 5px 10px;
111
- border-radius: 3px;
112
  font-size: 12px;
113
- cursor: move;
114
- animation: slideIn 0.3s ease-out;
115
- }
116
-
117
- @keyframes slideIn {
118
- from {
119
- transform: translateY(-10px);
120
- opacity: 0;
121
- }
122
- to {
123
- transform: translateY(0);
124
- opacity: 1;
125
- }
126
- }
127
-
128
- .event.work { background: #ffcdd2; }
129
- .event.personal { background: #c8e6c9; }
130
- .event.meeting { background: #bbdefb; }
131
-
132
- .event.dragging {
133
- opacity: 0.5;
134
- transform: scale(0.95);
135
- }
136
-
137
- .add-event-btn {
138
- position: fixed;
139
- bottom: 30px;
140
- right: 30px;
141
- width: 60px;
142
- height: 60px;
143
- border-radius: 50%;
144
- background: #007bff;
145
- color: white;
146
- border: none;
147
- font-size: 24px;
148
- cursor: pointer;
149
- box-shadow: 0 3px 10px rgba(0,0,0,0.2);
150
- transition: 0.3s;
151
- }
152
-
153
- .add-event-btn:hover {
154
- transform: scale(1.1);
155
  }
156
 
157
- .modal {
158
  display: none;
159
- position: fixed;
160
- top: 0;
161
- left: 0;
162
- width: 100%;
163
- height: 100%;
164
- background: rgba(0,0,0,0.5);
165
- justify-content: center;
166
- align-items: center;
167
- }
168
-
169
- .modal-content {
170
- background: white;
171
- padding: 20px;
172
- border-radius: 10px;
173
- width: 90%;
174
- max-width: 400px;
175
- }
176
-
177
- .form-group {
178
- margin: 15px 0;
179
- }
180
-
181
- input, select {
182
- width: 100%;
183
- padding: 8px;
184
- border: 1px solid #ddd;
185
- border-radius: 5px;
186
  }
187
 
188
- .modal-buttons {
189
- display: flex;
190
- justify-content: flex-end;
191
- gap: 10px;
192
- margin-top: 20px;
193
  }
194
 
195
- .modal-btn {
196
- padding: 8px 15px;
197
- border: none;
198
- border-radius: 5px;
199
- cursor: pointer;
200
  }
201
-
202
- .save-btn { background: #007bff; color: white; }
203
- .cancel-btn { background: #eee; }
204
  </style>
205
  </head>
206
  <body>
207
- <div class="calendar-container">
208
- <div class="calendar-header">
209
- <div class="month-nav">
210
- <button class="nav-btn" onclick="changeMonth(-1)">โ†</button>
211
- <div class="current-month"></div>
212
- <button class="nav-btn" onclick="changeMonth(1)">โ†’</button>
213
- </div>
214
- <div class="filter-controls">
215
- <button class="filter-btn active" data-type="all">All</button>
216
- <button class="filter-btn" data-type="work">Work</button>
217
- <button class="filter-btn" data-type="personal">Personal</button>
218
- <button class="filter-btn" data-type="meeting">Meeting</button>
 
 
 
 
219
  </div>
220
  </div>
221
- <div class="calendar-grid"></div>
222
- </div>
223
 
224
- <button class="add-event-btn" onclick="openModal()">+</button>
225
-
226
- <div class="modal">
227
- <div class="modal-content">
228
- <h2>Add Event</h2>
229
- <div class="form-group">
230
- <input type="text" id="eventTitle" placeholder="Event Title">
231
- </div>
232
- <div class="form-group">
233
- <select id="eventType">
234
- <option value="work">Work</option>
235
- <option value="personal">Personal</option>
236
- <option value="meeting">Meeting</option>
237
- </select>
238
- </div>
239
- <div class="modal-buttons">
240
- <button class="modal-btn cancel-btn" onclick="closeModal()">Cancel</button>
241
- <button class="modal-btn save-btn" onclick="saveEvent()">Save</button>
 
 
 
 
242
  </div>
243
  </div>
244
  </div>
245
 
246
  <script>
247
- let currentDate = new Date();
248
- let events = [];
249
- let selectedDate = null;
250
- let draggedEvent = null;
251
-
252
- // LocalStorage์—์„œ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
253
- function loadEvents() {
254
- const savedEvents = localStorage.getItem('calendarEvents');
255
- if (savedEvents) {
256
- events = JSON.parse(savedEvents).map(event => ({
257
- ...event,
258
- date: new Date(event.date)
259
- }));
260
  }
261
- }
262
 
263
- // LocalStorage์— ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ์ €์žฅ
264
- function saveEvents() {
265
- localStorage.setItem('calendarEvents', JSON.stringify(events));
266
  }
267
 
268
- function initCalendar() {
269
- loadEvents(); // ์ดˆ๊ธฐํ™” ์‹œ ์ €์žฅ๋œ ์ด๋ฒคํŠธ ๋กœ๋“œ
270
- updateMonthDisplay();
271
- renderCalendar();
272
- initEventListeners();
 
 
273
  }
274
 
275
- function updateMonthDisplay() {
276
- const months = ['January', 'February', 'March', 'April', 'May', 'June',
277
- 'July', 'August', 'September', 'October', 'November', 'December'];
278
- document.querySelector('.current-month').textContent =
279
- `${months[currentDate.getMonth()]} ${currentDate.getFullYear()}`;
280
  }
281
 
282
- function renderCalendar() {
283
- const grid = document.querySelector('.calendar-grid');
284
- grid.innerHTML = '';
285
 
286
- const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
287
- days.forEach(day => {
288
- const dayHeader = document.createElement('div');
289
- dayHeader.className = 'day-header';
290
- dayHeader.textContent = day;
291
- grid.appendChild(dayHeader);
292
- });
293
 
294
- const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
295
- const lastDay = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
296
-
297
- const paddingDays = firstDay.getDay();
298
- const prevLastDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 0);
299
- for (let i = paddingDays - 1; i >= 0; i--) {
300
- const dayCell = createDayCell(prevLastDay.getDate() - i, true);
301
- grid.appendChild(dayCell);
302
  }
303
 
304
- for (let day = 1; day <= lastDay.getDate(); day++) {
305
- const dayCell = createDayCell(day, false);
306
- grid.appendChild(dayCell);
 
307
  }
308
 
309
- const remainingDays = 42 - (paddingDays + lastDay.getDate());
310
- for (let i = 1; i <= remainingDays; i++) {
311
- const dayCell = createDayCell(i, true);
312
- grid.appendChild(dayCell);
313
  }
314
- }
315
 
316
- function createDayCell(day, isDifferentMonth) {
317
- const cell = document.createElement('div');
318
- cell.className = `day-cell${isDifferentMonth ? ' different-month' : ''}`;
319
- cell.innerHTML = `<div class="day-number">${day}</div>`;
320
- cell.addEventListener('dragover', e => e.preventDefault());
321
- cell.addEventListener('drop', handleDrop);
322
-
323
- if (!isDifferentMonth) {
324
- const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day);
325
- const dayEvents = events.filter(event => {
326
- const eventDate = new Date(event.date);
327
- return eventDate.toDateString() === date.toDateString();
328
- });
329
-
330
- dayEvents.forEach(event => {
331
- const eventElement = createEventElement(event);
332
- cell.appendChild(eventElement);
333
- });
334
- }
335
 
336
- return cell;
337
- }
338
 
339
- function createEventElement(event) {
340
- const eventDiv = document.createElement('div');
341
- eventDiv.className = `event ${event.type}`;
342
- eventDiv.textContent = event.title;
343
- eventDiv.draggable = true;
344
- eventDiv.dataset.eventId = event.id;
345
 
346
- eventDiv.addEventListener('dragstart', e => {
347
- draggedEvent = event;
348
- e.target.classList.add('dragging');
349
- });
350
-
351
- eventDiv.addEventListener('dragend', e => {
352
- e.target.classList.remove('dragging');
353
- });
354
-
355
- return eventDiv;
356
- }
357
 
358
- function handleDrop(e) {
359
- e.preventDefault();
360
- if (!draggedEvent) return;
361
 
362
- const dayCell = e.target.closest('.day-cell');
363
- if (!dayCell) return;
 
364
 
365
- const dayNumber = dayCell.querySelector('.day-number').textContent;
366
- const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), dayNumber);
367
-
368
- const eventIndex = events.findIndex(e => e.id === draggedEvent.id);
369
- events[eventIndex].date = newDate;
370
 
371
- saveEvents(); // ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ํ›„ ์ €์žฅ
372
- draggedEvent = null;
373
- renderCalendar();
374
- }
375
 
376
- function changeMonth(delta) {
377
- currentDate.setMonth(currentDate.getMonth() + delta);
378
- updateMonthDisplay();
379
- renderCalendar();
380
- }
381
 
382
- function openModal() {
383
- document.querySelector('.modal').style.display = 'flex';
384
- }
385
 
386
- function closeModal() {
387
- document.querySelector('.modal').style.display = 'none';
388
- }
389
 
390
- function saveEvent() {
391
- const title = document.getElementById('eventTitle').value;
392
- const type = document.getElementById('eventType').value;
393
-
394
- if (!title) return;
395
-
396
- const newEvent = {
397
- id: Date.now(),
398
- title,
399
- type,
400
- date: new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
401
- };
402
-
403
- events.push(newEvent);
404
- saveEvents(); // ์ƒˆ ์ด๋ฒคํŠธ ์ถ”๊ฐ€ ํ›„ ์ €์žฅ
405
- renderCalendar();
406
- closeModal();
407
- document.getElementById('eventTitle').value = '';
408
- }
409
 
410
- function initEventListeners() {
411
- document.querySelectorAll('.filter-btn').forEach(btn => {
412
- btn.addEventListener('click', (e) => {
413
- document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
414
- e.target.classList.add('active');
415
-
416
- const type = e.target.dataset.type;
417
- document.querySelectorAll('.event').forEach(event => {
418
- if (type === 'all' || event.classList.contains(type)) {
419
- event.style.display = 'block';
420
- } else {
421
- event.style.display = 'none';
422
- }
423
- });
424
- });
425
- });
426
  }
427
-
428
- // ์ดˆ๊ธฐํ™”
429
- initCalendar();
430
  </script>
431
  </body>
432
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login & Register</title>
7
  <style>
 
8
  * {
9
  margin: 0;
10
  padding: 0;
11
  box-sizing: border-box;
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
  }
14
 
15
  body {
16
+ min-height: 100vh;
17
+ display: flex;
18
+ justify-content: center;
19
+ align-items: center;
20
+ background: linear-gradient(45deg, #6b5b95, #feb236);
21
  }
22
 
23
+ .container {
24
+ background: rgba(255, 255, 255, 0.95);
25
+ border-radius: 20px;
26
+ padding: 40px;
27
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
28
+ width: 400px;
 
29
  }
30
 
31
+ .form-group {
 
 
 
32
  margin-bottom: 20px;
33
  }
34
 
35
+ h2 {
36
+ text-align: center;
 
 
 
 
 
 
 
 
 
37
  color: #333;
38
+ margin-bottom: 30px;
39
  }
40
 
41
+ input {
42
+ width: 100%;
43
+ padding: 12px;
44
+ border: 2px solid #ddd;
45
+ border-radius: 8px;
46
+ font-size: 16px;
47
+ transition: all 0.3s ease;
48
+ outline: none;
49
  }
50
 
51
+ input:focus {
52
+ border-color: #6b5b95;
 
53
  }
54
 
55
+ button {
56
+ width: 100%;
57
+ padding: 12px;
58
+ background: #6b5b95;
59
+ color: white;
60
  border: none;
61
+ border-radius: 8px;
62
+ font-size: 16px;
63
  cursor: pointer;
64
+ transition: all 0.3s ease;
 
65
  }
66
 
67
+ button:hover {
68
+ background: #574a7a;
 
69
  }
70
 
71
+ .switch-form {
72
+ text-align: center;
73
+ margin-top: 20px;
 
74
  }
75
 
76
+ .switch-form a {
77
+ color: #6b5b95;
78
+ text-decoration: none;
79
+ font-weight: 600;
 
 
80
  }
81
 
82
+ .alert {
 
83
  padding: 10px;
84
+ margin: 10px 0;
85
+ border-radius: 8px;
86
+ display: none;
 
87
  }
88
 
89
+ .alert-success {
90
+ background: #d4edda;
91
+ color: #155724;
92
+ border: 1px solid #c3e6cb;
93
  }
94
 
95
+ .alert-error {
96
+ background: #f8d7da;
97
+ color: #721c24;
98
+ border: 1px solid #f5c6cb;
99
  }
100
 
101
+ .password-requirements {
 
 
 
102
  font-size: 12px;
103
+ color: #666;
104
+ margin-top: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  }
106
 
107
+ .loading {
108
  display: none;
109
+ text-align: center;
110
+ margin-top: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  }
112
 
113
+ .loading::after {
114
+ content: "...";
115
+ animation: dots 1s steps(5, end) infinite;
 
 
116
  }
117
 
118
+ @keyframes dots {
119
+ 0%, 20% { content: "."; }
120
+ 40% { content: ".."; }
121
+ 60%, 100% { content: "..."; }
 
122
  }
 
 
 
123
  </style>
124
  </head>
125
  <body>
126
+ <div class="container">
127
+ <div id="loginForm">
128
+ <h2>Login</h2>
129
+ <div class="alert alert-error" id="loginError"></div>
130
+ <form onsubmit="handleLogin(event)">
131
+ <div class="form-group">
132
+ <input type="email" id="loginEmail" placeholder="Email" required>
133
+ </div>
134
+ <div class="form-group">
135
+ <input type="password" id="loginPassword" placeholder="Password" required>
136
+ </div>
137
+ <button type="submit">Login</button>
138
+ <div class="loading" id="loginLoading">Processing</div>
139
+ </form>
140
+ <div class="switch-form">
141
+ Don't have an account? <a href="#" onclick="toggleForms()">Register</a>
142
  </div>
143
  </div>
 
 
144
 
145
+ <div id="registerForm" style="display: none;">
146
+ <h2>Register</h2>
147
+ <div class="alert alert-success" id="registerSuccess"></div>
148
+ <div class="alert alert-error" id="registerError"></div>
149
+ <form onsubmit="handleRegister(event)">
150
+ <div class="form-group">
151
+ <input type="email" id="registerEmail" placeholder="Email" required>
152
+ </div>
153
+ <div class="form-group">
154
+ <input type="password" id="registerPassword" placeholder="Password" required>
155
+ <div class="password-requirements">
156
+ Password must be at least 8 characters long and contain letters and numbers
157
+ </div>
158
+ </div>
159
+ <div class="form-group">
160
+ <input type="password" id="confirmPassword" placeholder="Confirm Password" required>
161
+ </div>
162
+ <button type="submit">Register</button>
163
+ <div class="loading" id="registerLoading">Processing</div>
164
+ </form>
165
+ <div class="switch-form">
166
+ Already have an account? <a href="#" onclick="toggleForms()">Login</a>
167
  </div>
168
  </div>
169
  </div>
170
 
171
  <script>
172
+ // Mock user database
173
+ let users = [];
174
+
175
+ function toggleForms() {
176
+ const loginForm = document.getElementById('loginForm');
177
+ const registerForm = document.getElementById('registerForm');
178
+
179
+ if (loginForm.style.display === 'none') {
180
+ loginForm.style.display = 'block';
181
+ registerForm.style.display = 'none';
182
+ } else {
183
+ loginForm.style.display = 'none';
184
+ registerForm.style.display = 'block';
185
  }
 
186
 
187
+ // Clear all alerts and forms
188
+ clearAlerts();
189
+ document.querySelectorAll('form').forEach(form => form.reset());
190
  }
191
 
192
+ function clearAlerts() {
193
+ document.querySelectorAll('.alert').forEach(alert => {
194
+ alert.style.display = 'none';
195
+ });
196
+ document.querySelectorAll('.loading').forEach(loading => {
197
+ loading.style.display = 'none';
198
+ });
199
  }
200
 
201
+ function validatePassword(password) {
202
+ const minLength = 8;
203
+ const hasLetter = /[a-zA-Z]/.test(password);
204
+ const hasNumber = /\d/.test(password);
205
+ return password.length >= minLength && hasLetter && hasNumber;
206
  }
207
 
208
+ async function handleRegister(event) {
209
+ event.preventDefault();
210
+ clearAlerts();
211
 
212
+ const email = document.getElementById('registerEmail').value;
213
+ const password = document.getElementById('registerPassword').value;
214
+ const confirmPassword = document.getElementById('confirmPassword').value;
215
+ const loading = document.getElementById('registerLoading');
216
+ const success = document.getElementById('registerSuccess');
217
+ const error = document.getElementById('registerError');
 
218
 
219
+ if (!validatePassword(password)) {
220
+ error.textContent = 'Password must be at least 8 characters long and contain both letters and numbers';
221
+ error.style.display = 'block';
222
+ return;
 
 
 
 
223
  }
224
 
225
+ if (password !== confirmPassword) {
226
+ error.textContent = 'Passwords do not match';
227
+ error.style.display = 'block';
228
+ return;
229
  }
230
 
231
+ if (users.some(user => user.email === email)) {
232
+ error.textContent = 'Email already registered';
233
+ error.style.display = 'block';
234
+ return;
235
  }
 
236
 
237
+ loading.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
+ // Simulate API call
240
+ await new Promise(resolve => setTimeout(resolve, 1500));
241
 
242
+ users.push({ email, password });
 
 
 
 
 
243
 
244
+ loading.style.display = 'none';
245
+ success.textContent = 'Registration successful! Please login.';
246
+ success.style.display = 'block';
 
 
 
 
 
 
 
 
247
 
248
+ // Clear form
249
+ event.target.reset();
 
250
 
251
+ // Switch to login form after 2 seconds
252
+ setTimeout(toggleForms, 2000);
253
+ }
254
 
255
+ async function handleLogin(event) {
256
+ event.preventDefault();
257
+ clearAlerts();
 
 
258
 
259
+ const email = document.getElementById('loginEmail').value;
260
+ const password = document.getElementById('loginPassword').value;
261
+ const loading = document.getElementById('loginLoading');
262
+ const error = document.getElementById('loginError');
263
 
264
+ loading.style.display = 'block';
 
 
 
 
265
 
266
+ // Simulate API call
267
+ await new Promise(resolve => setTimeout(resolve, 1500));
 
268
 
269
+ const user = users.find(u => u.email === email && u.password === password);
 
 
270
 
271
+ loading.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
+ if (user) {
274
+ alert('Login successful!');
275
+ event.target.reset();
276
+ } else {
277
+ error.textContent = 'Invalid email or password';
278
+ error.style.display = 'block';
279
+ }
 
 
 
 
 
 
 
 
 
280
  }
 
 
 
281
  </script>
282
  </body>
283
  </html>