📱 ИНТЕРАКТИВНЫЙ ВЕБ-ИНТЕРФЕЙС УПРАВЛЕНИЯ
От статической панели к живому цифровому опыту
🎯 МЕТОДОЛОГИЧЕСКАЯ КОНЦЕПЦИЯ СПРИНТА
Философия трансформации:
1БЫЛО: Веб-страница отвечает на клики (реактивный интерфейс)
2СТАЛО: Интерфейс ведет диалог с пользователем (проактивный партнер)
Ключевая идея: Дети создают “цифрового помощника” - интерфейс, который не просто выполняет команды, а понимает контекст, предлагает решения и адаптируется под каждого пользователя.
Эволюционный скачок:
- Кнопки → Жесты: От механических кликов к естественным движениям
- Статика → Анимация: Каждое взаимодействие живое и отзывчивое
- Универсальность → Персонализация: Интерфейс помнит и адаптируется
- Инструмент → Собеседник: Система ведет диалог с пользователем
🧠 ПЕДАГОГИЧЕСКИЕ ЦЕЛИ СПРИНТА
Концептуальные цели:
- “Интерактивность” как двусторонний разговор человека и машины
- “Контекстное мышление” - система понимает ситуацию пользователя
- “Адаптивность” - интерфейс эволюционирует от использования
- “Эмпатический дизайн” - технология должна понимать эмоции
Технические цели:
- Advanced JavaScript: жесты, анимации, real-time обновления
- CSS3: transitions, transforms, keyframes для живого интерфейса
- Web APIs: геолокация, вибрация, battery, device orientation
- Progressive Web App (PWA) - превращаем в мобильное приложение
- Machine Learning на клиенте: TensorFlow.js для предсказаний
Метакогнитивные цели:
- “UX мышление” - как пользователь думает и чувствует
- “Поведенческий дизайн” - как интерфейс влияет на действия
- “Инклюзивное мышление” - дизайн для всех типов пользователей
📚 СТРУКТУРА СПРИНТА (4 занятия)
Занятие 1: “Психология взаимодействия” 🧠
Длительность: 90 минут
Фаза 1: Эксперимент “Эмоции интерфейса” (25 мин)
Метод: Экспериментальная психология UX
Практический эксперимент: Дети тестируют 3 версии одного интерфейса:
1<!-- Версия 1: "Холодный робот" -->
2<button onclick="turnOnLight()">ВКЛЮЧИТЬ СВЕТ</button>
3
4<!-- Версия 2: "Дружелюбный помощник" -->
5<button onclick="turnOnLight()" class="friendly">
6 ☀️ Сделать светлее
7</button>
8
9<!-- Версия 3: "Умный собеседник" -->
10<button onclick="smartLighting()" class="smart">
11 💡 <span id="lightSuggestion">Создать комфортное освещение</span>
12</button>
Ключевые открытия:
- “Слова = эмоции. Технология тоже может быть дружелюбной”
- “Эмодзи = универсальный язык эмоций”
- “Предложения лучше команд”
Фаза 2: Анализ пользовательских ролей (30 мин)
Метод: Persona Development
Создание “Персон” для системы:
1const userPersonas = {
2 teacher: {
3 name: "Мария Ивановна",
4 age: 45,
5 techLevel: "средний",
6 goals: ["быстро подготовить класс", "сэкономить время"],
7 frustrations: ["сложные интерфейсы", "медленные системы"],
8 preferredInteraction: "голосовые команды",
9 timeOfDay: "утро",
10 interface: {
11 buttonSize: "large",
12 textSize: "18px",
13 animations: "minimal",
14 shortcuts: true
15 }
16 },
17
18 student: {
19 name: "Максим",
20 age: 12,
21 techLevel: "высокий",
22 goals: ["изучить как работает", "поиграть с системой"],
23 frustrations: ["скучные интерфейсы", "много текста"],
24 preferredInteraction: "жесты и анимации",
25 timeOfDay: "любое",
26 interface: {
27 buttonSize: "medium",
28 textSize: "16px",
29 animations: "rich",
30 gamification: true
31 }
32 },
33
34 janitor: {
35 name: "Сергей Петрович",
36 age: 55,
37 techLevel: "низкий",
38 goals: ["проверить что все выключено", "безопасность"],
39 frustrations: ["сложная технология", "мелкий текст"],
40 preferredInteraction: "простые кнопки",
41 timeOfDay: "вечер",
42 interface: {
43 buttonSize: "extra-large",
44 textSize: "24px",
45 animations: "none",
46 highContrast: true
47 }
48 }
49};
Практическое задание: Дети адаптируют один интерфейс под каждую персону.
Фаза 3: Принципы живого интерфейса (20 мин)
Концепция: “12 принципов интерактивности”
1const interactivityPrinciples = {
2 1: "Немедленная реакция - система отвечает за 100мс",
3 2: "Визуальная обратная связь - пользователь видит результат",
4 3: "Тактильная обратная связь - вибрация подтверждает действие",
5 4: "Звуковая обратная связь - звуки создают атмосферу",
6 5: "Предсказание желаний - система предлагает действия",
7 6: "Контекстная адаптация - интерфейс меняется по ситуации",
8 7: "Прощение ошибок - любое действие можно отменить",
9 8: "Прогрессивное раскрытие - сложность появляется постепенно",
10 9: "Эмоциональный дизайн - интерфейс вызывает положительные эмоции",
11 10: "Инклюзивность - доступно для людей с ограничениями",
12 11: "Персонализация - система помнит предпочтения",
13 12: "Обучение - интерфейс становится умнее от использования"
14};
Фаза 4: Wireframing интерактивного интерфейса (15 мин)
Метод: Collaborative Design
Дети рисуют схемы интерфейса с учетом всех принципов:
- Где располагаются элементы для каждой персоны
- Какие анимации и переходы будут
- Как система будет адаптироваться
- Какие жесты и взаимодействия поддерживать
Занятие 2: “Создание живого интерфейса” ✨
Длительность: 90 минут
Фаза 1: Продвинутый HTML с семантикой (20 мин)
Концепция: “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">
6 <meta name="description" content="ALEX - Умная система управления классом">
7 <meta name="theme-color" content="#667eea">
8
9 <!-- PWA мета-теги -->
10 <link rel="manifest" href="/manifest.json">
11 <link rel="apple-touch-icon" href="/icon-192.png">
12
13 <title>🤖 ALEX - Ваш умный помощник</title>
14
15 <style>
16 /* CSS Custom Properties для динамической темизации */
17 :root {
18 --primary-color: #667eea;
19 --secondary-color: #764ba2;
20 --accent-color: #4ECDC4;
21 --text-color: white;
22 --background-opacity: 0.1;
23 --animation-speed: 0.3s;
24 --border-radius: 15px;
25 --shadow-color: rgba(0,0,0,0.2);
26
27 /* Адаптивные размеры */
28 --button-size: clamp(120px, 15vw, 200px);
29 --text-size: clamp(14px, 2vw, 18px);
30 --icon-size: clamp(2rem, 4vw, 4rem);
31 }
32
33 /* Темы для разных персон */
34 [data-persona="teacher"] {
35 --button-size: clamp(150px, 20vw, 250px);
36 --text-size: clamp(16px, 2.5vw, 20px);
37 --animation-speed: 0.2s;
38 }
39
40 [data-persona="student"] {
41 --accent-color: #FF6B6B;
42 --animation-speed: 0.4s;
43 --border-radius: 20px;
44 }
45
46 [data-persona="janitor"] {
47 --button-size: clamp(180px, 25vw, 300px);
48 --text-size: clamp(18px, 3vw, 24px);
49 --animation-speed: 0.1s;
50 --background-opacity: 0.2;
51 }
52
53 * {
54 margin: 0;
55 padding: 0;
56 box-sizing: border-box;
57 }
58
59 body {
60 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
61 background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
62 min-height: 100vh;
63 overflow-x: hidden;
64 transition: all var(--animation-speed) ease;
65 user-select: none;
66 }
67
68 /* Адаптивный контейнер */
69 .container {
70 max-width: 1200px;
71 margin: 0 auto;
72 padding: clamp(15px, 3vw, 30px);
73 min-height: 100vh;
74 display: flex;
75 flex-direction: column;
76 }
77
78 /* Семантическая шапка */
79 .system-header {
80 text-align: center;
81 margin-bottom: 2rem;
82 animation: slideInDown 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
83 }
84
85 .system-title {
86 font-size: clamp(2rem, 6vw, 4rem);
87 color: var(--text-color);
88 margin-bottom: 1rem;
89 text-shadow: 2px 2px 4px var(--shadow-color);
90 position: relative;
91 }
92
93 .system-title::after {
94 content: '';
95 position: absolute;
96 bottom: -10px;
97 left: 50%;
98 transform: translateX(-50%);
99 width: 100px;
100 height: 3px;
101 background: var(--accent-color);
102 border-radius: 2px;
103 animation: expandWidth 1s ease-out 0.5s backwards;
104 }
105
106 @keyframes expandWidth {
107 from { width: 0; }
108 to { width: 100px; }
109 }
110
111 /* Адаптивная статус-панель */
112 .status-panel {
113 display: flex;
114 align-items: center;
115 justify-content: center;
116 gap: 1rem;
117 background: rgba(255,255,255, var(--background-opacity));
118 padding: 1rem 2rem;
119 border-radius: var(--border-radius);
120 backdrop-filter: blur(10px);
121 border: 1px solid rgba(255,255,255,0.2);
122 margin-bottom: 2rem;
123 transition: all var(--animation-speed) ease;
124 }
125
126 .status-panel:hover {
127 transform: translateY(-2px);
128 box-shadow: 0 5px 25px var(--shadow-color);
129 }
130
131 .status-indicator {
132 display: flex;
133 align-items: center;
134 gap: 0.5rem;
135 }
136
137 .status-dot {
138 width: 12px;
139 height: 12px;
140 border-radius: 50%;
141 background: var(--accent-color);
142 animation: pulse 2s infinite;
143 position: relative;
144 }
145
146 .status-dot::after {
147 content: '';
148 position: absolute;
149 width: 100%;
150 height: 100%;
151 border-radius: 50%;
152 background: var(--accent-color);
153 animation: ripple 2s infinite;
154 }
155
156 @keyframes pulse {
157 0%, 100% { opacity: 1; }
158 50% { opacity: 0.5; }
159 }
160
161 @keyframes ripple {
162 0% {
163 transform: scale(1);
164 opacity: 0.7;
165 }
166 100% {
167 transform: scale(2);
168 opacity: 0;
169 }
170 }
171
172 /* Основная область контента */
173 .main-content {
174 flex: 1;
175 display: grid;
176 gap: 2rem;
177 grid-template-columns: 1fr;
178 }
179
180 @media (min-width: 768px) {
181 .main-content {
182 grid-template-columns: 2fr 1fr;
183 }
184 }
185
186 /* Интерактивная панель датчиков */
187 .sensors-section {
188 display: grid;
189 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
190 gap: 1.5rem;
191 }
192
193 .sensor-card {
194 background: rgba(255,255,255, var(--background-opacity));
195 backdrop-filter: blur(15px);
196 border-radius: var(--border-radius);
197 padding: 1.5rem;
198 border: 1px solid rgba(255,255,255,0.2);
199 position: relative;
200 overflow: hidden;
201 cursor: pointer;
202 transition: all var(--animation-speed) cubic-bezier(0.175, 0.885, 0.32, 1.275);
203
204 /* Подготовка к анимациям */
205 transform-origin: center;
206 will-change: transform;
207 }
208
209 .sensor-card::before {
210 content: '';
211 position: absolute;
212 top: 0;
213 left: -100%;
214 width: 100%;
215 height: 100%;
216 background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
217 transition: left 0.5s ease;
218 z-index: 1;
219 }
220
221 .sensor-card:hover::before {
222 left: 100%;
223 }
224
225 .sensor-card:hover {
226 transform: translateY(-8px) scale(1.02);
227 box-shadow: 0 20px 40px var(--shadow-color);
228 border-color: rgba(255,255,255,0.4);
229 }
230
231 .sensor-card:active {
232 transform: translateY(-4px) scale(0.98);
233 }
234
235 /* Семантическая структура датчика */
236 .sensor-icon {
237 font-size: var(--icon-size);
238 margin-bottom: 1rem;
239 display: block;
240 transition: transform var(--animation-speed) ease;
241 position: relative;
242 z-index: 2;
243 }
244
245 .sensor-card:hover .sensor-icon {
246 transform: scale(1.2) rotate(5deg);
247 }
248
249 .sensor-name {
250 font-size: calc(var(--text-size) * 1.1);
251 color: rgba(255,255,255,0.9);
252 margin-bottom: 0.5rem;
253 font-weight: 600;
254 position: relative;
255 z-index: 2;
256 }
257
258 .sensor-value {
259 font-size: calc(var(--text-size) * 1.8);
260 font-weight: bold;
261 color: var(--text-color);
262 margin: 1rem 0;
263 position: relative;
264 z-index: 2;
265 transition: all var(--animation-speed) ease;
266 }
267
268 .sensor-status {
269 font-size: calc(var(--text-size) * 0.9);
270 color: rgba(255,255,255,0.8);
271 font-style: italic;
272 position: relative;
273 z-index: 2;
274 }
275
276 /* Визуальный прогресс-бар */
277 .sensor-progress {
278 width: 100%;
279 height: 6px;
280 background: rgba(255,255,255,0.2);
281 border-radius: 3px;
282 overflow: hidden;
283 margin-top: 1rem;
284 position: relative;
285 z-index: 2;
286 }
287
288 .progress-fill {
289 height: 100%;
290 background: linear-gradient(90deg, var(--accent-color), var(--primary-color));
291 border-radius: 3px;
292 transition: width 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
293 position: relative;
294 }
295
296 .progress-fill::after {
297 content: '';
298 position: absolute;
299 top: 0;
300 left: 0;
301 right: 0;
302 bottom: 0;
303 background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
304 animation: progressShine 2s infinite;
305 }
306
307 @keyframes progressShine {
308 0% { transform: translateX(-100%); }
309 100% { transform: translateX(100%); }
310 }
311
312 /* Панель управления */
313 .controls-section {
314 display: flex;
315 flex-direction: column;
316 gap: 1.5rem;
317 }
318
319 .section-title {
320 color: var(--text-color);
321 font-size: calc(var(--text-size) * 1.3);
322 margin-bottom: 1rem;
323 text-align: center;
324 position: relative;
325 }
326
327 .section-title::after {
328 content: '';
329 position: absolute;
330 bottom: -5px;
331 left: 50%;
332 transform: translateX(-50%);
333 width: 50px;
334 height: 2px;
335 background: var(--accent-color);
336 border-radius: 1px;
337 }
338
339 /* Умные кнопки управления */
340 .control-grid {
341 display: grid;
342 grid-template-columns: repeat(auto-fit, minmax(var(--button-size), 1fr));
343 gap: 1rem;
344 }
345
346 .smart-button {
347 background: rgba(255,255,255, var(--background-opacity));
348 border: 2px solid rgba(255,255,255,0.3);
349 border-radius: var(--border-radius);
350 padding: 1.5rem 1rem;
351 color: var(--text-color);
352 font-size: var(--text-size);
353 font-weight: 600;
354 cursor: pointer;
355 position: relative;
356 overflow: hidden;
357 transition: all var(--animation-speed) cubic-bezier(0.175, 0.885, 0.32, 1.275);
358 text-align: center;
359 user-select: none;
360 backdrop-filter: blur(10px);
361
362 /* Подготовка к трансформациям */
363 transform-origin: center;
364 will-change: transform;
365 }
366
367 .smart-button::before {
368 content: '';
369 position: absolute;
370 top: 0;
371 left: 0;
372 right: 0;
373 bottom: 0;
374 background: radial-gradient(circle at center, rgba(255,255,255,0.3) 0%, transparent 70%);
375 opacity: 0;
376 transition: opacity var(--animation-speed) ease;
377 z-index: 1;
378 }
379
380 .smart-button:hover::before {
381 opacity: 1;
382 }
383
384 .smart-button:hover {
385 transform: translateY(-4px) scale(1.05);
386 box-shadow: 0 15px 35px var(--shadow-color);
387 border-color: rgba(255,255,255,0.5);
388 background: rgba(255,255,255, calc(var(--background-opacity) + 0.1));
389 }
390
391 .smart-button:active {
392 transform: translateY(-2px) scale(0.95);
393 transition: transform 0.1s ease;
394 }
395
396 /* Содержимое кнопки */
397 .button-content {
398 position: relative;
399 z-index: 2;
400 display: flex;
401 flex-direction: column;
402 align-items: center;
403 gap: 0.5rem;
404 }
405
406 .button-icon {
407 font-size: calc(var(--text-size) * 1.5);
408 margin-bottom: 0.5rem;
409 }
410
411 .button-text {
412 font-weight: 600;
413 line-height: 1.2;
414 }
415
416 .button-description {
417 font-size: calc(var(--text-size) * 0.8);
418 opacity: 0.8;
419 line-height: 1.2;
420 }
421
422 /* Специальные кнопки */
423 .smart-button.primary {
424 background: linear-gradient(135deg, var(--accent-color), var(--primary-color));
425 border: none;
426 animation: primaryGlow 3s infinite;
427 }
428
429 @keyframes primaryGlow {
430 0%, 100% {
431 box-shadow: 0 5px 20px rgba(78, 205, 196, 0.4);
432 }
433 50% {
434 box-shadow: 0 5px 30px rgba(78, 205, 196, 0.6);
435 }
436 }
437
438 .smart-button.danger {
439 border-color: #ff6b6b;
440 color: #ff6b6b;
441 }
442
443 .smart-button.danger:hover {
444 background: rgba(255, 107, 107, 0.1);
445 border-color: #ff5252;
446 }
447
448 /* Рипл-эффект для кнопок */
449 .smart-button::after {
450 content: '';
451 position: absolute;
452 top: 50%;
453 left: 50%;
454 width: 0;
455 height: 0;
456 border-radius: 50%;
457 background: rgba(255,255,255,0.4);
458 transform: translate(-50%, -50%);
459 transition: width 0.6s ease, height 0.6s ease;
460 z-index: 1;
461 }
462
463 .smart-button:active::after {
464 width: 300px;
465 height: 300px;
466 }
467
468 /* Адаптивность */
469 @media (max-width: 768px) {
470 .main-content {
471 grid-template-columns: 1fr;
472 }
473
474 .sensors-section {
475 grid-template-columns: 1fr;
476 }
477 }
478
479 @media (max-width: 480px) {
480 .sensor-card {
481 padding: 1rem;
482 }
483
484 .smart-button {
485 padding: 1rem 0.5rem;
486 }
487 }
488
489 /* Темы времени суток */
490 .theme-morning {
491 --primary-color: #ffeaa7;
492 --secondary-color: #fdcb6e;
493 --accent-color: #e17055;
494 }
495
496 .theme-evening {
497 --primary-color: #6c5ce7;
498 --secondary-color: #a29bfe;
499 --accent-color: #fd79a8;
500 }
501
502 .theme-night {
503 --primary-color: #2d3436;
504 --secondary-color: #636e72;
505 --accent-color: #00b894;
506 }
507
508 /* Анимации появления */
509 @keyframes slideInDown {
510 from {
511 transform: translateY(-50px);
512 opacity: 0;
513 }
514 to {
515 transform: translateY(0);
516 opacity: 1;
517 }
518 }
519
520 @keyframes slideInUp {
521 from {
522 transform: translateY(50px);
523 opacity: 0;
524 }
525 to {
526 transform: translateY(0);
527 opacity: 1;
528 }
529 }
530
531 @keyframes fadeInScale {
532 from {
533 transform: scale(0.8);
534 opacity: 0;
535 }
536 to {
537 transform: scale(1);
538 opacity: 1;
539 }
540 }
541
542 /* Применение анимаций */
543 .sensor-card {
544 animation: fadeInScale 0.6s ease-out backwards;
545 }
546
547 .sensor-card:nth-child(1) { animation-delay: 0.1s; }
548 .sensor-card:nth-child(2) { animation-delay: 0.2s; }
549 .sensor-card:nth-child(3) { animation-delay: 0.3s; }
550 .sensor-card:nth-child(4) { animation-delay: 0.4s; }
551
552 .smart-button {
553 animation: slideInUp 0.6s ease-out backwards;
554 }
555
556 .smart-button:nth-child(1) { animation-delay: 0.5s; }
557 .smart-button:nth-child(2) { animation-delay: 0.6s; }
558 .smart-button:nth-child(3) { animation-delay: 0.7s; }
559 .smart-button:nth-child(4) { animation-delay: 0.8s; }
560 .smart-button:nth-child(5) { animation-delay: 0.9s; }
561 .smart-button:nth-child(6) { animation-delay: 1.0s; }
562 </style>
563</head>
564
565<body data-persona="student" id="app">
566 <div class="container">
567 <!-- Семантическая шапка -->
568 <header class="system-header">
569 <h1 class="system-title">🤖 ALEX</h1>
570 <div class="status-panel">
571 <div class="status-indicator">
572 <div class="status-dot" id="statusDot"></div>
573 <span id="systemStatus">Подключаюсь к системе...</span>
574 </div>
575 <div class="status-indicator">
576 <span id="connectionQuality">📶</span>
577 <span id="batteryLevel">🔋</span>
578 </div>
579 </div>
580 </header>
581
582 <!-- Основной контент -->
583 <main class="main-content">
584 <!-- Секция датчиков -->
585 <section class="sensors-section">
586 <article class="sensor-card" data-sensor="temperature" role="button" tabindex="0" aria-label="Датчик температуры">
587 <span class="sensor-icon" aria-hidden="true">🌡️</span>
588 <h3 class="sensor-name">Температура</h3>
589 <div class="sensor-value" id="temperature" aria-live="polite">--°C</div>
590 <p class="sensor-status" id="tempStatus">Загрузка...</p>
591 <div class="sensor-progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
592 <div class="progress-fill" id="tempProgress" style="width: 0%"></div>
593 </div>
594 </article>
595
596 <article class="sensor-card" data-sensor="light" role="button" tabindex="0" aria-label="Датчик освещенности">
597 <span class="sensor-icon" aria-hidden="true">💡</span>
598 <h3 class="sensor-name">Освещенность</h3>
599 <div class="sensor-value" id="light" aria-live="polite">--%</div>
600 <p class="sensor-status" id="lightStatus">Загрузка...</p>
601 <div class="sensor-progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
602 <div class="progress-fill" id="lightProgress" style="width: 0%"></div>
603 </div>
604 </article>
605
606 <article class="sensor-card" data-sensor="sound" role="button" tabindex="0" aria-label="Датчик звука">
607 <span class="sensor-icon" aria-hidden="true">🔊</span>
608 <h3 class="sensor-name">Уровень шума</h3>
609 <div class="sensor-value" id="sound" aria-live="polite">--дБ</div>
610 <p class="sensor-status" id="soundStatus">Загрузка...</p>
611 <div class="sensor-progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
612 <div class="progress-fill" id="soundProgress" style="width: 0%"></div>
613 </div>
614 </article>
615
616 <article class="sensor-card" data-sensor="motion" role="button" tabindex="0" aria-label="Датчик движения">
617 <span class="sensor-icon" aria-hidden="true">🚶</span>
618 <h3 class="sensor-name">Движение</h3>
619 <div class="sensor-value" id="motion" aria-live="polite">--</div>
620 <p class="sensor-status" id="motionStatus">Загрузка...</p>
621 <div class="sensor-progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
622 <div class="progress-fill" id="motionProgress" style="width: 0%"></div>
623 </div>
624 </article>
625 </section>
626
627 <!-- Панель управления -->
628 <aside class="controls-section">
629 <h2 class="section-title">🎛️ Умное управление</h2>
630 <div class="control-grid">
631 <button class="smart-button primary" data-mode="comfort" data-description="Идеальная температура и освещение">
632 <div class="button-content">
633 <span class="button-icon">😌</span>
634 <span class="button-text">Комфорт</span>
635 <span class="button-description">Создать уют</span>
636 </div>
637 </button>
638
639 <button class="smart-button" data-mode="eco" data-description="Экономия энергии с умом">
640 <div class="button-content">
641 <span class="button-icon">🌱</span>
642 <span class="button-text">Эко-режим</span>
643 <span class="button-description">Сберечь планету</span>
644 </div>
645 </button>
646
647 <button class="smart-button" data-mode="focus" data-description="Оптимальные условия для концентрации">
648 <div class="button-content">
649 <span class="button-icon">🎯</span>
650 <span class="button-text">Фокус</span>
651 <span class="button-description">Время учиться</span>
652 </div>
653 </button>
654
655 <button class="smart-button" data-mode="party" data-description="Атмосфера для веселья">
656 <div class="button-content">
657 <span class="button-icon">🎉</span>
658 <span class="button-text">Вечеринка</span>
659 <span class="button-description">Время радости</span>
660 </div>
661 </button>
662
663 <button class="smart-button" data-mode="presentation" data-description="Подготовка к показу">
664 <div class="button-content">
665 <span class="button-icon">📽️</span>
666 <span class="button-text">Презентация</span>
667 <span class="button-description">Все внимание</span>
668 </div>
669 </button>
670
671 <button class="smart-button danger" data-mode="emergency" data-description="Экстренное отключение">
672 <div class="button-content">
673 <span class="button-icon">🚨</span>
674 <span class="button-text">Экстренный</span>
675 <span class="button-description">Все выключить</span>
676 </div>
677 </button>
678 </div>
679 </aside>
680 </main>
681 </div>
682</body>
683</html>
Фаза 2: Продвинутый JavaScript для интерактивности (35 мин)
Концепция: “JavaScript как мозг интерфейса”
1// === КЛАСС УМНОГО ИНТЕРФЕЙСА ===
2class SmartInterface {
3 constructor() {
4 this.sensorData = {};
5 this.userPreferences = this.loadUserPreferences();
6 this.currentPersona = this.detectPersona();
7 this.socket = null;
8 this.isOnline = false;
9 this.gestureRecognizer = null;
10
11 this.init();
12 }
13
14 async init() {
15 console.log('🚀 Инициализация умного интерфейса...');
16
17 // Определяем возможности устройства
18 await this.detectDeviceCapabilities();
19
20 // Настраиваем персону
21 this.applyPersona(this.currentPersona);
22
23 // Подключаемся к системе
24 this.connectToSystem();
25
26 // Инициализируем взаимодействия
27 this.setupInteractions();
28
29 // Запускаем адаптивную логику
30 this.startAdaptiveEngine();
31
32 console.log('✅ Умный интерфейс готов!');
33 this.showWelcomeMessage();
34 }
35
36 // === ОПРЕДЕЛЕНИЕ ВОЗМОЖНОСТЕЙ УСТРОЙСТВА ===
37 async detectDeviceCapabilities() {
38 const capabilities = {
39 vibration: 'vibrate' in navigator,
40 geolocation: 'geolocation' in navigator,
41 battery: 'getBattery' in navigator,
42 deviceMotion: 'DeviceMotionEvent' in window,
43 touchGestures: 'ontouchstart' in window,
44 voiceRecognition: 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window,
45 notifications: 'Notification' in window,
46 serviceWorker: 'serviceWorker' in navigator
47 };
48
49 this.deviceCapabilities = capabilities;
50
51 // Запрашиваем разрешения
52 if (capabilities.notifications && Notification.permission !== 'granted') {
53 await Notification.requestPermission();
54 }
55
56 // Получаем информацию о батарее
57 if (capabilities.battery) {
58 try {
59 this.battery = await navigator.getBattery();
60 this.updateBatteryIndicator();
61 this.battery.addEventListener('levelchange', () => this.updateBatteryIndicator());
62 } catch (e) {
63 console.log('Информация о батарее недоступна');
64 }
65 }
66
67 console.log('📱 Возможности устройства:', capabilities);
68 }
69
70 // === ОПРЕДЕЛЕНИЕ ПЕРСОНЫ ПОЛЬЗОВАТЕЛЯ ===
71 detectPersona() {
72 const hour = new Date().getHours();
73 const userAgent = navigator.userAgent;
74 const screenSize = window.innerWidth;
75
76 // Простая эвристика определения типа пользователя
77 if (hour >= 7 && hour <= 9) {
78 return 'teacher'; // Утром обычно учителя
79 } else if (hour >= 16 && hour <= 18) {
80 return 'janitor'; // Вечером техперсонал
81 } else if (screenSize < 768) {
82 return 'student'; // Мобильные устройства чаще у учеников
83 }
84
85 return 'student'; // По умолчанию
86 }
87
88 // === ПРИМЕНЕНИЕ ПЕРСОНЫ ===
89 applyPersona(persona) {
90 document.body.setAttribute('data-persona', persona);
91
92 const personaSettings = {
93 teacher: {
94 animationSpeed: '0.2s',
95 buttonSize: '200px',
96 textSize: '18px',
97 shortcuts: true,
98 voiceControl: true
99 },
100 student: {
101 animationSpeed: '0.4s',
102 buttonSize: '150px',
103 textSize: '16px',
104 richAnimations: true,
105 gameElements: true
106 },
107 janitor: {
108 animationSpeed: '0.1s',
109 buttonSize: '250px',
110 textSize: '20px',
111 highContrast: true,
112 largeTargets: true
113 }
114 };
115
116 const settings = personaSettings[persona];
117 if (settings) {
118 // Применяем CSS переменные для персоны
119 Object.entries(settings).forEach(([key, value]) => {
120 if (typeof value === 'string' && value.includes('px') || value.includes('s')) {
121 document.documentElement.style.setProperty(`--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`, value);
122 }
123 });
124
125 // Специфичные настройки
126 if (settings.shortcuts) this.enableKeyboardShortcuts();
127 if (settings.voiceControl) this.enableVoiceControl();
128 if (settings.richAnimations) this.enableRichAnimations();
129 if (settings.gameElements) this.enableGameElements();
130 }
131
132 console.log(`👤 Применена персона: ${persona}`);
133 }
134
135 // === ПОДКЛЮЧЕНИЕ К СИСТЕМЕ ===
136 connectToSystem() {
137 const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
138 const wsUrl = `${wsProtocol}//${window.location.hostname}:81`;
139
140 try {
141 this.socket = new WebSocket(wsUrl);
142
143 this.socket.onopen = () => {
144 this.isOnline = true;
145 this.updateConnectionStatus('🟢 Подключен к системе', 'online');
146 console.log('🔗 WebSocket подключен');
147
148 // Отправляем информацию о пользователе
149 this.socket.send(JSON.stringify({
150 type: 'user_info',
151 persona: this.currentPersona,
152 capabilities: this.deviceCapabilities,
153 preferences: this.userPreferences
154 }));
155 };
156
157 this.socket.onmessage = (event) => {
158 this.handleSystemMessage(event.data);
159 };
160
161 this.socket.onclose = () => {
162 this.isOnline = false;
163 this.updateConnectionStatus('🔴 Переподключение...', 'reconnecting');
164 console.log('📡 WebSocket отключен, переподключаюсь...');
165
166 // Автоматическое переподключение
167 setTimeout(() => this.connectToSystem(), 3000);
168 };
169
170 this.socket.onerror = (error) => {
171 console.error('❌ Ошибка WebSocket:', error);
172 this.updateConnectionStatus('🔴 Ошибка соединения', 'error');
173 };
174
175 } catch (error) {
176 console.error('❌ Не удалось подключиться к системе:', error);
177 this.updateConnectionStatus('🔴 Офлайн режим', 'offline');
178 }
179 }
180
181 // === ОБРАБОТКА СООБЩЕНИЙ ОТ СИСТЕМЫ ===
182 handleSystemMessage(message) {
183 try {
184 const data = JSON.parse(message);
185
186 switch (data.type) {
187 case 'sensor_update':
188 this.updateSensorData(data);
189 break;
190 case 'system_alert':
191 this.showAlert(data.message, data.level);
192 break;
193 case 'suggestion':
194 this.showSuggestion(data.suggestion);
195 break;
196 case 'mood_update':
197 this.updateSystemMood(data.mood);
198 break;
199 default:
200 console.log('💬 Сообщение от системы:', message);
201 }
202 } catch (e) {
203 // Обычное текстовое сообщение
204 console.log('📝 Текст от системы:', message);
205 }
206 }
207
208 // === ОБНОВЛЕНИЕ ДАННЫХ ДАТЧИКОВ ===
209 updateSensorData(data) {
210 const oldData = { ...this.sensorData };
211 this.sensorData = data;
212
213 // Анимированное обновление каждого датчика
214 this.animateValueUpdate('temperature', data.temperature + '°C', oldData.temperature);
215 this.animateValueUpdate('light', Math.round((data.light / 4095) * 100) + '%', oldData.light);
216 this.animateValueUpdate('sound', Math.round((data.sound / 4095) * 100) + 'дБ', oldData.sound);
217 this.animateValueUpdate('motion', data.motion ? 'Есть' : 'Нет', oldData.motion);
218
219 // Обновляем прогресс-бары
220 this.updateProgressBar('tempProgress', data.temperature, 0, 40);
221 this.updateProgressBar('lightProgress', (data.light / 4095) * 100, 0, 100);
222 this.updateProgressBar('soundProgress', (data.sound / 4095) * 100, 0, 100);
223 this.updateProgressBar('motionProgress', data.motion ? 100 : 0, 0, 100);
224
225 // Обновляем статусы
226 this.updateSensorStatus('tempStatus', this.getTemperatureStatus(data.temperature));
227 this.updateSensorStatus('lightStatus', this.getLightStatus((data.light / 4095) * 100));
228 this.updateSensorStatus('soundStatus', this.getSoundStatus((data.sound / 4095) * 100));
229 this.updateSensorStatus('motionStatus', this.getMotionStatus(data.motion));
230
231 // Проверяем нужны ли предложения
232 this.checkForSuggestions(data);
233
234 // Адаптируем интерфейс под условия
235 this.adaptInterfaceToConditions(data);
236 }
237
238 // === АНИМИРОВАННОЕ ОБНОВЛЕНИЕ ЗНАЧЕНИЙ ===
239 animateValueUpdate(elementId, newValue, oldValue) {
240 const element = document.getElementById(elementId);
241 const currentValue = element.textContent;
242
243 if (currentValue !== newValue) {
244 // Создаем эффект изменения
245 element.style.transform = 'scale(1.1)';
246 element.style.color = '#FFD93D';
247
248 // Добавляем вибрацию на мобильных
249 if (this.deviceCapabilities.vibration && oldValue !== undefined) {
250 navigator.vibrate(50);
251 }
252
253 setTimeout(() => {
254 element.textContent = newValue;
255 element.style.transform = 'scale(1)';
256 element.style.color = '';
257
258 // Подсвечиваем родительскую карточку
259 const card = element.closest('.sensor-card');
260 if (card) {
261 card.style.boxShadow = '0 0 30px rgba(255, 217, 61, 0.5)';
262 setTimeout(() => {
263 card.style.boxShadow = '';
264 }, 800);
265 }
266 }, 200);
267
268 // Обновляем ARIA атрибуты для скринридеров
269 element.setAttribute('aria-live', 'polite');
270 }
271 }
272
273 // === ОБНОВЛЕНИЕ ПРОГРЕСС-БАРОВ ===
274 updateProgressBar(progressId, value, min, max) {
275 const progressElement = document.getElementById(progressId);
276 const percentage = Math.max(0, Math.min(100, ((value - min) / (max - min)) * 100));
277
278 // Плавная анимация изменения
279 progressElement.style.width = percentage + '%';
280
281 // Обновляем ARIA атрибуты
282 const progressBar = progressElement.closest('[role="progressbar"]');
283 if (progressBar) {
284 progressBar.setAttribute('aria-valuenow', Math.round(percentage));
285 }
286 }
287
288 // === УМНЫЕ СТАТУСЫ ДАТЧИКОВ ===
289 getTemperatureStatus(temp) {
290 if (temp < 18) return { message: '🥶 Холодно! Предлагаю включить обогрев', color: '#4FC3F7', action: 'heating' };
291 if (temp > 26) return { message: '🔥 Жарко! Нужно охлаждение', color: '#FF7043', action: 'cooling' };
292 if (temp >= 20 && temp <= 24) return { message: '😌 Идеальная температура', color: '#66BB6A', action: null };
293 return { message: '🌡️ Нормальная температура', color: '#FFD54F', action: null };
294 }
295
296 getLightStatus(light) {
297 if (light < 20) return { message: '🌙 Темно! Включить освещение?', color: '#9575CD', action: 'lighting' };
298 if (light > 80) return { message: '☀️ Очень светло', color: '#FFB74D', action: null };
299 return { message: '👍 Комфортное освещение', color: '#81C784', action: null };
300 }
301
302 getSoundStatus(sound) {
303 if (sound < 20) return { message: '🤫 Очень тихо', color: '#4DB6AC', action: null };
304 if (sound > 70) return { message: '📢 Шумно! Попросить потише?', color: '#E57373', action: 'noise_control' };
305 return { message: '🎵 Комфортный уровень звука', color: '#AED581', action: null };
306 }
307
308 getMotionStatus(motion) {
309 return motion ?
310 { message: '👥 Люди в классе', color: '#64B5F6', action: null } :
311 { message: '🏫 Класс пустой - эко-режим?', color: '#90A4AE', action: 'eco_mode' };
312 }
313
314 // === ОБНОВЛЕНИЕ СТАТУСОВ ===
315 updateSensorStatus(statusId, status) {
316 const statusElement = document.getElementById(statusId);
317 statusElement.textContent = status.message;
318 statusElement.style.color = status.color;
319
320 // Если есть предлагаемое действие, показываем кнопку
321 if (status.action) {
322 this.showActionSuggestion(statusId, status.action, status.message);
323 }
324 }
325
326 // === ПРЕДЛОЖЕНИЯ ДЕЙСТВИЙ ===
327 showActionSuggestion(statusId, action, message) {
328 const statusElement = document.getElementById(statusId);
329 const existingSuggestion = statusElement.parentElement.querySelector('.action-suggestion');
330
331 if (existingSuggestion) {
332 existingSuggestion.remove();
333 }
334
335 const suggestionButton = document.createElement('button');
336 suggestionButton.className = 'action-suggestion';
337 suggestionButton.innerHTML = '✨ Исправить';
338 suggestionButton.style.cssText = `
339 margin-top: 8px;
340 padding: 6px 12px;
341 background: rgba(255,255,255,0.2);
342 border: 1px solid rgba(255,255,255,0.3);
343 border-radius: 15px;
344 color: white;
345 font-size: 12px;
346 cursor: pointer;
347 transition: all 0.3s ease;
348 `;
349
350 suggestionButton.addEventListener('click', () => {
351 this.executeQuickAction(action);
352 suggestionButton.remove();
353 });
354
355 suggestionButton.addEventListener('mouseenter', () => {
356 suggestionButton.style.background = 'rgba(255,255,255,0.3)';
357 suggestionButton.style.transform = 'scale(1.05)';
358 });
359
360 suggestionButton.addEventListener('mouseleave', () => {
361 suggestionButton.style.background = 'rgba(255,255,255,0.2)';
362 suggestionButton.style.transform = 'scale(1)';
363 });
364
365 statusElement.parentElement.appendChild(suggestionButton);
366 }
367
368 // === БЫСТРЫЕ ДЕЙСТВИЯ ===
369 async executeQuickAction(action) {
370 const actionMap = {
371 'heating': () => this.executeSmartMode('comfort'),
372 'cooling': () => this.executeSmartMode('cool'),
373 'lighting': () => this.executeSmartMode('bright'),
374 'noise_control': () => this.showNoiseAlert(),
375 'eco_mode': () => this.executeSmartMode('eco')
376 };
377
378 if (actionMap[action]) {
379 await actionMap[action]();
380 }
381 }
382
383 // === НАСТРОЙКА ВЗАИМОДЕЙСТВИЙ ===
384 setupInteractions() {
385 // Обработчики кнопок управления
386 document.querySelectorAll('.smart-button').forEach(button => {
387 this.setupSmartButton(button);
388 });
389
390 // Обработчики карточек датчиков
391 document.querySelectorAll('.sensor-card').forEach(card => {
392 this.setupSensorCard(card);
393 });
394
395 // Клавиатурные сокращения
396 this.setupKeyboardShortcuts();
397
398 // Жесты
399 if (this.deviceCapabilities.touchGestures) {
400 this.setupTouchGestures();
401 }
402
403 // Голосовое управление
404 if (this.deviceCapabilities.voiceRecognition) {
405 this.setupVoiceControl();
406 }
407
408 // Адаптация к ориентации устройства
409 if (this.deviceCapabilities.deviceMotion) {
410 this.setupOrientationAdaptation();
411 }
412 }
413
414 // === НАСТРОЙКА УМНЫХ КНОПОК ===
415 setupSmartButton(button) {
416 const mode = button.dataset.mode;
417
418 button.addEventListener('click', async (e) => {
419 e.preventDefault();
420 await this.executeSmartMode(mode, button);
421 });
422
423 // Предварительный просмотр при наведении
424 button.addEventListener('mouseenter', () => {
425 this.showModePreview(mode, button);
426 });
427
428 button.addEventListener('mouseleave', () => {
429 this.hideModePreview();
430 });
431
432 // Поддержка клавиатуры
433 button.addEventListener('keydown', (e) => {
434 if (e.key === 'Enter' || e.key === ' ') {
435 e.preventDefault();
436 button.click();
437 }
438 });
439 }
440
441 // === ВЫПОЛНЕНИЕ УМНОГО РЕЖИМА ===
442 async executeSmartMode(mode, buttonElement = null) {
443 if (buttonElement) {
444 // Визуальная обратная связь
445 const originalContent = buttonElement.innerHTML;
446 buttonElement.innerHTML = '<div class="button-content"><span class="button-icon">⏳</span><span class="button-text">Применяю...</span></div>';
447 buttonElement.disabled = true;
448
449 // Тактильная обратная связь
450 if (this.deviceCapabilities.vibration) {
451 navigator.vibrate([100, 50, 100]);
452 }
453 }
454
455 try {
456 const response = await fetch(`/api/smart-mode/${mode}`, {
457 method: 'POST',
458 headers: { 'Content-Type': 'application/json' },
459 body: JSON.stringify({
460 currentSensors: this.sensorData,
461 userPreferences: this.userPreferences,
462 persona: this.currentPersona,
463 deviceCapabilities: this.deviceCapabilities,
464 timestamp: Date.now()
465 })
466 });
467
468 const result = await response.json();
469
470 if (result.success) {
471 if (buttonElement) {
472 buttonElement.innerHTML = '<div class="button-content"><span class="button-icon">✅</span><span class="button-text">Готово!</span></div>';
473 buttonElement.style.background = 'linear-gradient(135deg, #4CAF50, #45a049)';
474 }
475
476 // Показываем детали изменений
477 this.showModeResult(mode, result);
478
479 // Обучаем систему на выборе пользователя
480 this.learnFromUserChoice(mode, this.sensorData);
481
482 // Показываем уведомление
483 this.showNotification(`Режим "${this.getModeTitle(mode)}" активирован`, 'success');
484
485 } else {
486 throw new Error(result.message || 'Ошибка выполнения');
487 }
488
489 } catch (error) {
490 console.error('❌ Ошибка выполнения режима:', error);
491
492 if (buttonElement) {
493 buttonElement.innerHTML = '<div class="button-content"><span class="button-icon">❌</span><span class="button-text">Ошибка</span></div>';
494 buttonElement.style.background = 'linear-gradient(135deg, #f44336, #d32f2f)';
495 }
496
497 this.showNotification('Ошибка выполнения команды', 'error');
498 } finally {
499 // Возвращаем кнопку в исходное состояние
500 if (buttonElement) {
501 setTimeout(() => {
502 buttonElement.innerHTML = buttonElement.dataset.originalContent || originalContent;
503 buttonElement.style.background = '';
504 buttonElement.disabled = false;
505 }, 2000);
506 }
507 }
508 }
509
510 // === ПРЕДВАРИТЕЛЬНЫЙ ПРОСМОТР РЕЖИМА ===
511 showModePreview(mode, button) {
512 const preview = this.getModePreview(mode);
513
514 // Создаем всплывающую подсказку
515 const tooltip = document.createElement('div');
516 tooltip.className = 'mode-preview-tooltip';
517 tooltip.innerHTML = `
518 <h4>${preview.title}</h4>
519 <p>${preview.description}</p>
520 <ul>
521 ${preview.effects.map(effect => `<li>${effect}</li>`).join('')}
522 </ul>
523 `;
524
525 tooltip.style.cssText = `
526 position: absolute;
527 bottom: 100%;
528 left: 50%;
529 transform: translateX(-50%);
530 background: rgba(0,0,0,0.9);
531 color: white;
532 padding: 15px;
533 border-radius: 10px;
534 font-size: 14px;
535 max-width: 250px;
536 z-index: 1000;
537 animation: fadeInUp 0.3s ease;
538 box-shadow: 0 5px 20px rgba(0,0,0,0.3);
539 `;
540
541 button.style.position = 'relative';
542 button.appendChild(tooltip);
543 }
544
545 hideModePreview() {
546 document.querySelectorAll('.mode-preview-tooltip').forEach(tooltip => {
547 tooltip.style.animation = 'fadeOut 0.2s ease';
548 setTimeout(() => tooltip.remove(), 200);
549 });
550 }
551
552 getModePreview(mode) {
553 const previews = {
554 comfort: {
555 title: '😌 Комфортный режим',
556 description: 'Создаю идеальные условия для отдыха и работы',
557 effects: ['🌡️ Температура 22°C', '💡 Мягкое освещение', '🔇 Контроль шума']
558 },
559 eco: {
560 title: '🌱 Эко-режим',
561 description: 'Экономлю энергию с умом',
562 effects: ['⚡ Снижение потребления на 40%', '🌍 Забота об экологии', '💚 Умная оптимизация']
563 },
564 focus: {
565 title: '🎯 Режим концентрации',
566 description: 'Оптимальные условия для учебы',
567 effects: ['🧠 Температура для мозга 21°C', '📚 Яркое освещение', '🤫 Минимум отвлечений']
568 },
569 party: {
570 title: '🎉 Вечеринка',
571 description: 'Создаю атмосферу веселья',
572 effects: ['🌟 Яркое освещение', '🎵 Разрешен шум', '💃 Энергичная атмосфера']
573 },
574 presentation: {
575 title: '📽️ Презентация',
576 description: 'Готовлю класс для показа',
577 effects: ['🔅 Приглушенный свет', '👥 Комфорт для аудитории', '📊 Фокус на экране']
578 }
579 };
580
581 return previews[mode] || { title: mode, description: 'Специальный режим', effects: [] };
582 }
583
584
585
586 // === НАСТРОЙКА КАРТОЧЕК ДАТЧИКОВ ===
587 setupSensorCard(card) {
588 const sensorType = card.dataset.sensor;
589
590 card.addEventListener('click', () => {
591 this.showSensorDetails(sensorType);
592 });
593
594 // Двойной клик для быстрой калибровки
595 card.addEventListener('dblclick', () => {
596 this.calibrateSensor(sensorType);
597 });
598
599 // Поддержка клавиатуры
600 card.addEventListener('keydown', (e) => {
601 if (e.key === 'Enter') {
602 this.showSensorDetails(sensorType);
603 }
604 });
605 }
606
607 showSensorDetails(sensorType) {
608 // Показываем детальную аналитику датчика
609 console.log(`📊 Детали датчика: ${sensorType}`);
610
611 const modal = this.createModal(`
612 <h3>📊 Аналитика датчика: ${this.getSensorName(sensorType)}</h3>
613 <div class="sensor-analytics">
614 <div class="chart-container">
615 <canvas id="sensorChart" width="300" height="200"></canvas>
616 </div>
617 <div class="stats">
618 <p><strong>Текущее значение:</strong> <span id="currentValue">--</span></p>
619 <p><strong>Среднее за день:</strong> <span id="avgValue">--</span></p>
620 <p><strong>Минимум:</strong> <span id="minValue">--</span></p>
621 <p><strong>Максимум:</strong> <span id="maxValue">--</span></p>
622 </div>
623 <button onclick="smartInterface.calibrateSensor('${sensorType}')" class="calibrate-btn">
624 🔧 Калибровать датчик
625 </button>
626 </div>
627 `);
628
629 // Здесь можно добавить реальную визуализацию данных
630 this.drawSensorChart(sensorType);
631 }
632
633 getSensorName(type) {
634 const names = {
635 temperature: '🌡️ Температура',
636 light: '💡 Освещенность',
637 sound: '🔊 Звук',
638 motion: '🚶 Движение'
639 };
640 return names[type] || type;
641 }
642
643 // === ГОЛОСОВОЕ УПРАВЛЕНИЕ ===
644 setupVoiceControl() {
645 if (!this.deviceCapabilities.voiceRecognition) return;
646
647 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
648 this.recognition = new SpeechRecognition();
649
650 this.recognition.lang = 'ru-RU';
651 this.recognition.continuous = false;
652 this.recognition.interimResults = false;
653
654 this.recognition.onresult = (event) => {
655 const command = event.results[0][0].transcript.toLowerCase();
656 this.processVoiceCommand(command);
657 };
658
659 // Кнопка активации голосового управления
660 const voiceButton = this.createVoiceButton();
661 document.body.appendChild(voiceButton);
662 }
663
664 processVoiceCommand(command) {
665 console.log(`🎤 Голосовая команда: ${command}`);
666
667 const commandMap = {
668 'комфорт': () => this.executeSmartMode('comfort'),
669 'эко режим': () => this.executeSmartMode('eco'),
670 'фокус': () => this.executeSmartMode('focus'),
671 'вечеринка': () => this.executeSmartMode('party'),
672 'презентация': () => this.executeSmartMode('presentation'),
673 'включить свет': () => this.executeQuickAction('lighting'),
674 'включить обогрев': () => this.executeQuickAction('heating'),
675 'статус системы': () => this.showSystemStatus()
676 };
677
678 for (const [pattern, action] of Object.entries(commandMap)) {
679 if (command.includes(pattern)) {
680 action();
681 this.speak(`Выполняю команду: ${pattern}`);
682 return;
683 }
684 }
685
686 this.speak('Команда не распознана. Повторите, пожалуйста.');
687 }
688
689 speak(text) {
690 const utterance = new SpeechSynthesisUtterance(text);
691 utterance.lang = 'ru-RU';
692 speechSynthesis.speak(utterance);
693 }
694
695 // === ЖЕСТОВОЕ УПРАВЛЕНИЕ ===
696 setupTouchGestures() {
697 let startX, startY, endX, endY;
698
699 document.addEventListener('touchstart', (e) => {
700 startX = e.touches[0].clientX;
701 startY = e.touches[0].clientY;
702 });
703
704 document.addEventListener('touchend', (e) => {
705 endX = e.changedTouches[0].clientX;
706 endY = e.changedTouches[0].clientY;
707
708 this.handleGesture(startX, startY, endX, endY);
709 });
710 }
711
712 handleGesture(startX, startY, endX, endY) {
713 const deltaX = endX - startX;
714 const deltaY = endY - startY;
715 const threshold = 100;
716
717 if (Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold) {
718 if (Math.abs(deltaX) > Math.abs(deltaY)) {
719 // Горизонтальный свайп
720 if (deltaX > 0) {
721 this.executeSmartMode('comfort'); // Свайп вправо = комфорт
722 } else {
723 this.executeSmartMode('eco'); // Свайп влево = эко
724 }
725 } else {
726 // Вертикальный свайп
727 if (deltaY < 0) {
728 this.showSystemStatus(); // Свайп вверх = статус
729 } else {
730 this.hideAllModals(); // Свайп вниз = закрыть
731 }
732 }
733 }
734 }
735
736 // === АДАПТАЦИЯ К УСЛОВИЯМ ===
737 adaptInterfaceToConditions(data) {
738 // Меняем тему в зависимости от времени суток
739 const hour = new Date().getHours();
740 let theme = 'default';
741
742 if (hour >= 6 && hour < 12) {
743 theme = 'morning';
744 } else if (hour >= 12 && hour < 18) {
745 theme = 'day';
746 } else if (hour >= 18 && hour < 22) {
747 theme = 'evening';
748 } else {
749 theme = 'night';
750 }
751
752 document.body.className = `theme-${theme}`;
753
754 // Адаптируем яркость интерфейса к освещенности
755 const lightLevel = (data.light / 4095) * 100;
756 if (lightLevel < 30) {
757 document.documentElement.style.setProperty('--background-opacity', '0.05');
758 document.documentElement.style.setProperty('--text-color', '#ffffff');
759 } else {
760 document.documentElement.style.setProperty('--background-opacity', '0.1');
761 document.documentElement.style.setProperty('--text-color', '#ffffff');
762 }
763 }
764
765 // === УВЕДОМЛЕНИЯ И МОДАЛЬНЫЕ ОКНА ===
766 showNotification(message, type = 'info') {
767 const notification = document.createElement('div');
768 notification.className = `notification ${type}`;
769 notification.innerHTML = `
770 <div class="notification-content">
771 <span class="notification-icon">${this.getNotificationIcon(type)}</span>
772 <span class="notification-text">${message}</span>
773 <button class="notification-close" onclick="this.parentElement.parentElement.remove()">×</button>
774 </div>
775 `;
776
777 notification.style.cssText = `
778 position: fixed;
779 top: 20px;
780 right: 20px;
781 background: ${this.getNotificationColor(type)};
782 color: white;
783 padding: 15px 20px;
784 border-radius: 10px;
785 box-shadow: 0 5px 20px rgba(0,0,0,0.3);
786 z-index: 1000;
787 animation: slideInRight 0.5s ease;
788 max-width: 300px;
789 `;
790
791 document.body.appendChild(notification);
792
793 // Автоудаление через 5 секунд
794 setTimeout(() => {
795 if (notification.parentElement) {
796 notification.style.animation = 'slideOutRight 0.5s ease';
797 setTimeout(() => notification.remove(), 500);
798 }
799 }, 5000);
800
801 // Push-уведомление если разрешено
802 if (this.deviceCapabilities.notifications && Notification.permission === 'granted') {
803 new Notification('🤖 ALEX', {
804 body: message,
805 icon: '/icon-192.png'
806 });
807 }
808 }
809
810 getNotificationIcon(type) {
811 const icons = {
812 success: '✅',
813 error: '❌',
814 warning: '⚠️',
815 info: 'ℹ️'
816 };
817 return icons[type] || 'ℹ️';
818 }
819
820 getNotificationColor(type) {
821 const colors = {
822 success: 'linear-gradient(135deg, #4CAF50, #45a049)',
823 error: 'linear-gradient(135deg, #f44336, #d32f2f)',
824 warning: 'linear-gradient(135deg, #ff9800, #f57c00)',
825 info: 'linear-gradient(135deg, #2196F3, #1976D2)'
826 };
827 return colors[type] || colors.info;
828 }
829
830 createModal(content) {
831 const modal = document.createElement('div');
832 modal.className = 'modal-overlay';
833 modal.innerHTML = `
834 <div class="modal-content">
835 <button class="modal-close" onclick="this.closest('.modal-overlay').remove()">×</button>
836 ${content}
837 </div>
838 `;
839
840 modal.style.cssText = `
841 position: fixed;
842 top: 0;
843 left: 0;
844 width: 100%;
845 height: 100%;
846 background: rgba(0,0,0,0.8);
847 display: flex;
848 justify-content: center;
849 align-items: center;
850 z-index: 2000;
851 animation: fadeIn 0.3s ease;
852 `;
853
854 document.body.appendChild(modal);
855 return modal;
856 }
857
858 // === СОХРАНЕНИЕ ПРЕДПОЧТЕНИЙ ===
859 saveUserPreferences() {
860 localStorage.setItem('alexUserPreferences', JSON.stringify(this.userPreferences));
861 }
862
863 loadUserPreferences() {
864 const stored = localStorage.getItem('alexUserPreferences');
865 return stored ? JSON.parse(stored) : {
866 preferredMode: 'comfort',
867 notifications: true,
868 voiceControl: false,
869 animations: true,
870 theme: 'auto'
871 };
872 }
873
874 learnFromUserChoice(mode, sensorData) {
875 // Обучение на основе выбора пользователя
876 this.userPreferences.preferredMode = mode;
877 this.userPreferences.lastUsed = Date.now();
878
879 // Запоминаем контекст выбора
880 if (!this.userPreferences.contextualChoices) {
881 this.userPreferences.contextualChoices = [];
882 }
883
884 this.userPreferences.contextualChoices.push({
885 mode: mode,
886 temperature: sensorData.temperature,
887 light: sensorData.light,
888 time: new Date().getHours(),
889 timestamp: Date.now()
890 });
891
892 // Ограничиваем историю последними 50 выборами
893 if (this.userPreferences.contextualChoices.length > 50) {
894 this.userPreferences.contextualChoices = this.userPreferences.contextualChoices.slice(-50);
895 }
896
897 this.saveUserPreferences();
898 console.log('🧠 Система изучила предпочтения пользователя');
899 }
900
901 // === ОБНОВЛЕНИЕ СТАТУСА ===
902 updateConnectionStatus(status, type) {
903 document.getElementById('systemStatus').textContent = status;
904
905 const statusDot = document.getElementById('statusDot');
906 const colors = {
907 online: '#4CAF50',
908 offline: '#f44336',
909 reconnecting: '#ff9800',
910 error: '#f44336'
911 };
912
913 statusDot.style.backgroundColor = colors[type] || colors.offline;
914 }
915
916 updateBatteryIndicator() {
917 if (!this.battery) return;
918
919 const batteryElement = document.getElementById('batteryLevel');
920 const level = Math.round(this.battery.level * 100);
921
922 let icon = '🔋';
923 if (level < 20) icon = '🪫';
924 else if (level < 50) icon = '🔋';
925 else icon = '🔋';
926
927 if (this.battery.charging) icon = '⚡';
928
929 batteryElement.textContent = icon;
930 batteryElement.title = `Батарея: ${level}%`;
931 }
932
933 showWelcomeMessage() {
934 const persona = this.currentPersona;
935 const messages = {
936 teacher: '👋 Добро пожаловать! Готов помочь с управлением классом.',
937 student: '🎉 Привет! Давай изучать умные технологии вместе!',
938 janitor: '🔧 Здравствуйте! Помогу следить за всеми системами.'
939 };
940
941 setTimeout(() => {
942 this.showNotification(messages[persona] || messages.student, 'info');
943 }, 1000);
944 }
945}
946
947// === ИНИЦИАЛИЗАЦИЯ ===
948let smartInterface;
949
950document.addEventListener('DOMContentLoaded', function() {
951 smartInterface = new SmartInterface();
952});
953
954// === ДОПОЛНИТЕЛЬНЫЕ CSS АНИМАЦИИ ===
955const additionalStyles = `
956 @keyframes slideInRight {
957 from { transform: translateX(100%); opacity: 0; }
958 to { transform: translateX(0); opacity: 1; }
959 }
960
961 @keyframes slideOutRight {
962 from { transform: translateX(0); opacity: 1; }
963 to { transform: translateX(100%); opacity: 0; }
964 }
965
966 @keyframes fadeIn {
967 from { opacity: 0; }
968 to { opacity: 1; }
969 }
970
971 .modal-content {
972 background: white;
973 padding: 30px;
974 border-radius: 15px;
975 max-width: 500px;
976 max-height: 80vh;
977 overflow-y: auto;
978 position: relative;
979 color: #333;
980 }
981
982 .modal-close {
983 position: absolute;
984 top: 10px;
985 right: 15px;
986 background: none;
987 border: none;
988 font-size: 24px;
989 cursor: pointer;
990 color: #999;
991 }
992
993 .notification-content {
994 display: flex;
995 align-items: center;
996 gap: 10px;
997 }
998
999 .notification-close {
1000 background: none;
1001 border: none;
1002 color: white;
1003 font-size: 18px;
1004 cursor: pointer;
1005 margin-left: auto;
1006 }
1007`;
1008
1009// Добавляем дополнительные стили
1010const styleSheet = document.createElement('style');
1011styleSheet.textContent = additionalStyles;
1012document.head.appendChild(styleSheet);
Занятие 3: “PWA и адаптивность” 📱
Длительность: 90 минут
Создание Progressive Web App (PWA):
1// manifest.json
2{
3 "name": "ALEX - Умная система класса",
4 "short_name": "ALEX",
5 "description": "Интеллектуальная система управления классом",
6 "start_url": "/",
7 "display": "standalone",
8 "theme_color": "#667eea",
9 "background_color": "#667eea",
10 "orientation": "portrait-primary",
11 "icons": [
12 {
13 "src": "/icon-192.png",
14 "sizes": "192x192",
15 "type": "image/png"
16 },
17 {
18 "src": "/icon-512.png",
19 "sizes": "512x512",
20 "type": "image/png"
21 }
22 ],
23 "categories": ["education", "productivity"],
24 "screenshots": [
25 {
26 "src": "/screenshot1.png",
27 "sizes": "1080x1920",
28 "type": "image/png"
29 }
30 ]
31}
Service Worker для офлайн работы:
1// sw.js
2const CACHE_NAME = 'alex-v1.0.0';
3const urlsToCache = [
4 '/',
5 '/styles.css',
6 '/script.js',
7 '/manifest.json',
8 '/icon-192.png',
9 '/icon-512.png'
10];
11
12self.addEventListener('install', event => {
13 event.waitUntil(
14 caches.open(CACHE_NAME)
15 .then(cache => cache.addAll(urlsToCache))
16 );
17});
18
19self.addEventListener('fetch', event => {
20 event.respondWith(
21 caches.match(event.request)
22 .then(response => {
23 return response || fetch(event.request);
24 }
25 )
26 );
27});
Занятие 4: “Тестирование и оптимизация” 🧪
Длительность: 90 минут
A/B тестирование интерфейсов:
- Дети создают 2 версии одного элемента
- Тестируют на разных пользователях
- Анализируют какой вариант лучше
Оптимизация производительности:
- Минификация CSS/JS
- Ленивая загрузка изображений
- Кэширование данных
🎯 ИТОГИ СПРИНТА 18
Ключевые достижения:
✅ Интерактивный интерфейс - живое взаимодействие с пользователем
✅ Адаптивность - подстройка под разные типы пользователей
✅ Мультимодальность - голос, жесты, клавиатура, касания
✅ PWA - веб-приложение как нативное мобильное
✅ Персонализация - система запоминает предпочтения
✅ Доступность - интерфейс для всех категорий пользователей
Концептуальные прорывы:
- UX-мышление - понимание потребностей пользователя
- Эмпатический дизайн - технология с человеческим лицом
- Адаптивные системы - интерфейс как живой организм
- Мультимодальное взаимодействие - общение на языке пользователя
Технические навыки:
- Продвинутый JavaScript и CSS3
- Web APIs и PWA технологии
- Accessibility и инклюзивный дизайн
- UX/UI принципы и методологии
🚀 ПОДГОТОВКА К СПРИНТУ 19
Мостик к машинному обучению:
“Наш интерфейс умеет адаптироваться, но что если он научится предсказывать желания пользователей и самостоятельно предлагать оптимальные решения?”
Фундамент для AI:
- ✅ Сбор данных о поведении пользователей
- ✅ Контекстное понимание ситуаций
- ✅ Персонализация и обучение на предпочтениях
- ✅ Предиктивные предложения действий
Спринт 18 завершен! 📱
Дети создали по-настоящему живой интерфейс, который понимает пользователей и адаптируется под них!
Готов к анализу Спринта 19: “Введение в Machine Learning”! 🤖🧠