Aleksmorshen commited on
Commit
b5f5087
·
verified ·
1 Parent(s): 6ded4be

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +406 -19
index.html CHANGED
@@ -1,19 +1,406 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
+ <title>Менеджер Ссылок iOS</title>
7
+ <!-- Подключение React и ReactDOM -->
8
+ <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
9
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
10
+ <!-- Подключение Babel для JSX -->
11
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
12
+
13
+ <style>
14
+ /* --- iOS 18 Inspired Styles --- */
15
+ :root {
16
+ --ios-system-blue: #007AFF;
17
+ --ios-system-red: #FF3B30;
18
+ --ios-system-gray: #8E8E93;
19
+ --ios-system-gray-2: #AEAEB2;
20
+ --ios-system-gray-3: #C7C7CC;
21
+ --ios-system-gray-4: #D1D1D6;
22
+ --ios-system-gray-5: #E5E5EA;
23
+ --ios-system-gray-6: #F2F2F7;
24
+ --ios-background-light: #FFFFFF; /* Primary Light Background */
25
+ --ios-secondary-background-light: #F2F2F7; /* Secondary Grouped Background */
26
+ --ios-label-light: #000000; /* Primary Text */
27
+ --ios-secondary-label-light: rgba(60, 60, 67, 0.6); /* Secondary Text */
28
+ --ios-separator-light: rgba(60, 60, 67, 0.29);
29
+
30
+ /* Dark Mode (Optional - basic setup) */
31
+ /* @media (prefers-color-scheme: dark) { ... } */
32
+ }
33
+
34
+ html {
35
+ /* Prevent elastic scrolling glow */
36
+ overscroll-behavior-y: none;
37
+ }
38
+
39
+ body {
40
+ margin: 0;
41
+ padding: 0; /* Remove default padding */
42
+ font-family: -apple-system, BlinkMacSystemFont, "San Francisco", "Helvetica Neue", "Arial", sans-serif;
43
+ background-color: var(--ios-secondary-background-light);
44
+ color: var(--ios-label-light);
45
+ -webkit-font-smoothing: antialiased;
46
+ -moz-osx-font-smoothing: grayscale;
47
+ font-size: 17px; /* Base iOS font size */
48
+ line-height: 1.4;
49
+ overscroll-behavior-y: contain; /* Prevent body scroll */
50
+ height: 100%; /* Ensure body takes full height */
51
+ }
52
+
53
+ #root {
54
+ max-width: 600px; /* Typical max width for content on larger screens */
55
+ margin: 0 auto;
56
+ background-color: var(--ios-secondary-background-light);
57
+ min-height: 100vh; /* Full height */
58
+ display: flex;
59
+ flex-direction: column;
60
+ }
61
+
62
+ .app-container {
63
+ padding: 0; /* Reset padding */
64
+ display: flex;
65
+ flex-direction: column;
66
+ flex-grow: 1; /* Allow container to grow */
67
+ }
68
+
69
+ /* Navigation Bar Style Header */
70
+ .app-header {
71
+ background-color: rgba(249, 249, 249, 0.94); /* Slightly transparent background like iOS nav */
72
+ backdrop-filter: saturate(180%) blur(20px);
73
+ -webkit-backdrop-filter: saturate(180%) blur(20px);
74
+ border-bottom: 0.5px solid var(--ios-separator-light);
75
+ padding: 12px 16px;
76
+ text-align: center;
77
+ position: sticky;
78
+ top: 0;
79
+ z-index: 10;
80
+ }
81
+
82
+ .app-header h1 {
83
+ margin: 0;
84
+ font-size: 17px; /* iOS Nav Bar Title Size */
85
+ font-weight: 600; /* Semibold */
86
+ color: var(--ios-label-light);
87
+ }
88
+
89
+ /* Main Content Area */
90
+ .content-area {
91
+ padding: 20px 0; /* Vertical padding, no horizontal padding here */
92
+ flex-grow: 1;
93
+ }
94
+
95
+ /* Section Styling (like grouped table view) */
96
+ .ios-section {
97
+ background-color: var(--ios-background-light);
98
+ margin: 0 16px 20px 16px; /* Standard iOS horizontal margins */
99
+ border-radius: 10px; /* iOS rounded corners */
100
+ overflow: hidden; /* Clip children to rounded corners */
101
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05); /* Subtle shadow */
102
+ }
103
+
104
+ .ios-section-header {
105
+ padding: 8px 16px;
106
+ font-size: 13px; /* Smaller header text */
107
+ color: var(--ios-secondary-label-light);
108
+ text-transform: uppercase;
109
+ font-weight: 500;
110
+ background-color: var(--ios-secondary-background-light); /* Header outside the white box */
111
+ margin: 0 -16px 0 -16px; /* Extend to edges if needed */
112
+ padding-left: 32px; /* Indent header text */
113
+ padding-right: 32px;
114
+ }
115
+
116
+ /* Form Styling */
117
+ .add-form {
118
+ padding: 16px;
119
+ display: flex;
120
+ flex-direction: column;
121
+ gap: 12px;
122
+ }
123
+
124
+ .add-form input[type="url"],
125
+ .add-form input[type="text"] {
126
+ width: calc(100% - 24px); /* Account for padding */
127
+ padding: 11px 12px; /* iOS input padding */
128
+ border: 0.5px solid var(--ios-system-gray-4);
129
+ border-radius: 8px; /* Slightly less rounded than section */
130
+ font-size: 16px;
131
+ background-color: var(--ios-background-light); /* Ensure background matches */
132
+ color: var(--ios-label-light);
133
+ outline: none;
134
+ -webkit-appearance: none; /* Remove default iOS styling */
135
+ appearance: none;
136
+ transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
137
+ }
138
+
139
+ .add-form input[type="url"]:focus,
140
+ .add-form input[type="text"]:focus {
141
+ border-color: var(--ios-system-blue);
142
+ box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2); /* Subtle focus ring */
143
+ }
144
+
145
+ .add-form button {
146
+ background-color: var(--ios-system-blue);
147
+ color: white;
148
+ border: none;
149
+ padding: 12px 16px;
150
+ border-radius: 8px;
151
+ font-size: 17px;
152
+ font-weight: 500;
153
+ cursor: pointer;
154
+ text-align: center;
155
+ transition: background-color 0.15s ease;
156
+ -webkit-appearance: none;
157
+ appearance: none;
158
+ }
159
+
160
+ .add-form button:hover,
161
+ .add-form button:focus {
162
+ background-color: #0056b3; /* Darker blue on hover/focus */
163
+ outline: none;
164
+ }
165
+
166
+ /* Link List Styling */
167
+ .link-list {
168
+ list-style: none;
169
+ padding: 0;
170
+ margin: 0; /* Reset margin inside section */
171
+ }
172
+
173
+ .link-item {
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: space-between;
177
+ padding: 12px 16px; /* Standard iOS cell padding */
178
+ border-bottom: 0.5px solid var(--ios-separator-light);
179
+ gap: 10px;
180
+ }
181
+
182
+ /* Remove border from last item */
183
+ .link-item:last-child {
184
+ border-bottom: none;
185
+ }
186
+
187
+ .link-content {
188
+ flex-grow: 1;
189
+ min-width: 0; /* Allow content to shrink */
190
+ }
191
+
192
+ .link-description {
193
+ font-size: 16px;
194
+ font-weight: 400;
195
+ color: var(--ios-label-light);
196
+ margin-bottom: 3px;
197
+ white-space: nowrap;
198
+ overflow: hidden;
199
+ text-overflow: ellipsis;
200
+ }
201
+
202
+ .link-url {
203
+ font-size: 14px;
204
+ color: var(--ios-system-gray);
205
+ white-space: nowrap;
206
+ overflow: hidden;
207
+ text-overflow: ellipsis;
208
+ display: block; /* Ensure it takes block space */
209
+ text-decoration: none; /* Remove default underline */
210
+ }
211
+
212
+ .link-url:hover {
213
+ color: var(--ios-system-blue); /* Highlight on hover */
214
+ text-decoration: underline;
215
+ }
216
+
217
+ .delete-button {
218
+ background-color: var(--ios-system-red);
219
+ color: white;
220
+ border: none;
221
+ border-radius: 50%; /* Circular button */
222
+ width: 28px;
223
+ height: 28px;
224
+ font-size: 16px;
225
+ font-weight: bold;
226
+ cursor: pointer;
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ flex-shrink: 0; /* Prevent shrinking */
231
+ padding: 0;
232
+ line-height: 1;
233
+ transition: background-color 0.15s ease;
234
+ -webkit-appearance: none;
235
+ appearance: none;
236
+ }
237
+
238
+ .delete-button:hover,
239
+ .delete-button:focus {
240
+ background-color: #D92C2A; /* Darker red */
241
+ outline: none;
242
+ }
243
+
244
+ /* Empty State */
245
+ .empty-state {
246
+ text-align: center;
247
+ padding: 40px 20px;
248
+ color: var(--ios-secondary-label-light);
249
+ font-size: 16px;
250
+ }
251
+
252
+ </style>
253
+ </head>
254
+ <body>
255
+ <div id="root"></div>
256
+
257
+ <script type="text/babel">
258
+ const { useState, useEffect, Fragment } = React;
259
+
260
+ // --- Утилита для работы с Local Storage ---
261
+ const useLocalStorage = (key, initialValue) => {
262
+ // Получаем из localStorage или используем initialValue
263
+ const [storedValue, setStoredValue] = useState(() => {
264
+ try {
265
+ const item = window.localStorage.getItem(key);
266
+ return item ? JSON.parse(item) : initialValue;
267
+ } catch (error) {
268
+ console.error("Ошибка чтения из localStorage:", error);
269
+ return initialValue;
270
+ }
271
+ });
272
+
273
+ // Обновляем localStorage при изменении state
274
+ useEffect(() => {
275
+ try {
276
+ window.localStorage.setItem(key, JSON.stringify(storedValue));
277
+ } catch (error) {
278
+ console.error("Ошибка записи в localStorage:", error);
279
+ }
280
+ }, [key, storedValue]);
281
+
282
+ return [storedValue, setStoredValue];
283
+ };
284
+
285
+ // --- Основной Компонент Приложения ---
286
+ function App() {
287
+ const [links, setLinks] = useLocalStorage('savedLinks', []);
288
+ const [newUrl, setNewUrl] = useState('');
289
+ const [newDescription, setNewDescription] = useState('');
290
+ const [error, setError] = useState(''); // Для отображения ошибок ввода
291
+
292
+ const isValidUrl = (string) => {
293
+ try {
294
+ new URL(string);
295
+ return true;
296
+ } catch (_) {
297
+ return false;
298
+ }
299
+ };
300
+
301
+ const handleAddLink = (e) => {
302
+ e.preventDefault();
303
+ setError(''); // Сброс ошибки
304
+
305
+ if (!newDescription.trim()) {
306
+ setError('Пожалуйста, введите описание ссылки.');
307
+ return;
308
+ }
309
+ if (!newUrl.trim()) {
310
+ setError('Пожалуйста, введите URL ссылки.');
311
+ return;
312
+ }
313
+ if (!isValidUrl(newUrl.trim())) {
314
+ setError('Пожалуйста, введите корректный URL (например, https://example.com).');
315
+ return;
316
+ }
317
+
318
+
319
+ const newLink = {
320
+ id: Date.now(), // Простой уникальный ID
321
+ url: newUrl.trim(),
322
+ description: newDescription.trim(),
323
+ };
324
+
325
+ setLinks([newLink, ...links]); // Добавляем новую ссылку в начало списка
326
+ setNewUrl('');
327
+ setNewDescription('');
328
+ };
329
+
330
+ const handleDeleteLink = (idToDelete) => {
331
+ setLinks(links.filter(link => link.id !== idToDelete));
332
+ };
333
+
334
+ return (
335
+ <div className="app-container">
336
+ <header className="app-header">
337
+ <h1>Мои Ссылки</h1>
338
+ </header>
339
+
340
+ <div className="content-area">
341
+ {/* --- Форма добавления --- */}
342
+ <div className="ios-section">
343
+ <form className="add-form" onSubmit={handleAddLink}>
344
+ <input
345
+ type="text"
346
+ value={newDescription}
347
+ onChange={(e) => setNewDescription(e.target.value)}
348
+ placeholder="Описание (напр., Документация React)"
349
+ aria-label="Описание ссылки"
350
+ />
351
+ <input
352
+ type="url"
353
+ value={newUrl}
354
+ onChange={(e) => setNewUrl(e.target.value)}
355
+ placeholder="URL (напр., https://reactjs.org)"
356
+ aria-label="URL ссылки"
357
+ />
358
+ {error && <p style={{ color: 'var(--ios-system-red)', fontSize: '14px', margin: '-5px 0 5px 0' }}>{error}</p>}
359
+ <button type="submit">Добавить ссылку</button>
360
+ </form>
361
+ </div>
362
+
363
+ {/* --- Список ссылок --- */}
364
+ <div className="ios-section">
365
+ {links.length > 0 ? (
366
+ <ul className="link-list">
367
+ {links.map(link => (
368
+ <li key={link.id} className="link-item">
369
+ <div className="link-content">
370
+ <div className="link-description">{link.description}</div>
371
+ <a
372
+ href={link.url}
373
+ target="_blank"
374
+ rel="noopener noreferrer"
375
+ className="link-url"
376
+ >
377
+ {link.url}
378
+ </a>
379
+ </div>
380
+ <button
381
+ onClick={() => handleDeleteLink(link.id)}
382
+ className="delete-button"
383
+ aria-label={`Удалить ссылку ${link.description}`}
384
+ >
385
+ × {/* Крестик для удаления */}
386
+ </button>
387
+ </li>
388
+ ))}
389
+ </ul>
390
+ ) : (
391
+ <div className="empty-state">
392
+ Нет сохраненных ссылок. Добавьте первую!
393
+ </div>
394
+ )}
395
+ </div>
396
+ </div>
397
+ </div>
398
+ );
399
+ }
400
+
401
+ // --- Рендер приложения ---
402
+ const root = ReactDOM.createRoot(document.getElementById('root'));
403
+ root.render(<App />);
404
+ </script>
405
+ </body>
406
+ </html>