[{"data":1,"prerenderedAt":20806},["ShallowReactive",2],{"navigation_docs":3,"-csharp-network-programming-websockets":3302,"-csharp-network-programming-websockets-surround":20801},[4,1707,1896,2350,2531,2572,2779,2901,2951,3008,3042,3168,3245,3298],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"C#","i-devicon-csharp","\u002Fcsharp","01.csharp",[10,13,60,90,120,202,219,253,379,404,457,650,1364,1654,1703],{"title":11,"path":7,"stem":12},"C# та .NET","01.csharp\u002Findex",{"title":14,"icon":15,"path":16,"stem":17,"children":18,"page":59},"Fundamentals","i-lucide-book-open","\u002Fcsharp\u002Ffundamentals","01.csharp\u002F01.fundamentals",[19,23,27,31,35,39,43,47,51,55],{"title":20,"path":21,"stem":22},"Вступ до екосистеми .NET","\u002Fcsharp\u002Ffundamentals\u002Fintroduction-to-ecosystem","01.csharp\u002F01.fundamentals\u002F01.introduction-to-ecosystem",{"title":24,"path":25,"stem":26},"Структура програми на C#","\u002Fcsharp\u002Ffundamentals\u002Fprogram-structure","01.csharp\u002F01.fundamentals\u002F02.program-structure",{"title":28,"path":29,"stem":30},"Змінні та Типи Даних","\u002Fcsharp\u002Ffundamentals\u002Fvariables-data-types","01.csharp\u002F01.fundamentals\u002F03.variables-data-types",{"title":32,"path":33,"stem":34},"Масиви","\u002Fcsharp\u002Ffundamentals\u002Farrays","01.csharp\u002F01.fundamentals\u002F04.arrays",{"title":36,"path":37,"stem":38},"Strings & Text Handling","\u002Fcsharp\u002Ffundamentals\u002Fstrings-text-handling","01.csharp\u002F01.fundamentals\u002F05.strings-text-handling",{"title":40,"path":41,"stem":42},"Дати і Час","\u002Fcsharp\u002Ffundamentals\u002Fdates-time-handling","01.csharp\u002F01.fundamentals\u002F06.dates-time-handling",{"title":44,"path":45,"stem":46},"Потік Керування","\u002Fcsharp\u002Ffundamentals\u002Fcontrol-flow","01.csharp\u002F01.fundamentals\u002F07.control-flow",{"title":48,"path":49,"stem":50},"Методи","\u002Fcsharp\u002Ffundamentals\u002Fmethods","01.csharp\u002F01.fundamentals\u002F08.methods",{"title":52,"path":53,"stem":54},"Основи Відлагодження","\u002Fcsharp\u002Ffundamentals\u002Fdebugging-basics","01.csharp\u002F01.fundamentals\u002F09.debugging-basics",{"title":56,"path":57,"stem":58},"Інтерактивна Консоль (Classic)","\u002Fcsharp\u002Ffundamentals\u002Finteractive-console","01.csharp\u002F01.fundamentals\u002F10.interactive-console",false,{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":59},"OOP","i-lucide-box","\u002Fcsharp\u002Foop","01.csharp\u002F02.oop",[66,70,74,78,82,86],{"title":67,"path":68,"stem":69},"Package Management (Управління Пакетами)","\u002Fcsharp\u002Foop\u002Fpackage-management","01.csharp\u002F02.oop\u002F01.package-management",{"title":71,"path":72,"stem":73},"Класи та Об'єкти","\u002Fcsharp\u002Foop\u002Fclasses-objects","01.csharp\u002F02.oop\u002F02.classes-objects",{"title":75,"path":76,"stem":77},"Властивості та Поля","\u002Fcsharp\u002Foop\u002Fproperties-fields","01.csharp\u002F02.oop\u002F03.properties-fields",{"title":79,"path":80,"stem":81},"Стовпи ООП","\u002Fcsharp\u002Foop\u002Foop-pillars","01.csharp\u002F02.oop\u002F04.oop-pillars",{"title":83,"path":84,"stem":85},"Advanced Types","\u002Fcsharp\u002Foop\u002Fadvanced-types","01.csharp\u002F02.oop\u002F05.advanced-types",{"title":87,"path":88,"stem":89},"Namespaces (Простори Імен)","\u002Fcsharp\u002Foop\u002Fnamespaces","01.csharp\u002F02.oop\u002F06.namespaces",{"title":91,"icon":92,"path":93,"stem":94,"children":95,"page":59},"Advanced Core","i-lucide-zap","\u002Fcsharp\u002Fadvanced-core","01.csharp\u002F03.advanced-core",[96,100,104,108,112,116],{"title":97,"path":98,"stem":99},"Generics (Узагальнення)","\u002Fcsharp\u002Fadvanced-core\u002Fgenerics","01.csharp\u002F03.advanced-core\u002F01.generics",{"title":101,"path":102,"stem":103},"Делегати, Події та Лямбда-вирази","\u002Fcsharp\u002Fadvanced-core\u002Fdelegates-events-lambdas","01.csharp\u002F03.advanced-core\u002F02.delegates-events-lambdas",{"title":105,"path":106,"stem":107},"Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)","\u002Fcsharp\u002Fadvanced-core\u002Finterfaces-deep-dive","01.csharp\u002F03.advanced-core\u002F03.interfaces-deep-dive",{"title":109,"path":110,"stem":111},"Обробка Винятків","\u002Fcsharp\u002Fadvanced-core\u002Fexception-handling","01.csharp\u002F03.advanced-core\u002F04.exception-handling",{"title":113,"path":114,"stem":115},"Pattern Matching","\u002Fcsharp\u002Fadvanced-core\u002Fpattern-matching","01.csharp\u002F03.advanced-core\u002F05.pattern-matching",{"title":117,"path":118,"stem":119},"Додаткові Можливості C#","\u002Fcsharp\u002Fadvanced-core\u002Fadditional-features","01.csharp\u002F03.advanced-core\u002F06.additional-features",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":59},"Architecture Best Practices","i-lucide-building-2","\u002Fcsharp\u002Farchitecture-best-practices","01.csharp\u002F04.architecture-best-practices",[126,130,149,153,157,161,165,169],{"title":127,"path":128,"stem":129},"Software Design Principles (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fsoftware-design-principles","01.csharp\u002F04.architecture-best-practices\u002F01.software-design-principles",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":59},"Design Patterns","i-lucide-folder","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns",[136],{"title":137,"icon":132,"path":138,"stem":139,"children":140,"page":59},"Creational","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational",[141,145],{"title":142,"path":143,"stem":144},"Singleton (Одинак)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fsingleton","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F01.singleton",{"title":146,"path":147,"stem":148},"Builder (Будівельник)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fbuilder","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F02.builder",{"title":150,"path":151,"stem":152},"Building Professional CLIs","\u002Fcsharp\u002Farchitecture-best-practices\u002Fbuilding-professional-clis","01.csharp\u002F04.architecture-best-practices\u002F03.building-professional-clis",{"title":154,"path":155,"stem":156},"Validation & Flow Control","\u002Fcsharp\u002Farchitecture-best-practices\u002Fvalidation-flow-control","01.csharp\u002F04.architecture-best-practices\u002F04.validation-flow-control",{"title":158,"path":159,"stem":160},"The Modern .NET Host (Microsoft.Extensions)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fmodern-dotnet-host","01.csharp\u002F04.architecture-best-practices\u002F05.modern-dotnet-host",{"title":162,"path":163,"stem":164},"Data Mapper: Repository та DAO патерни (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part1","01.csharp\u002F04.architecture-best-practices\u002F06.data-mapper-part1",{"title":166,"path":167,"stem":168},"Data Mapper: Repository та DAO патерни (Частина 2)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part2","01.csharp\u002F04.architecture-best-practices\u002F07.data-mapper-part2",{"title":170,"icon":132,"path":171,"stem":172,"children":173,"page":59},"Di Ioc","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc",[174,178,182,186,190,194,198],{"title":175,"path":176,"stem":177},"Проблема залежностей та Інверсія Контролю","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fthe-dependency-problem","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F01.the-dependency-problem",{"title":179,"path":180,"stem":181},"Будуємо власний Service Container","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fbuild-your-own-container","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F02.build-your-own-container",{"title":183,"path":184,"stem":185},"Service Locator: Паттерн та Анти-паттерн","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-locator-pattern","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F03.service-locator-pattern",{"title":187,"path":188,"stem":189},"Паттерни Dependency Injection","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdependency-injection-patterns","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F04.dependency-injection-patterns",{"title":191,"path":192,"stem":193},"Microsoft DI: IServiceCollection та IServiceProvider","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fmicrosoft-di-deep-dive","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F05.microsoft-di-deep-dive",{"title":195,"path":196,"stem":197},"Service Lifetimes та Scopes","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-lifetimes-and-scopes","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F06.service-lifetimes-and-scopes",{"title":199,"path":200,"stem":201},"DI Анти-паттерни та Найкращі Практики","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdi-anti-patterns-and-best-practices","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F07.di-anti-patterns-and-best-practices",{"title":203,"icon":132,"path":204,"stem":205,"children":206,"page":59},"Standard Library","\u002Fcsharp\u002Fstandard-library","01.csharp\u002F05.standard-library",[207,211,215],{"title":208,"path":209,"stem":210},"Collections (Колекції)","\u002Fcsharp\u002Fstandard-library\u002Fcollections","01.csharp\u002F05.standard-library\u002F01.collections",{"title":212,"path":213,"stem":214},"High Performance Types (Високопродуктивні Типи)","\u002Fcsharp\u002Fstandard-library\u002Fhigh-performance-types","01.csharp\u002F05.standard-library\u002F02.high-performance-types",{"title":216,"path":217,"stem":218},"LINQ (Language Integrated Query)","\u002Fcsharp\u002Fstandard-library\u002Flinq","01.csharp\u002F05.standard-library\u002F03.linq",{"title":220,"icon":221,"path":222,"stem":223,"children":224,"page":59},"System Internals Concurrency","i-lucide-server","\u002Fcsharp\u002Fsystem-internals-concurrency","01.csharp\u002F06.system-internals-concurrency",[225,229,233,237,241,245,249],{"title":226,"path":227,"stem":228},"Memory Management","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmemory-management","01.csharp\u002F06.system-internals-concurrency\u002F01.memory-management",{"title":230,"path":231,"stem":232},"Reflection API: System.Type та Метадані","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Freflection-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F02.reflection-fundamentals",{"title":234,"path":235,"stem":236},"Attributes та Dynamic Language Runtime","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fattributes-dynamic","01.csharp\u002F06.system-internals-concurrency\u002F03.attributes-dynamic",{"title":238,"path":239,"stem":240},"Expression Trees: Швидка Альтернатива Рефлексії","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fexpression-trees-compiled","01.csharp\u002F06.system-internals-concurrency\u002F04.expression-trees-compiled",{"title":242,"path":243,"stem":244},"Source Generators: Compile-Time Code Generation","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsource-generators","01.csharp\u002F06.system-internals-concurrency\u002F05.source-generators",{"title":246,"path":247,"stem":248},"Multithreading Fundamentals","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmultithreading-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F06.multithreading-fundamentals",{"title":250,"path":251,"stem":252},"Synchronization Primitives","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsynchronization-primitives","01.csharp\u002F06.system-internals-concurrency\u002F07.synchronization-primitives",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":59},"System Programming Windows","i-lucide-cpu","\u002Fcsharp\u002Fsystem-programming-windows","01.csharp\u002F07.system-programming-windows",[259,263,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335,339,343,347,351,355,359,363,367,371,375],{"title":260,"path":261,"stem":262},"Як Працює Операційна Система","\u002Fcsharp\u002Fsystem-programming-windows\u002Fhow-os-works","01.csharp\u002F07.system-programming-windows\u002F01.how-os-works",{"title":264,"path":265,"stem":266},"Процеси в .NET — API та Запуск","\u002Fcsharp\u002Fsystem-programming-windows\u002Fprocesses-in-dotnet","01.csharp\u002F07.system-programming-windows\u002F02.processes-in-dotnet",{"title":268,"path":269,"stem":270},"Процеси в .NET — IPC та Міжпроцесна Комунікація","\u002Fcsharp\u002Fsystem-programming-windows\u002F02a.processes-ipc","01.csharp\u002F07.system-programming-windows\u002F02a.processes-ipc",{"title":272,"path":273,"stem":274},"Application Domains та Збірки — AppDomain і AssemblyLoadContext","\u002Fcsharp\u002Fsystem-programming-windows\u002Fappdomains-assemblies","01.csharp\u002F07.system-programming-windows\u002F03.appdomains-assemblies",{"title":276,"path":277,"stem":278},"Application Domains та Збірки — Plug-in Система з Hot-Reload","\u002Fcsharp\u002Fsystem-programming-windows\u002F03a.appdomains-plugin-system","01.csharp\u002F07.system-programming-windows\u002F03a.appdomains-plugin-system",{"title":280,"path":281,"stem":282},"Потоки — Основи та API Thread","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-fundamentals","01.csharp\u002F07.system-programming-windows\u002F04.thread-fundamentals",{"title":284,"path":285,"stem":286},"Потоки — Lifecycle, Пріоритети та Безпечне Завершення","\u002Fcsharp\u002Fsystem-programming-windows\u002F04a.thread-lifecycle-priorities","01.csharp\u002F07.system-programming-windows\u002F04a.thread-lifecycle-priorities",{"title":288,"path":289,"stem":290},"Проблеми Спільного Стану — Race Condition та Data Race","\u002Fcsharp\u002Fsystem-programming-windows\u002Fshared-state-problems","01.csharp\u002F07.system-programming-windows\u002F05.shared-state-problems",{"title":292,"path":293,"stem":294},"Проблеми Спільного Стану — Memory Model та volatile","\u002Fcsharp\u002Fsystem-programming-windows\u002F05a.shared-state-memory-model","01.csharp\u002F07.system-programming-windows\u002F05a.shared-state-memory-model",{"title":296,"path":297,"stem":298},"Синхронізація — Monitor, lock та еволюція примітивів","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-fundamentals","01.csharp\u002F07.system-programming-windows\u002F06.synchronization-fundamentals",{"title":300,"path":301,"stem":302},"Синхронізація — Наскрізний Приклад та Deadlock Detection","\u002Fcsharp\u002Fsystem-programming-windows\u002F06a.synchronization-walkthrough","01.csharp\u002F07.system-programming-windows\u002F06a.synchronization-walkthrough",{"title":304,"path":305,"stem":306},"Синхронізація — Mutex, Semaphore та Event-Based Primitives","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-advanced","01.csharp\u002F07.system-programming-windows\u002F07.synchronization-advanced",{"title":308,"path":309,"stem":310},"Синхронізація — Interlocked, Volatile та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002F07a.synchronization-advanced-walkthrough","01.csharp\u002F07.system-programming-windows\u002F07a.synchronization-advanced-walkthrough",{"title":312,"path":313,"stem":314},"Interlocked, CAS та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002Finterlocked-cas-lockfree","01.csharp\u002F07.system-programming-windows\u002F08.interlocked-cas-lockfree",{"title":316,"path":317,"stem":318},"Volatile, Memory Model та Spinning","\u002Fcsharp\u002Fsystem-programming-windows\u002F08a.volatile-memory-model","01.csharp\u002F07.system-programming-windows\u002F08a.volatile-memory-model",{"title":320,"path":321,"stem":322},"ThreadPool — Пул Потоків для Ефективного Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-pool","01.csharp\u002F07.system-programming-windows\u002F09.thread-pool",{"title":324,"path":325,"stem":326},"ThreadPool — Просунуті Сценарії та Внутрішня Будова","\u002Fcsharp\u002Fsystem-programming-windows\u002F09a.thread-pool-advanced","01.csharp\u002F07.system-programming-windows\u002F09a.thread-pool-advanced",{"title":328,"path":329,"stem":330},"Concurrent та Immutable Collections","\u002Fcsharp\u002Fsystem-programming-windows\u002Fconcurrent-collections","01.csharp\u002F07.system-programming-windows\u002F10.concurrent-collections",{"title":332,"path":333,"stem":334},"TPL, Task та Композиція — Від Thread до Task","\u002Fcsharp\u002Fsystem-programming-windows\u002Ftpl-parallel-plinq","01.csharp\u002F07.system-programming-windows\u002F11.tpl-parallel-plinq",{"title":336,"path":337,"stem":338},"Parallel Class та PLINQ — Data Parallelism","\u002Fcsharp\u002Fsystem-programming-windows\u002F11a.tpl-parallel-plinq-advanced","01.csharp\u002F07.system-programming-windows\u002F11a.tpl-parallel-plinq-advanced",{"title":340,"path":341,"stem":342},"Async\u002FAwait — Фундамент Асинхронного Програмування","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-fundamentals","01.csharp\u002F07.system-programming-windows\u002F12.async-fundamentals",{"title":344,"path":345,"stem":346},"SynchronizationContext та ConfigureAwait — Контекст Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-context-configureawait","01.csharp\u002F07.system-programming-windows\u002F13.async-context-configureawait",{"title":348,"path":349,"stem":350},"Async — Просунуті Паттерни","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-advanced","01.csharp\u002F07.system-programming-windows\u002F14.async-advanced",{"title":352,"path":353,"stem":354},"System.Threading.Channels — Async Producer-Consumer","\u002Fcsharp\u002Fsystem-programming-windows\u002Fchannels","01.csharp\u002F07.system-programming-windows\u002F15.channels",{"title":356,"path":357,"stem":358},"Асинхронна Синхронізація","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-synchronization","01.csharp\u002F07.system-programming-windows\u002F16.async-synchronization",{"title":360,"path":361,"stem":362},"Unsafe Code та Вказівники","\u002Fcsharp\u002Fsystem-programming-windows\u002Funsafe-code","01.csharp\u002F07.system-programming-windows\u002F17.unsafe-code",{"title":364,"path":365,"stem":366},"P\u002FInvoke та Windows API — Міст між .NET та Native Code","\u002Fcsharp\u002Fsystem-programming-windows\u002Fpinvoke-winapi","01.csharp\u002F07.system-programming-windows\u002F18.pinvoke-winapi",{"title":368,"path":369,"stem":370},"Реєстр Windows — Центральна База Конфігурації Системи","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-registry","01.csharp\u002F07.system-programming-windows\u002F19.windows-registry",{"title":372,"path":373,"stem":374},"Windows Hooks, Hotkeys та Services — Глибока Інтеграція з ОС","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-hooks-services","01.csharp\u002F07.system-programming-windows\u002F20.windows-hooks-services",{"title":376,"path":377,"stem":378},"Системне Програмування C# (Windows) — 07.system-programming-windows","\u002Fcsharp\u002Fsystem-programming-windows\u002Fimplementation_plan","01.csharp\u002F07.system-programming-windows\u002Fimplementation_plan",{"title":380,"icon":132,"path":381,"stem":382,"children":383,"page":59},"Io","\u002Fcsharp\u002Fio","01.csharp\u002F08.io",[384,388,392,396,400],{"title":385,"path":386,"stem":387},"8.1.1. Основи роботи з файловою системою","\u002Fcsharp\u002Fio\u002Ffile-system-basics","01.csharp\u002F08.io\u002F01.file-system-basics",{"title":389,"path":390,"stem":391},"8.1.2. Потоки (Streams) та Серіалізація Даних","\u002Fcsharp\u002Fio\u002Fstreams-serialization","01.csharp\u002F08.io\u002F02.streams-serialization",{"title":393,"path":394,"stem":395},"8.2.1. JSON Serialization з System.Text.Json","\u002Fcsharp\u002Fio\u002Fjson-serialization","01.csharp\u002F08.io\u002F03.json-serialization",{"title":397,"path":398,"stem":399},"8.2.2. XML Serialization та LINQ to XML","\u002Fcsharp\u002Fio\u002Fxml-serialization","01.csharp\u002F08.io\u002F04.xml-serialization",{"title":401,"path":402,"stem":403},"8.2.3. Binary Serialization: MessagePack та Protocol Buffers","\u002Fcsharp\u002Fio\u002Fbinary-serialization","01.csharp\u002F08.io\u002F05.binary-serialization",{"title":405,"icon":132,"path":406,"stem":407,"children":408,"page":59},"Ado Net","\u002Fcsharp\u002Fado-net","01.csharp\u002F09.ado-net",[409,413,417,421,425,429,433,437,441,445,449,453],{"title":410,"path":411,"stem":412},"9.1. Введення в ADO.NET","\u002Fcsharp\u002Fado-net\u002Fintroduction-to-adonet","01.csharp\u002F09.ado-net\u002F01.introduction-to-adonet",{"title":414,"path":415,"stem":416},"9.2. Клас DbConnection — з'єднання з базою даних","\u002Fcsharp\u002Fado-net\u002Fconnection","01.csharp\u002F09.ado-net\u002F02.connection",{"title":418,"path":419,"stem":420},"9.3. Клас DbCommand — виконання SQL-запитів","\u002Fcsharp\u002Fado-net\u002Fcommand-and-queries","01.csharp\u002F09.ado-net\u002F03.command-and-queries",{"title":422,"path":423,"stem":424},"9.4. Клас DbDataReader — ефективне читання даних","\u002Fcsharp\u002Fado-net\u002Fdatareader","01.csharp\u002F09.ado-net\u002F04.datareader",{"title":426,"path":427,"stem":428},"9.5. Параметризовані запити та захист від SQL Injection","\u002Fcsharp\u002Fado-net\u002Fparameters-and-sql-injection","01.csharp\u002F09.ado-net\u002F05.parameters-and-sql-injection",{"title":430,"path":431,"stem":432},"9.6. Транзакції в ADO.NET","\u002Fcsharp\u002Fado-net\u002Ftransactions","01.csharp\u002F09.ado-net\u002F06.transactions",{"title":434,"path":435,"stem":436},"9.7. DbProviderFactory — провайдер-незалежний код","\u002Fcsharp\u002Fado-net\u002Fprovider-factory","01.csharp\u002F09.ado-net\u002F07.provider-factory",{"title":438,"path":439,"stem":440},"9.8. Асинхронний доступ до даних","\u002Fcsharp\u002Fado-net\u002Fasync-data-access","01.csharp\u002F09.ado-net\u002F08.async-data-access",{"title":442,"path":443,"stem":444},"9.9. Від'єднаний режим: DataSet, DataTable, DataRow","\u002Fcsharp\u002Fado-net\u002Fdisconnected-mode-dataset","01.csharp\u002F09.ado-net\u002F09.disconnected-mode-dataset",{"title":446,"path":447,"stem":448},"9.10. DataAdapter — міст між DataSet та базою даних","\u002Fcsharp\u002Fado-net\u002Fdata-adapter","01.csharp\u002F09.ado-net\u002F10.data-adapter",{"title":450,"path":451,"stem":452},"9.11. Data Mapper та Repository: Архітектура доступу до даних","\u002Fcsharp\u002Fado-net\u002Fdata-mapper-repository","01.csharp\u002F09.ado-net\u002F11.data-mapper-repository",{"title":454,"path":455,"stem":456},"9.12. Identity Map, Unit of Work та Specification Pattern","\u002Fcsharp\u002Fado-net\u002Fadvanced-patterns","01.csharp\u002F09.ado-net\u002F12.advanced-patterns",{"title":458,"icon":255,"path":459,"stem":460,"children":461,"page":59},"Ef Core","\u002Fcsharp\u002Fef-core","01.csharp\u002F10.ef-core",[462,466,470,474,478,482,486,490,494,498,502,506,510,514,518,522,526,532,538,542,546,550,554,558,562,566,570,574,578,582,586,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646],{"title":463,"path":464,"stem":465},"Що таке ORM? Від SQL до об'єктів","\u002Fcsharp\u002Fef-core\u002Fwhat-is-orm","01.csharp\u002F10.ef-core\u002F01.what-is-orm",{"title":467,"path":468,"stem":469},"Перший проєкт — від нуля до CRUD","\u002Fcsharp\u002Fef-core\u002Ffirst-project","01.csharp\u002F10.ef-core\u002F02.first-project",{"title":471,"path":472,"stem":473},"DbContext — Серце EF Core","\u002Fcsharp\u002Fef-core\u002Fdbcontext-deep-dive","01.csharp\u002F10.ef-core\u002F03.dbcontext-deep-dive",{"title":475,"path":476,"stem":477},"Провайдери баз даних — Архітектура та Вибір СУБД","\u002Fcsharp\u002Fef-core\u002Fdatabase-providers","01.csharp\u002F10.ef-core\u002F04.database-providers",{"title":479,"path":480,"stem":481},"Конвенції EF Core — Магія без конфігурації","\u002Fcsharp\u002Fef-core\u002Fconventions","01.csharp\u002F10.ef-core\u002F05.conventions",{"title":483,"path":484,"stem":485},"Fluent API та Data Annotations — Явна конфігурація моделі","\u002Fcsharp\u002Fef-core\u002Ffluent-api-vs-annotations","01.csharp\u002F10.ef-core\u002F06.fluent-api-vs-annotations",{"title":487,"path":488,"stem":489},"Зв'язки — One-to-One та One-to-Many","\u002Fcsharp\u002Fef-core\u002Frelationships-basics","01.csharp\u002F10.ef-core\u002F07.relationships-basics",{"title":491,"path":492,"stem":493},"Зв'язки Advanced — Many-to-Many та Складні Сценарії","\u002Fcsharp\u002Fef-core\u002Frelationships-advanced","01.csharp\u002F10.ef-core\u002F08.relationships-advanced",{"title":495,"path":496,"stem":497},"Властивості — Типи, Конвертери, Компаратори (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part1","01.csharp\u002F10.ef-core\u002F09.property-configuration-part1",{"title":499,"path":500,"stem":501},"Властивості — Value Comparers, Generators, Shadow Properties (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part2","01.csharp\u002F10.ef-core\u002F09.property-configuration-part2",{"title":503,"path":504,"stem":505},"Складні типи — Owned Types та Complex Types (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part1","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part1",{"title":507,"path":508,"stem":509},"Складні типи — Complex Types, Keyless Entities, Порівняння (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part2","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part2",{"title":511,"path":512,"stem":513},"JSON Columns — Складні дані у JSON (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part1","01.csharp\u002F10.ef-core\u002F11.json-columns-part1",{"title":515,"path":516,"stem":517},"JSON Columns — Value Comparers, Індекси, Провайдери (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part2","01.csharp\u002F10.ef-core\u002F11.json-columns-part2",{"title":519,"path":520,"stem":521},"Успадкування — Абстрактні класи та TPH (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finheritance-part1","01.csharp\u002F10.ef-core\u002F12.inheritance-part1",{"title":523,"path":524,"stem":525},"Успадкування — TPT, TPC та Порівняння Стратегій (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finheritance-part2","01.csharp\u002F10.ef-core\u002F12.inheritance-part2",{"title":527,"path":528,"stem":529,"children":530},"Індекси, Обмеження та Схема (Частина 1)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part1","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part1",[531],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"Індекси, Обмеження та Схема (Частина 2)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part2","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part2",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541},"Seed Data — Початкові Дані (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fseeding-part1","01.csharp\u002F10.ef-core\u002F14.seeding-part1",{"title":543,"path":544,"stem":545},"Seed Data — SQL-скрипти, Bogus та Стратегії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fseeding-part2","01.csharp\u002F10.ef-core\u002F14.seeding-part2",{"title":547,"path":548,"stem":549},"Global Query Filters — Глобальні Фільтри (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part1","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part1",{"title":551,"path":552,"stem":553},"Global Query Filters — Підводні камені та Інтеграція (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part2","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part2",{"title":555,"path":556,"stem":557},"LINQ-запити в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part1","01.csharp\u002F10.ef-core\u002F16.linq-queries-part1",{"title":559,"path":560,"stem":561},"LINQ-запити в EF Core (Частина 2)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part2","01.csharp\u002F10.ef-core\u002F16.linq-queries-part2",{"title":563,"path":564,"stem":565},"Завантаження Пов'язаних Даних (Частина 1)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part1","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part1",{"title":567,"path":568,"stem":569},"Завантаження Пов'язаних Даних (Частина 2)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part2","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part2",{"title":571,"path":572,"stem":573},"Raw SQL, Views та Stored Procedures (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part1","01.csharp\u002F10.ef-core\u002F18.raw-sql-part1",{"title":575,"path":576,"stem":577},"Raw SQL — Stored Procedures, DbFunction та Bulk Operations (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part2","01.csharp\u002F10.ef-core\u002F18.raw-sql-part2",{"title":579,"path":580,"stem":581},"Продвинуті Запити — Compiled Queries, Bulk та Оптимізація (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part1","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part1",{"title":583,"path":584,"stem":585},"Продвинуті Запити — Query Tags, Bulk та Interceptors (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part2","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part2",{"title":587,"path":588,"stem":589},"Change Tracker — Відстеження Змін (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part1","01.csharp\u002F10.ef-core\u002F20.change-tracking-part1",{"title":591,"path":592,"stem":593},"Change Tracker — Графи Об'єктів та Disconnected (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part2","01.csharp\u002F10.ef-core\u002F20.change-tracking-part2",{"title":595,"path":596,"stem":597},"Збереження Даних та Транзакції (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part1","01.csharp\u002F10.ef-core\u002F21.saving-data-part1",{"title":599,"path":600,"stem":601},"Збереження Даних — Concurrency та Outbox (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part2","01.csharp\u002F10.ef-core\u002F21.saving-data-part2",{"title":603,"path":604,"stem":605},"Конкурентність та Блокування (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part1","01.csharp\u002F10.ef-core\u002F22.concurrency-part1",{"title":607,"path":608,"stem":609},"Конкурентність — Дедлоки та Queue Processing (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part2","01.csharp\u002F10.ef-core\u002F22.concurrency-part2",{"title":611,"path":612,"stem":613},"Міграції в EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part1","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part1",{"title":615,"path":616,"stem":617},"Міграції в EF Core — Основи (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part2","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part2",{"title":619,"path":620,"stem":621},"Міграції — Просунуті Сценарії (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part1","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part1",{"title":623,"path":624,"stem":625},"Міграції — Просунуті Сценарії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part2","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part2",{"title":627,"path":628,"stem":629},"Управління Схемою та Database-First (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part1","01.csharp\u002F10.ef-core\u002F25.schema-management-part1",{"title":631,"path":632,"stem":633},"Управління Схемою та Database-First (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part2","01.csharp\u002F10.ef-core\u002F25.schema-management-part2",{"title":635,"path":636,"stem":637},"Продуктивність EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fperformance-fundamentals-part1","01.csharp\u002F10.ef-core\u002F26.performance-fundamentals-part1",{"title":639,"path":640,"stem":641},"Interceptors в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part1","01.csharp\u002F10.ef-core\u002F29.interceptors-part1",{"title":643,"path":644,"stem":645},"Interceptors в EF Core — Connection, Transaction та Materialization (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part2","01.csharp\u002F10.ef-core\u002F29.interceptors-part2",{"title":647,"path":648,"stem":649},"План вивчення Entity Framework Core — Повний курс","\u002Fcsharp\u002Fef-core\u002Fimplementation_plan","01.csharp\u002F10.ef-core\u002Fimplementation_plan",{"title":651,"icon":652,"path":653,"stem":654,"children":655,"page":59},"ASP.NET","i-devicon-dotnetcore","\u002Fcsharp\u002Faspnet","01.csharp\u002F11.aspnet",[656,730,791,869,927,941,967,1057,1111,1182,1212,1289,1346],{"title":657,"icon":658,"path":659,"stem":660,"children":661,"page":59},"Minimal API","i-lucide-network","\u002Fcsharp\u002Faspnet\u002Fminimal-api","01.csharp\u002F11.aspnet\u002F01.minimal-api",[662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726],{"title":663,"path":664,"stem":665},"Вступ до ASP.NET та еволюція фреймворку","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fintroduction","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F01.introduction",{"title":667,"path":668,"stem":669},"Перший додаток на ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Ffirst-application","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F02.first-application",{"title":671,"path":672,"stem":673},"WebApplication, Builder та Dependency Injection","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fwebapplication-builder","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F03.webapplication-builder",{"title":675,"path":676,"stem":677},"Конвеєр запитів та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frequest-pipeline-middleware","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F04.request-pipeline-middleware",{"title":679,"path":680,"stem":681},"Маршрутизація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F05.routing-basics",{"title":683,"path":684,"stem":685},"Маршрутизація в ASP.NET Core: Розширені можливості","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F06.routing-advanced",{"title":687,"path":688,"stem":689},"Статичні файли в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-files","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F07.static-files",{"title":691,"path":692,"stem":693},"Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-assets","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F08.static-assets",{"title":695,"path":696,"stem":697},"Конфігурація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-fundamentals","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F09.configuration-fundamentals",{"title":699,"path":700,"stem":701},"Конфігурація: Паттерн Options","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-options","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F10.configuration-options",{"title":703,"path":704,"stem":705},"Логування в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F11.logging-basics",{"title":707,"path":708,"stem":709},"Логування: Serilog та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F12.logging-advanced",{"title":711,"path":712,"stem":713},"Управління станом: HttpContext.Items та Cookies","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstate-management","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F13.state-management",{"title":715,"path":716,"stem":717},"Стан сесії: Sessions","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fsession-state","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F14.session-state",{"title":719,"path":720,"stem":721},"Структура проєкту: від хаосу до архітектури","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fproject-structure","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F15.project-structure",{"title":723,"path":724,"stem":725},"Scalar у Minimal API: повний проєкт і Fluent OpenAPI","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fscalar-openapi-fluent","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F16.scalar-openapi-fluent",{"title":727,"path":728,"stem":729},"Swagger \u002F Swashbuckle у Minimal API: окремий класичний шлях","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fswagger-swashbuckle","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F17.swagger-swashbuckle",{"title":731,"icon":658,"path":732,"stem":733,"children":734,"page":59},"API","\u002Fcsharp\u002Faspnet\u002Fapi","01.csharp\u002F11.aspnet\u002F02.api",[735,739,743,747,751,755,759,763,767,771,775,779,783,787],{"title":736,"path":737,"stem":738},"Що таке API. Клієнт-серверна архітектура","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fwhat-is-api","01.csharp\u002F11.aspnet\u002F02.api\u002F01.what-is-api",{"title":740,"path":741,"stem":742},"Формати даних: JSON, XML, TOML та бінарні формати","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fdata-formats","01.csharp\u002F11.aspnet\u002F02.api\u002F02.data-formats",{"title":744,"path":745,"stem":746},"Парадигми API та концепція REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-paradigms-rest","01.csharp\u002F11.aspnet\u002F02.api\u002F03.api-paradigms-rest",{"title":748,"path":749,"stem":750},"HTTP-методи, статус-коди та заголовки","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fhttp-methods-status-codes","01.csharp\u002F11.aspnet\u002F02.api\u002F04.http-methods-status-codes",{"title":752,"path":753,"stem":754},"Організація HTTP API за принципами REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Frest-organizing","01.csharp\u002F11.aspnet\u002F02.api\u002F05.rest-organizing",{"title":756,"path":757,"stem":758},"Номенклатура URL та CRUD-операції","\u002Fcsharp\u002Faspnet\u002Fapi\u002Furl-nomenclature-crud","01.csharp\u002F11.aspnet\u002F02.api\u002F06.url-nomenclature-crud",{"title":760,"path":761,"stem":762},"Правила дизайну: іменування та стандарти","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-naming","01.csharp\u002F11.aspnet\u002F02.api\u002F07.api-design-naming",{"title":764,"path":765,"stem":766},"Валідація, ліміти та обробка помилок","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-validation","01.csharp\u002F11.aspnet\u002F02.api\u002F08.api-design-validation",{"title":768,"path":769,"stem":770},"Обробка помилок у Minimal API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Ferror-handling-http","01.csharp\u002F11.aspnet\u002F02.api\u002F09.error-handling-http",{"title":772,"path":773,"stem":774},"Ідемпотентність та синхронізація стану","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fidempotency-sync","01.csharp\u002F11.aspnet\u002F02.api\u002F10.idempotency-sync",{"title":776,"path":777,"stem":778},"Пагінація та організація списків","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fpagination-lists","01.csharp\u002F11.aspnet\u002F02.api\u002F11.pagination-lists",{"title":780,"path":781,"stem":782},"Безпека API, кешування та інтернаціоналізація","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fsecurity-auth","01.csharp\u002F11.aspnet\u002F02.api\u002F12.security-auth",{"title":784,"path":785,"stem":786},"Процес проєктування API та документування","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-process","01.csharp\u002F11.aspnet\u002F02.api\u002F13.api-design-process",{"title":788,"path":789,"stem":790},"OpenAPI: контракт, специфікація та документація API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fopenapi","01.csharp\u002F11.aspnet\u002F02.api\u002F14.openapi",{"title":792,"icon":793,"path":794,"stem":795,"children":796,"page":59},"Auth","i-lucide-shield-check","\u002Fcsharp\u002Faspnet\u002Fauth","01.csharp\u002F11.aspnet\u002F03.auth",[797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865],{"title":798,"path":799,"stem":800},"Основи аутентифікації та авторизації","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauth-fundamentals","01.csharp\u002F11.aspnet\u002F03.auth\u002F01.auth-fundamentals",{"title":802,"path":803,"stem":804},"JWT-аутентифікація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fjwt-authentication","01.csharp\u002F11.aspnet\u002F03.auth\u002F02.jwt-authentication",{"title":806,"path":807,"stem":808},"Авторизація: ролі, політики та resource-based доступ","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauthorization-policies","01.csharp\u002F11.aspnet\u002F03.auth\u002F03.authorization-policies",{"title":810,"path":811,"stem":812},"Cookie-аутентифікація та ASP.NET Core Identity","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcookie-auth-identity","01.csharp\u002F11.aspnet\u002F03.auth\u002F04.cookie-auth-identity",{"title":814,"path":815,"stem":816},"JWT + Refresh Tokens (HttpOnly Cookie)","\u002Fcsharp\u002Faspnet\u002Fauth\u002F04b.identity-auth-jwt","01.csharp\u002F11.aspnet\u002F03.auth\u002F04b.identity-auth-jwt",{"title":818,"path":819,"stem":820},"Identity: Підтвердження Email та Скидання Пароля","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-email-confirmation","01.csharp\u002F11.aspnet\u002F03.auth\u002F05.identity-email-confirmation",{"title":822,"path":823,"stem":824},"Identity: Двофакторна Аутентифікація (2FA)","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-two-factor","01.csharp\u002F11.aspnet\u002F03.auth\u002F06.identity-two-factor",{"title":826,"path":827,"stem":828},"Identity: Внутрішня Архітектура та Кастомізація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-internals","01.csharp\u002F11.aspnet\u002F03.auth\u002F07.identity-internals",{"title":830,"path":831,"stem":832},"OAuth 2.0 та зовнішні провайдери","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-external-providers","01.csharp\u002F11.aspnet\u002F03.auth\u002F08.oauth-external-providers",{"title":834,"path":835,"stem":836},"Безпека на практиці: CORS, HTTPS та захист від атак","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fsecurity-hardening","01.csharp\u002F11.aspnet\u002F03.auth\u002F09.security-hardening",{"title":838,"path":839,"stem":840},"Теорія OAuth 2.0: Поняття, Аналогії та Флоу","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-theory","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oauth-theory",{"title":842,"path":843,"stem":844},"OIDC, OAuth 2.0 та Keycloak в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foidc-keycloak","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oidc-keycloak",{"title":846,"path":847,"stem":848},"API Keys аутентифікація в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fapi-keys","01.csharp\u002F11.aspnet\u002F03.auth\u002F11.api-keys",{"title":850,"path":851,"stem":852},"Rate Limiting та Throttling в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frate-limiting","01.csharp\u002F11.aspnet\u002F03.auth\u002F12.rate-limiting",{"title":854,"path":855,"stem":856},"Refresh Token Rotation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frefresh-token-rotation","01.csharp\u002F11.aspnet\u002F03.auth\u002F13.refresh-token-rotation",{"title":858,"path":859,"stem":860},"Certificate Authentication та mTLS в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcertificate-auth","01.csharp\u002F11.aspnet\u002F03.auth\u002F14.certificate-auth",{"title":862,"path":863,"stem":864},"RBAC, ABAC та ReBAC в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frbac-abac-rebac","01.csharp\u002F11.aspnet\u002F03.auth\u002F15.rbac-abac-rebac",{"title":866,"path":867,"stem":868},"Multi-tenancy та ізоляція даних в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fmulti-tenancy","01.csharp\u002F11.aspnet\u002F03.auth\u002F16.multi-tenancy",{"title":870,"icon":871,"path":872,"stem":873,"children":874,"page":59},"Нотифікації","i-lucide-bell","\u002Fcsharp\u002Faspnet\u002Fnotifications","01.csharp\u002F11.aspnet\u002F04.notifications",[875,879,883,887,891,895,899,903,907,911,915,919,923],{"title":876,"path":877,"stem":878},"In-App нотифікації через базу даних","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fin-app-database-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F01.in-app-database-notifications",{"title":880,"path":881,"stem":882},"Polling: Регулярний запит оновлень","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fpolling","01.csharp\u002F11.aspnet\u002F04.notifications\u002F02.polling",{"title":884,"path":885,"stem":886},"Server-Sent Events: Однострімовий push від сервера","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fserver-sent-events","01.csharp\u002F11.aspnet\u002F04.notifications\u002F03.server-sent-events",{"title":888,"path":889,"stem":890},"WebSockets: Двостороннє з'єднання в реальному часі","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fwebsockets","01.csharp\u002F11.aspnet\u002F04.notifications\u002F04.websockets",{"title":892,"path":893,"stem":894},"SignalR: Абстракція над транспортами реального часу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fsignalr","01.csharp\u002F11.aspnet\u002F04.notifications\u002F05.signalr",{"title":896,"path":897,"stem":898},"Background Services: Фонові задачі в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fbackground-services","01.csharp\u002F11.aspnet\u002F04.notifications\u002F06.background-services",{"title":900,"path":901,"stem":902},"Web Push нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fweb-push","01.csharp\u002F11.aspnet\u002F04.notifications\u002F07.web-push",{"title":904,"path":905,"stem":906},"Email нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Femail-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F08.email-notifications",{"title":908,"path":909,"stem":910},"Порівняння підходів: Як вибрати правильну технологію нотифікацій","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fchoosing-the-right-approach","01.csharp\u002F11.aspnet\u002F04.notifications\u002F09.choosing-the-right-approach",{"title":912,"path":913,"stem":914},"Hangfire: Надійне планування фонових задач","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire","01.csharp\u002F11.aspnet\u002F04.notifications\u002F10.hangfire",{"title":916,"path":917,"stem":918},"Практика: Конвертація зображень у WebP через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-image-webp","01.csharp\u002F11.aspnet\u002F04.notifications\u002F11.hangfire-image-webp",{"title":920,"path":921,"stem":922},"Практика: Підготовка відео до HLS-стрімінгу через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-video-hls","01.csharp\u002F11.aspnet\u002F04.notifications\u002F12.hangfire-video-hls",{"title":924,"path":925,"stem":926},"Telegram-нотифікації: від одного повідомлення до масових розсилок і мульти-канального підходу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Ftelegram-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F13.telegram-notifications",{"title":928,"icon":929,"path":930,"stem":931,"children":932,"page":59},"Інтернаціоналізація","i-lucide-languages","\u002Fcsharp\u002Faspnet\u002Fi18n","01.csharp\u002F11.aspnet\u002F05.i18n",[933,937],{"title":934,"path":935,"stem":936},"Інтернаціоналізація (i18n) у Minimal API: від A до Я","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Finternationalization","01.csharp\u002F11.aspnet\u002F05.i18n\u002F01.internationalization",{"title":938,"path":939,"stem":940},"Humanizer: людиномовні рядки у .NET","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Fhumanizer","01.csharp\u002F11.aspnet\u002F05.i18n\u002F02.humanizer",{"title":942,"icon":943,"path":944,"stem":945,"children":946,"page":59},"Кешування","i-lucide-layers","\u002Fcsharp\u002Faspnet\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching",[947,951,955,959,963],{"title":948,"path":949,"stem":950},"Огляд кешування: чотири рівні і коли що обирати","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching\u002F01.caching",{"title":952,"path":953,"stem":954},"IMemoryCache: кеш в оперативній пам'яті","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fmemory-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F02.memory-cache",{"title":956,"path":957,"stem":958},"IDistributedCache і Redis: розподілений кеш","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fdistributed-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F03.distributed-cache",{"title":960,"path":961,"stem":962},"Response Cache: HTTP-кешування через Cache-Control","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fresponse-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F04.response-cache",{"title":964,"path":965,"stem":966},"Output Cache: серверний кеш HTTP-відповідей (.NET 7+)","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Foutput-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F05.output-cache",{"title":968,"icon":969,"path":970,"stem":971,"children":972,"page":59},"Тестування","i-lucide-test-tube","\u002Fcsharp\u002Faspnet\u002Ftesting","01.csharp\u002F11.aspnet\u002F07.testing",[973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053],{"title":974,"path":975,"stem":976},"Що таке тестування? Від інтуїції до науки","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-is-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F01.what-is-testing",{"title":978,"path":979,"stem":980},"Піраміда тестування — Стратегія, а не Догма","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-pyramid","01.csharp\u002F11.aspnet\u002F07.testing\u002F02.testing-pyramid",{"title":982,"path":983,"stem":984},"Дві Школи Тестування — Лондон проти Детройту","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-schools","01.csharp\u002F11.aspnet\u002F07.testing\u002F03.testing-schools",{"title":986,"path":987,"stem":988},"TDD та BDD — Тести як Дизайн-інструмент","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftdd-and-bdd","01.csharp\u002F11.aspnet\u002F07.testing\u002F04.tdd-and-bdd",{"title":990,"path":991,"stem":992},"Що саме тестувати — Техніки аналізу та Циклomatична складність","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-to-test","01.csharp\u002F11.aspnet\u002F07.testing\u002F05.what-to-test",{"title":994,"path":995,"stem":996},"Тестові Фреймворки — Навіщо вони і що всередині","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftest-frameworks","01.csharp\u002F11.aspnet\u002F07.testing\u002F06.test-frameworks",{"title":998,"path":999,"stem":1000},"xUnit — Факти, Теорії та Lifecycle тестів","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-basics","01.csharp\u002F11.aspnet\u002F07.testing\u002F07.xunit-basics",{"title":1002,"path":1003,"stem":1004},"xUnit Advanced — Fixtures, Кастомізація та Розширення","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F08.xunit-advanced",{"title":1006,"path":1007,"stem":1008},"Moq — Глибоке занурення в мокування","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fmocking-with-moq","01.csharp\u002F11.aspnet\u002F07.testing\u002F09.mocking-with-moq",{"title":1010,"path":1011,"stem":1012},"Тестування Баз Даних — EF Core, SQLite та Testcontainers","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fdatabase-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F10.database-testing",{"title":1014,"path":1015,"stem":1016},"Integration Testing — Частина 1 [Теорія та WebApplicationFactory]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F11.integration-testing",{"title":1018,"path":1019,"stem":1020},"Інтеграційне тестування — Практика","\u002Fcsharp\u002Faspnet\u002Ftesting\u002F11a.integration-testing-practice","01.csharp\u002F11.aspnet\u002F07.testing\u002F11a.integration-testing-practice",{"title":1022,"path":1023,"stem":1024},"Integration Testing — Частина 2 [Просунуті Сценарії та Testcontainers]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F12.integration-testing-advanced",{"title":1026,"path":1027,"stem":1028},"Професійний Postman: Колекції, Змінні та GitHub Інтеграція","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fpostman-professional","01.csharp\u002F11.aspnet\u002F07.testing\u002F13.postman-professional",{"title":1030,"path":1031,"stem":1032},"HttpClient у Тестах Частина 1: Архітектура та MockHttpMessageHandler","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fhttpclient-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F14.httpclient-testing",{"title":1034,"path":1035,"stem":1036},"HttpClient у Тестах Частина 2: WireMock.Net та Resilience","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwiremock-net","01.csharp\u002F11.aspnet\u002F07.testing\u002F15.wiremock-net",{"title":1038,"path":1039,"stem":1040},"Патерни та Анти-патерни Тестування: Test Smells","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-patterns","01.csharp\u002F11.aspnet\u002F07.testing\u002F16.testing-patterns",{"title":1042,"path":1043,"stem":1044},"Просунуті інструменти: Time, Snapshots та Властивості","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fadvanced-testing-tools","01.csharp\u002F11.aspnet\u002F07.testing\u002F17.advanced-testing-tools",{"title":1046,"path":1047,"stem":1048},"Тестування Архітектури з NetArchTest","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Farchitecture-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F18.architecture-testing",{"title":1050,"path":1051,"stem":1052},"Тестування Продуктивності: BenchmarkDotNet, NBomber та k6","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fperformance-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F19.performance-testing",{"title":1054,"path":1055,"stem":1056},"Залишок плану для курсу \"Тестування ASP.NET Minimal API\"","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fremaining_plan","01.csharp\u002F11.aspnet\u002F07.testing\u002Fremaining_plan",{"title":1058,"icon":1059,"path":1060,"stem":1061,"children":1062,"page":59},"Платежі","i-lucide-credit-card","\u002Fcsharp\u002Faspnet\u002Fpayments","01.csharp\u002F11.aspnet\u002F08.payments",[1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107],{"title":1064,"path":1065,"stem":1066},"Основи платіжної інфраструктури","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-fundamentals","01.csharp\u002F11.aspnet\u002F08.payments\u002F01.payment-fundamentals",{"title":1068,"path":1069,"stem":1070},"Методи оплати в Україні","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-methods-ukraine","01.csharp\u002F11.aspnet\u002F08.payments\u002F02.payment-methods-ukraine",{"title":1072,"path":1073,"stem":1074},"PCI DSS та безпека платежів","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpci-dss-security","01.csharp\u002F11.aspnet\u002F08.payments\u002F03.pci-dss-security",{"title":1076,"path":1077,"stem":1078},"Архітектура платіжної підсистеми","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-architecture","01.csharp\u002F11.aspnet\u002F08.payments\u002F04.payment-architecture",{"title":1080,"path":1081,"stem":1082},"Інтеграція LiqPay (ПриватБанк)","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fliqpay-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F05.liqpay-integration",{"title":1084,"path":1085,"stem":1086},"Інтеграція Monobank Acquiring API","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fmonobank-acquiring","01.csharp\u002F11.aspnet\u002F08.payments\u002F06.monobank-acquiring",{"title":1088,"path":1089,"stem":1090},"Інтеграція Stripe","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fstripe-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F07.stripe-integration",{"title":1092,"path":1093,"stem":1094},"Webhooks — глибоке занурення","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fwebhooks-deep-dive","01.csharp\u002F11.aspnet\u002F08.payments\u002F08.webhooks-deep-dive",{"title":1096,"path":1097,"stem":1098},"Підписки та рекурентні платежі","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fsubscriptions-recurring","01.csharp\u002F11.aspnet\u002F08.payments\u002F09.subscriptions-recurring",{"title":1100,"path":1101,"stem":1102},"Повернення коштів та диспути","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Frefunds-disputes","01.csharp\u002F11.aspnet\u002F08.payments\u002F10.refunds-disputes",{"title":1104,"path":1105,"stem":1106},"Тестування платіжних інтеграцій","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Ftesting-payments","01.csharp\u002F11.aspnet\u002F08.payments\u002F11.testing-payments",{"title":1108,"path":1109,"stem":1110},"Чекліст виходу в Production","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fproduction-checklist","01.csharp\u002F11.aspnet\u002F08.payments\u002F12.production-checklist",{"title":1112,"icon":1113,"items":1114,"path":1127,"stem":1128,"children":1129,"page":59},"Популярні бібліотеки","lucide:box",[1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126],"01.fluent-validation","02.mapster","03.erroror-result-pattern","04.serilog","05.mediatr","06.polly","07.health-checks","08.feature-management","09.fluent-email","10.quest-pdf","11.bogus","12.humanizer-guard","\u002Fcsharp\u002Faspnet\u002Flibraries","01.csharp\u002F11.aspnet\u002F09.libraries",[1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178],{"title":1131,"path":1132,"stem":1133},"Валідація з FluentValidation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-validation","01.csharp\u002F11.aspnet\u002F09.libraries\u002F01.fluent-validation",{"title":1135,"path":1136,"stem":1137},"Маппінг об","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmapster","01.csharp\u002F11.aspnet\u002F09.libraries\u002F02.mapster",{"title":1139,"path":1140,"stem":1141},"Обробка помилок з ErrorOr та Result Pattern в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ferroror-result-pattern","01.csharp\u002F11.aspnet\u002F09.libraries\u002F03.erroror-result-pattern",{"title":1143,"path":1144,"stem":1145},"Структуроване логування з Serilog в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fserilog","01.csharp\u002F11.aspnet\u002F09.libraries\u002F04.serilog",{"title":1147,"path":1148,"stem":1149},"CQRS та Mediator з MediatR в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmediatr","01.csharp\u002F11.aspnet\u002F09.libraries\u002F05.mediatr",{"title":1151,"path":1152,"stem":1153},"Відмовостійкість з Polly в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fpolly","01.csharp\u002F11.aspnet\u002F09.libraries\u002F06.polly",{"title":1155,"path":1156,"stem":1157},"Health Checks в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F09.libraries\u002F07.health-checks",{"title":1159,"path":1160,"stem":1161},"Feature Management та Feature Flags в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffeature-management","01.csharp\u002F11.aspnet\u002F09.libraries\u002F08.feature-management",{"title":1163,"path":1164,"stem":1165},"Відправка Email з FluentEmail в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-email","01.csharp\u002F11.aspnet\u002F09.libraries\u002F09.fluent-email",{"title":1167,"path":1168,"stem":1169},"Генерація PDF з QuestPDF в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fquest-pdf","01.csharp\u002F11.aspnet\u002F09.libraries\u002F10.quest-pdf",{"title":1171,"path":1172,"stem":1173},"Генерація тестових даних з Bogus в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fbogus","01.csharp\u002F11.aspnet\u002F09.libraries\u002F11.bogus",{"title":1175,"path":1176,"stem":1177},"Humanizer та Guard Clauses в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhumanizer-guard","01.csharp\u002F11.aspnet\u002F09.libraries\u002F12.humanizer-guard",{"title":1179,"path":1180,"stem":1181},"План модуля 10.libraries — Популярні бібліотеки ASP.NET","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fplan","01.csharp\u002F11.aspnet\u002F09.libraries\u002Fplan",{"title":1183,"icon":1184,"path":1185,"stem":1186,"children":1187,"page":59},"Razor Pages","i-lucide-layout-template","\u002Fcsharp\u002Faspnet\u002Frazor-pages","01.csharp\u002F11.aspnet\u002F10.razor-pages",[1188,1192,1196,1200,1204,1208],{"title":1189,"path":1190,"stem":1191},"Від Minimal API до Razor Pages: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ffrom-minimal-api","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F01.from-minimal-api",{"title":1193,"path":1194,"stem":1195},"PageModel: логіка сторінки Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fpage-model","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F02.page-model",{"title":1197,"path":1198,"stem":1199},"Razor синтаксис: шаблонізатор у .cshtml","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Frazor-syntax","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F03.razor-syntax",{"title":1201,"path":1202,"stem":1203},"Tag Helpers: типізований HTML","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ftag-helpers","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F04.tag-helpers",{"title":1205,"path":1206,"stem":1207},"Форми і валідація: повний цикл обробки даних","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fforms-validation","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F05.forms-validation",{"title":1209,"path":1210,"stem":1211},"Практичний проєкт: TaskManager на Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fproject-task-manager","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F06.project-task-manager",{"title":1213,"path":1214,"stem":1215,"children":1216,"page":59},"ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc","01.csharp\u002F11.aspnet\u002F11.mvc",[1217,1221,1225,1229,1233,1237,1241,1245,1249,1253,1257,1261,1265,1269,1273,1277,1281,1285],{"title":1218,"path":1219,"stem":1220},"Патерн MVC: архітектура, що змінила веб","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-pattern","01.csharp\u002F11.aspnet\u002F11.mvc\u002F01.mvc-pattern",{"title":1222,"path":1223,"stem":1224},"Від Razor Pages до MVC: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffrom-razor-pages","01.csharp\u002F11.aspnet\u002F11.mvc\u002F02.from-razor-pages",{"title":1226,"path":1227,"stem":1228},"Controllers та Actions: серце MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fcontrollers-actions","01.csharp\u002F11.aspnet\u002F11.mvc\u002F03.controllers-actions",{"title":1230,"path":1231,"stem":1232},"Маршрутизація в MVC: Convention vs Attribute Routing","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Frouting-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F04.routing-mvc",{"title":1234,"path":1235,"stem":1236},"Model Binding: від HTTP до C#","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmodel-binding","01.csharp\u002F11.aspnet\u002F11.mvc\u002F05.model-binding",{"title":1238,"path":1239,"stem":1240},"Views, ViewData, ViewBag, TempData і ViewModel","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fviews-viewdata-tempdata","01.csharp\u002F11.aspnet\u002F11.mvc\u002F06.views-viewdata-tempdata",{"title":1242,"path":1243,"stem":1244},"Filters: аспектно-орієнтоване програмування в MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffilters","01.csharp\u002F11.aspnet\u002F11.mvc\u002F07.filters",{"title":1246,"path":1247,"stem":1248},"Areas: структурування великих застосунків","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fareas","01.csharp\u002F11.aspnet\u002F11.mvc\u002F08.areas",{"title":1250,"path":1251,"stem":1252},"View Components: повторювані незалежні блоки UI","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fview-components","01.csharp\u002F11.aspnet\u002F11.mvc\u002F09.view-components",{"title":1254,"path":1255,"stem":1256},"Display та Editor Templates","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fdisplay-editor-templates","01.csharp\u002F11.aspnet\u002F11.mvc\u002F10.display-editor-templates",{"title":1258,"path":1259,"stem":1260},"Валідація: IValidatableObject та FluentValidation","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fvalidation-advanced","01.csharp\u002F11.aspnet\u002F11.mvc\u002F11.validation-advanced",{"title":1262,"path":1263,"stem":1264},"HTMX: інтерактивність через HTML-атрибути","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx","01.csharp\u002F11.aspnet\u002F11.mvc\u002F12.htmx",{"title":1266,"path":1267,"stem":1268},"HTMX у ASP.NET Core MVC: серверна інтеграція","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fajax-htmx-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F13.ajax-htmx-mvc",{"title":1270,"path":1271,"stem":1272},"Практичний проєкт: Каталог товарів з HTMX","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F14.htmx-project",{"title":1274,"path":1275,"stem":1276},"Завантаження та обробка файлів","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffile-upload","01.csharp\u002F11.aspnet\u002F11.mvc\u002F15.file-upload",{"title":1278,"path":1279,"stem":1280},"Глобалізація та Локалізація MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fglobalization-localization","01.csharp\u002F11.aspnet\u002F11.mvc\u002F16.globalization-localization",{"title":1282,"path":1283,"stem":1284},"Підсумковий проєкт: Блог-платформа","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F17.mvc-project",{"title":1286,"path":1287,"stem":1288},"План курсу: ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fplan","01.csharp\u002F11.aspnet\u002F11.mvc\u002Fplan",{"title":1290,"path":1291,"stem":1292,"children":1293,"page":59},"Web Api","\u002Fcsharp\u002Faspnet\u002Fweb-api","01.csharp\u002F11.aspnet\u002F12.web-api",[1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342],{"title":1295,"path":1296,"stem":1297},"Від Minimal API до Controller-based API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffrom-minimal-api-to-controllers","01.csharp\u002F11.aspnet\u002F12.web-api\u002F01.from-minimal-api-to-controllers",{"title":1299,"path":1300,"stem":1301},"ControllerBase, ActionResult\u003CT> та Response Types","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontroller-base-actionresult","01.csharp\u002F11.aspnet\u002F12.web-api\u002F02.controller-base-actionresult",{"title":1303,"path":1304,"stem":1305},"Content Negotiation - JSON, XML та власні форматери","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontent-negotiation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F03.content-negotiation",{"title":1307,"path":1308,"stem":1309},"Версіонування API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-versioning","01.csharp\u002F11.aspnet\u002F12.web-api\u002F04.api-versioning",{"title":1311,"path":1312,"stem":1313},"ProblemDetails та структурована обробка помилок","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fproblemdetails-error-handling","01.csharp\u002F11.aspnet\u002F12.web-api\u002F05.problemdetails-error-handling",{"title":1315,"path":1316,"stem":1317},"Фільтри у Web API контексті","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffilters-for-api","01.csharp\u002F11.aspnet\u002F12.web-api\u002F06.filters-for-api",{"title":1319,"path":1320,"stem":1321},"Пагінація, фільтрація та сортування","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fpagination-filtering-sorting","01.csharp\u002F11.aspnet\u002F12.web-api\u002F07.pagination-filtering-sorting",{"title":1323,"path":1324,"stem":1325},"HATEOAS та Resource Expansion","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhateoas-resource-expansion","01.csharp\u002F11.aspnet\u002F12.web-api\u002F08.hateoas-resource-expansion",{"title":1327,"path":1328,"stem":1329},"Гібридна архітектура - Minimal API + Controllers","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fminimal-api-vs-controllers-hybrid","01.csharp\u002F11.aspnet\u002F12.web-api\u002F09.minimal-api-vs-controllers-hybrid",{"title":1331,"path":1332,"stem":1333},"Документація API - Swashbuckle, NSwag та генерація клієнтів","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-documentation-generation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F10.api-documentation-generation",{"title":1335,"path":1336,"stem":1337},"Health Checks та моніторинг API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhealth-checks-monitoring","01.csharp\u002F11.aspnet\u002F12.web-api\u002F11.health-checks-monitoring",{"title":1339,"path":1340,"stem":1341},"Підсумковий проєкт - Production-Ready REST API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fweb-api-project","01.csharp\u002F11.aspnet\u002F12.web-api\u002F12.web-api-project",{"title":1343,"path":1344,"stem":1345},"План курсу: ASP.NET Core Web API (Controllers)","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fplan","01.csharp\u002F11.aspnet\u002F12.web-api\u002Fplan",{"title":1347,"icon":1348,"path":1349,"stem":1350,"children":1351,"page":59},"Моніторинг","i-lucide-activity","\u002Fcsharp\u002Faspnet\u002Fmonitoring","01.csharp\u002F11.aspnet\u002F13.monitoring",[1352,1356,1360],{"title":1353,"path":1354,"stem":1355},"Спостережуваність: від console.log до production-систем","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fobservability-intro","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F01.observability-intro",{"title":1357,"path":1358,"stem":1359},"Health Checks: перший рівень observability","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F02.health-checks",{"title":1361,"path":1362,"stem":1363},"Вбудовані метрики .NET 10 та System.Diagnostics.Metrics","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fdotnet-metrics","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F03.dotnet-metrics",{"title":1365,"icon":1366,"path":1367,"stem":1368,"children":1369,"page":59},"Desktop UI","i-lucide-app-window","\u002Fcsharp\u002Fdesktop-ui","01.csharp\u002F12.desktop-ui",[1370,1374,1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426,1430,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1514,1518,1522,1526,1530,1534,1538,1542,1546,1550,1554,1558,1562,1566,1570,1574,1578,1582,1586,1590,1594,1598,1602,1606,1610,1614,1618,1622,1626,1630,1634,1638,1642,1646,1650],{"title":1371,"path":1372,"stem":1373},"Що таке десктопна розробка?","\u002Fcsharp\u002Fdesktop-ui\u002Fwhat-is-desktop-dev","01.csharp\u002F12.desktop-ui\u002F01.what-is-desktop-dev",{"title":1375,"path":1376,"stem":1377},"Архітектура WPF — як влаштований графічний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-architecture","01.csharp\u002F12.desktop-ui\u002F02.wpf-architecture",{"title":1379,"path":1380,"stem":1381},"Перший WPF-проєкт — від нуля до вікна","\u002Fcsharp\u002Fdesktop-ui\u002Ffirst-wpf-app","01.csharp\u002F12.desktop-ui\u002F03.first-wpf-app",{"title":1383,"path":1384,"stem":1385},"Перший Avalonia-проєкт: WPF для всіх платформ","\u002Fcsharp\u002Fdesktop-ui\u002F03a.first-avalonia-app","01.csharp\u002F12.desktop-ui\u002F03a.first-avalonia-app",{"title":1387,"path":1388,"stem":1389},"XAML: декларативний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-basics","01.csharp\u002F12.desktop-ui\u002F04.xaml-basics",{"title":1391,"path":1392,"stem":1393},"Fluent UI у WPF — сучасний дизайн Windows 11","\u002Fcsharp\u002Fdesktop-ui\u002F04a.wpf-fluent-ui","01.csharp\u002F12.desktop-ui\u002F04a.wpf-fluent-ui",{"title":1395,"path":1396,"stem":1397},"WPF UI — сучасна бібліотека Fluent контролів","\u002Fcsharp\u002Fdesktop-ui\u002F04b.wpf-ui-library","01.csharp\u002F12.desktop-ui\u002F04b.wpf-ui-library",{"title":1399,"path":1400,"stem":1401},"HandyControl — велика бібліотека UI контролів для WPF","\u002Fcsharp\u002Fdesktop-ui\u002F04c.handycontrol-library","01.csharp\u002F12.desktop-ui\u002F04c.handycontrol-library",{"title":1403,"path":1404,"stem":1405},"Простори імен та ресурси XAML","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-namespaces-resources","01.csharp\u002F12.desktop-ui\u002F05.xaml-namespaces-resources",{"title":1407,"path":1408,"stem":1409},"XAML в Avalonia: ключові відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F05a.avalonia-xaml-differences","01.csharp\u002F12.desktop-ui\u002F05a.avalonia-xaml-differences",{"title":1411,"path":1412,"stem":1413},"Розширення розмітки XAML (Markup Extensions)","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-markup-extensions","01.csharp\u002F12.desktop-ui\u002F06.xaml-markup-extensions",{"title":1415,"path":1416,"stem":1417},"Панелі Layout: StackPanel, WrapPanel, DockPanel","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part1","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part1",{"title":1419,"path":1420,"stem":1421},"Grid, Canvas, UniformGrid","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part2","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part2",{"title":1423,"path":1424,"stem":1425},"Просунуті техніки Layout","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-advanced","01.csharp\u002F12.desktop-ui\u002F08.layout-advanced",{"title":1427,"path":1428,"stem":1429},"Адаптивний Layout та найкращі практики","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-responsive","01.csharp\u002F12.desktop-ui\u002F09.layout-responsive",{"title":1431,"path":1432,"stem":1433},"Layout в Avalonia: відмінності та нові можливості","\u002Fcsharp\u002Fdesktop-ui\u002F09a.layout-avalonia","01.csharp\u002F12.desktop-ui\u002F09a.layout-avalonia",{"title":1435,"path":1436,"stem":1437},"Button, Image, ProgressBar та інші базові контроли","\u002Fcsharp\u002Fdesktop-ui\u002Fbasic-controls","01.csharp\u002F12.desktop-ui\u002F10.basic-controls",{"title":1439,"path":1440,"stem":1441},"Контроли в Avalonia: відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F10a.controls-avalonia","01.csharp\u002F12.desktop-ui\u002F10a.controls-avalonia",{"title":1443,"path":1444,"stem":1445},"Текстові контроли — TextBlock, TextBox, RichTextBox","\u002Fcsharp\u002Fdesktop-ui\u002Ftext-controls","01.csharp\u002F12.desktop-ui\u002F11.text-controls",{"title":1447,"path":1448,"stem":1449},"Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker","\u002Fcsharp\u002Fdesktop-ui\u002Fselection-controls","01.csharp\u002F12.desktop-ui\u002F12.selection-controls",{"title":1451,"path":1452,"stem":1453},"Content Model — GroupBox, Expander, TabControl, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fcontent-controls","01.csharp\u002F12.desktop-ui\u002F13.content-controls",{"title":1455,"path":1456,"stem":1457},"UI\u002FUX принципи десктопних застосунків","\u002Fcsharp\u002Fdesktop-ui\u002F13a.ui-ux-principles","01.csharp\u002F12.desktop-ui\u002F13a.ui-ux-principles",{"title":1459,"path":1460,"stem":1461},"Dependency Properties — Концепція та Value Resolution","\u002Fcsharp\u002Fdesktop-ui\u002Fdependency-properties-part1","01.csharp\u002F12.desktop-ui\u002F14.dependency-properties-part1",{"title":1463,"path":1464,"stem":1465},"Avalonia Property System — StyledProperty та DirectProperty","\u002Fcsharp\u002Fdesktop-ui\u002F14a.avalonia-property-system","01.csharp\u002F12.desktop-ui\u002F14a.avalonia-property-system",{"title":1467,"path":1468,"stem":1469},"Attached Properties — Властивості без меж","\u002Fcsharp\u002Fdesktop-ui\u002Fattached-properties","01.csharp\u002F12.desktop-ui\u002F15.attached-properties",{"title":1471,"path":1472,"stem":1473},"Routed Events — Маршрутизація подій у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Frouted-events","01.csharp\u002F12.desktop-ui\u002F16.routed-events",{"title":1475,"path":1476,"stem":1477},"Data Binding — Від Code-Behind до Декларативності","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part1","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part1",{"title":1479,"path":1480,"stem":1481},"INotifyPropertyChanged — Живе оновлення UI","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part2","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part2",{"title":1483,"path":1484,"stem":1485},"Compiled Bindings в Avalonia — Безпека на етапі компіляції","\u002Fcsharp\u002Fdesktop-ui\u002F17a.avalonia-compiled-bindings","01.csharp\u002F12.desktop-ui\u002F17a.avalonia-compiled-bindings",{"title":1487,"path":1488,"stem":1489},"Просунутий Data Binding — ElementName, RelativeSource, MultiBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-advanced","01.csharp\u002F12.desktop-ui\u002F18.data-binding-advanced",{"title":1491,"path":1492,"stem":1493},"Value Converters — Перетворення типів даних у Data Binding","\u002Fcsharp\u002Fdesktop-ui\u002Fvalue-converters","01.csharp\u002F12.desktop-ui\u002F19.value-converters",{"title":1495,"path":1496,"stem":1497},"Data Templates — Візуалізація об'єктів у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-templates","01.csharp\u002F12.desktop-ui\u002F20.data-templates",{"title":1499,"path":1500,"stem":1501},"Collections Binding Part 1 — ObservableCollection та ItemsControl","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part1","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part1",{"title":1503,"path":1504,"stem":1505},"Collections Binding Part 2 — ICollectionView, Filtering, Sorting та Virtualization","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part2","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part2",{"title":1507,"path":1508,"stem":1509},"MVVM Pattern — Від Spaghetti Code до архітектури","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-pattern","01.csharp\u002F12.desktop-ui\u002F22.mvvm-pattern",{"title":1511,"path":1512,"stem":1513},"ViewModel Implementation — Від BaseViewModel до валідації","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-implementation","01.csharp\u002F12.desktop-ui\u002F23.viewmodel-implementation",{"title":1515,"path":1516,"stem":1517},"Commands — Від event handlers до декларативних команд","\u002Fcsharp\u002Fdesktop-ui\u002Fcommands","01.csharp\u002F12.desktop-ui\u002F24.commands",{"title":1519,"path":1520,"stem":1521},"MVVM Toolkit — MVVM без boilerplate через Source Generators","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-toolkit","01.csharp\u002F12.desktop-ui\u002F25.mvvm-toolkit",{"title":1523,"path":1524,"stem":1525},"Messenger Pattern — Комунікація між ViewModel без прямих посилань","\u002Fcsharp\u002Fdesktop-ui\u002Fmessenger-pattern","01.csharp\u002F12.desktop-ui\u002F26.messenger-pattern",{"title":1527,"path":1528,"stem":1529},"Стилі WPF — CSS для десктопу","\u002Fcsharp\u002Fdesktop-ui\u002Fstyles-basics","01.csharp\u002F12.desktop-ui\u002F27.styles-basics",{"title":1531,"path":1532,"stem":1533},"CSS-like стилі Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F27a.avalonia-css-styling","01.csharp\u002F12.desktop-ui\u002F27a.avalonia-css-styling",{"title":1535,"path":1536,"stem":1537},"Control Templates — Частина 1. Концепція та TemplateBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part1","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part1",{"title":1539,"path":1540,"stem":1541},"Control Templates — Частина 2. Named Parts та ContentPresenter","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part2","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part2",{"title":1543,"path":1544,"stem":1545},"Control Themes в Avalonia — нова ера стилізації","\u002Fcsharp\u002Fdesktop-ui\u002F28a.avalonia-control-themes","01.csharp\u002F12.desktop-ui\u002F28a.avalonia-control-themes",{"title":1547,"path":1548,"stem":1549},"Triggers та Visual State Manager у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Ftriggers-visual-states","01.csharp\u002F12.desktop-ui\u002F29.triggers-visual-states",{"title":1551,"path":1552,"stem":1553},"Pseudo-classes в Avalonia — замість WPF Triggers","\u002Fcsharp\u002Fdesktop-ui\u002F29a.avalonia-pseudo-classes","01.csharp\u002F12.desktop-ui\u002F29a.avalonia-pseudo-classes",{"title":1555,"path":1556,"stem":1557},"Теми та ресурсні словники у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fresources-themes","01.csharp\u002F12.desktop-ui\u002F30.resources-themes",{"title":1559,"path":1560,"stem":1561},"Avalonia Themes — Fluent Design та система тематизації","\u002Fcsharp\u002Fdesktop-ui\u002F30a.avalonia-themes-fluent","01.csharp\u002F12.desktop-ui\u002F30a.avalonia-themes-fluent",{"title":1563,"path":1564,"stem":1565},"Контроли колекцій — глибоке занурення","\u002Fcsharp\u002Fdesktop-ui\u002Fcollection-controls","01.csharp\u002F12.desktop-ui\u002F31.collection-controls",{"title":1567,"path":1568,"stem":1569},"DataGrid — колонки та базове відображення","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part1","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part1",{"title":1571,"path":1572,"stem":1573},"DataGrid — сортування, фільтрація, редагування","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part2","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part2",{"title":1575,"path":1576,"stem":1577},"TreeView та GridView","\u002Fcsharp\u002Fdesktop-ui\u002Ftreeview-listview","01.csharp\u002F12.desktop-ui\u002F33.treeview-listview",{"title":1579,"path":1580,"stem":1581},"Меню, Toolbar, ContextMenu, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fmenus-toolbars","01.csharp\u002F12.desktop-ui\u002F34.menus-toolbars",{"title":1583,"path":1584,"stem":1585},"Навігація та керування вікнами. Частина 1: вікна та сторінки","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part1","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part1",{"title":1587,"path":1588,"stem":1589},"Навігація та керування вікнами. Частина 2: MVVM-навігація","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part2","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part2",{"title":1591,"path":1592,"stem":1593},"Avalonia — Навігація та діалоги","\u002Fcsharp\u002Fdesktop-ui\u002F35a.avalonia-navigation-dialogs","01.csharp\u002F12.desktop-ui\u002F35a.avalonia-navigation-dialogs",{"title":1595,"path":1596,"stem":1597},"Діалоги та File Pickers у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdialogs-file-pickers","01.csharp\u002F12.desktop-ui\u002F36.dialogs-file-pickers",{"title":1599,"path":1600,"stem":1601},"UserControl: компонентний підхід у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fuser-controls","01.csharp\u002F12.desktop-ui\u002F37.user-controls",{"title":1603,"path":1604,"stem":1605},"Custom Controls: Lookless Controls у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fcustom-controls","01.csharp\u002F12.desktop-ui\u002F38.custom-controls",{"title":1607,"path":1608,"stem":1609},"Avalonia TemplatedControl — Lookless Controls","\u002Fcsharp\u002Fdesktop-ui\u002F38a.avalonia-templated-controls","01.csharp\u002F12.desktop-ui\u002F38a.avalonia-templated-controls",{"title":1611,"path":1612,"stem":1613},"Анімації у WPF: Storyboard та Easing Functions","\u002Fcsharp\u002Fdesktop-ui\u002Fanimations-transitions","01.csharp\u002F12.desktop-ui\u002F39.animations-transitions",{"title":1615,"path":1616,"stem":1617},"Анімації в Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F39a.avalonia-animations","01.csharp\u002F12.desktop-ui\u002F39a.avalonia-animations",{"title":1619,"path":1620,"stem":1621},"2D Графіка та Мультимедіа у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fmedia-graphics","01.csharp\u002F12.desktop-ui\u002F40.media-graphics",{"title":1623,"path":1624,"stem":1625},"Dependency Injection у WPF та Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Fdi-integration","01.csharp\u002F12.desktop-ui\u002F41.di-integration",{"title":1627,"path":1628,"stem":1629},"SQLite та EF Core у десктопних додатках","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part1","01.csharp\u002F12.desktop-ui\u002F42.data-persistence-part1",{"title":1631,"path":1632,"stem":1633},"Repository Pattern та Unit of Work","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part2","01.csharp\u002F12.desktop-ui\u002F43.data-persistence-part2",{"title":1635,"path":1636,"stem":1637},"Тестування ViewModels","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-testing","01.csharp\u002F12.desktop-ui\u002F44.viewmodel-testing",{"title":1639,"path":1640,"stem":1641},"Avalonia Headless Testing — тестування UI без вікон","\u002Fcsharp\u002Fdesktop-ui\u002F44a.avalonia-headless-testing","01.csharp\u002F12.desktop-ui\u002F44a.avalonia-headless-testing",{"title":1643,"path":1644,"stem":1645},"Кросплатформна розробка з Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-cross-platform","01.csharp\u002F12.desktop-ui\u002F45.avalonia-cross-platform",{"title":1647,"path":1648,"stem":1649},"Пакування та розгортання Avalonia додатків","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F46.avalonia-packaging-deployment",{"title":1651,"path":1652,"stem":1653},"Розгортання WPF застосунків","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F47.wpf-packaging-deployment",{"title":1655,"icon":658,"path":1656,"stem":1657,"children":1658,"page":59},"Network Programming","\u002Fcsharp\u002Fnetwork-programming","01.csharp\u002F13.network-programming",[1659,1663,1667,1671,1675,1679,1683,1687,1691,1695,1699],{"title":1660,"path":1661,"stem":1662},"Основи комп'ютерних мереж","\u002Fcsharp\u002Fnetwork-programming\u002Ffoundations","01.csharp\u002F13.network-programming\u002F01.foundations",{"title":1664,"path":1665,"stem":1666},"Модель OSI та стек TCP\u002FIP","\u002Fcsharp\u002Fnetwork-programming\u002Fosi-model","01.csharp\u002F13.network-programming\u002F02.osi-model",{"title":1668,"path":1669,"stem":1670},"IP-протокол та адресація","\u002Fcsharp\u002Fnetwork-programming\u002Fip-addressing","01.csharp\u002F13.network-programming\u002F03.ip-addressing",{"title":1672,"path":1673,"stem":1674},"UDP — протокол без з'єднання","\u002Fcsharp\u002Fnetwork-programming\u002Fudp","01.csharp\u002F13.network-programming\u002F05.udp",{"title":1676,"path":1677,"stem":1678},"UDP Broadcast та Multicast","\u002Fcsharp\u002Fnetwork-programming\u002Fudp-broadcast-multicast","01.csharp\u002F13.network-programming\u002F06.udp-broadcast-multicast",{"title":1680,"path":1681,"stem":1682},"HTTP — протокол вебу","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-fundamentals","01.csharp\u002F13.network-programming\u002F07.http-fundamentals",{"title":1684,"path":1685,"stem":1686},"HttpListener — вбудований HTTP-сервер .NET","\u002Fcsharp\u002Fnetwork-programming\u002F07a.http-listener","01.csharp\u002F13.network-programming\u002F07a.http-listener",{"title":1688,"path":1689,"stem":1690},"HTTP Advanced — cookies, аутентифікація та HTTPS","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-advanced","01.csharp\u002F13.network-programming\u002F08.http-advanced",{"title":1692,"path":1693,"stem":1694},"SMTP та протоколи електронної пошти","\u002Fcsharp\u002Fnetwork-programming\u002Fsmtp","01.csharp\u002F13.network-programming\u002F09.smtp",{"title":1696,"path":1697,"stem":1698},"WebSocket — повнодуплексний протокол реального часу","\u002Fcsharp\u002Fnetwork-programming\u002Fwebsockets","01.csharp\u002F13.network-programming\u002F10.websockets",{"title":1700,"path":1701,"stem":1702},"TLS\u002FSSL — криптографічний захист мережевих з'єднань","\u002Fcsharp\u002Fnetwork-programming\u002Ftls-ssl","01.csharp\u002F13.network-programming\u002F11.tls-ssl",{"title":1704,"path":1705,"stem":1706},"C# & .NET: The Ultimate Roadmap","\u002Fcsharp\u002Froadmap","01.csharp\u002Froadmap",{"title":1708,"icon":1709,"path":1710,"stem":1711,"children":1712,"page":59},"C++","i-devicon-cplusplus","\u002Fcpp","02.cpp",[1713,1717,1721,1725,1729,1733,1737,1741,1745,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892],{"title":1714,"path":1715,"stem":1716},"Вступ у програмування та алгоритми","\u002Fcpp\u002Fintro-algorithms","02.cpp\u002F01.intro-algorithms",{"title":1718,"path":1719,"stem":1720},"Code Style: угоди про оформлення коду","\u002Fcpp\u002Fcode-style","02.cpp\u002F02.code-style",{"title":1722,"path":1723,"stem":1724},"Середовище розробки та перший проєкт","\u002Fcpp\u002Fide-setup","02.cpp\u002F03.ide-setup",{"title":1726,"path":1727,"stem":1728},"Вивід даних на екран","\u002Fcpp\u002Fdata-output","02.cpp\u002F04.data-output",{"title":1730,"path":1731,"stem":1732},"Типи даних, змінні та константи","\u002Fcpp\u002Fdata-types-variables","02.cpp\u002F05.data-types-variables",{"title":1734,"path":1735,"stem":1736},"Ввід даних з клавіатури","\u002Fcpp\u002Fdata-input","02.cpp\u002F06.data-input",{"title":1738,"path":1739,"stem":1740},"Оператори, перетворення типів та логічні операції","\u002Fcpp\u002Foperators-type-conversion","02.cpp\u002F07.operators-type-conversion",{"title":1742,"path":1743,"stem":1744},"Цикли","\u002Fcpp\u002Floops","02.cpp\u002F08.loops",{"title":32,"path":1746,"stem":1747},"\u002Fcpp\u002Farrays","02.cpp\u002F09.arrays",{"title":1749,"path":1750,"stem":1751},"Алгоритми сортування та аналіз складності","\u002Fcpp\u002Fsorting","02.cpp\u002F10.sorting",{"title":1753,"path":1754,"stem":1755},"Алгоритми пошуку","\u002Fcpp\u002Fsearching","02.cpp\u002F11.searching",{"title":1757,"path":1758,"stem":1759},"Функції: основи","\u002Fcpp\u002Ffunctions-basics","02.cpp\u002F12.functions-basics",{"title":1761,"path":1762,"stem":1763},"Функції: прототипи, область видимості та додаткові можливості","\u002Fcpp\u002Ffunctions-scope","02.cpp\u002F13.functions-scope",{"title":1765,"path":1766,"stem":1767},"Функції: перевантаження та шаблони","\u002Fcpp\u002Ffunctions-overloading-templates","02.cpp\u002F14.functions-overloading-templates",{"title":1769,"path":1770,"stem":1771},"Вказівники: основи","\u002Fcpp\u002Fpointers-basics","02.cpp\u002F15.pointers-basics",{"title":1773,"path":1774,"stem":1775},"Посилання (References)","\u002Fcpp\u002Freferences","02.cpp\u002F16.references",{"title":1777,"path":1778,"stem":1779},"Вказівники, const і масиви","\u002Fcpp\u002Fpointers-const-arrays","02.cpp\u002F17.pointers-const-arrays",{"title":1781,"path":1782,"stem":1783},"Адресна арифметика","\u002Fcpp\u002Fpointer-arithmetic","02.cpp\u002F18.pointer-arithmetic",{"title":1785,"path":1786,"stem":1787},"Динамічна пам'ять","\u002Fcpp\u002Fdynamic-memory","02.cpp\u002F19.dynamic-memory",{"title":1789,"path":1790,"stem":1791},"Вказівники типу void","\u002Fcpp\u002Fvoid-pointers","02.cpp\u002F20.void-pointers",{"title":1793,"path":1794,"stem":1795},"Вказівники на вказівники","\u002Fcpp\u002Fpointers-to-pointers","02.cpp\u002F21.pointers-to-pointers",{"title":1797,"path":1798,"stem":1799},"Оператор доступу до членів через вказівник (->)","\u002Fcpp\u002Fmember-access-operator","02.cpp\u002F22.member-access-operator",{"title":1801,"path":1802,"stem":1803},"Цикл for-each (Range-based for)","\u002Fcpp\u002Fforeach-loop","02.cpp\u002F23.foreach-loop",{"title":1805,"path":1806,"stem":1807},"Вказівники на функції","\u002Fcpp\u002Ffunction-pointers","02.cpp\u002F24.function-pointers",{"title":1809,"path":1810,"stem":1811},"Лямбда-вирази","\u002Fcpp\u002Flambdas","02.cpp\u002F25.lambdas",{"title":1813,"path":1814,"stem":1815},"Лямбда-захоплення","\u002Fcpp\u002Flambda-captures","02.cpp\u002F26.lambda-captures",{"title":1817,"path":1818,"stem":1819},"Еліпсис","\u002Fcpp\u002Fellipsis","02.cpp\u002F27.ellipsis",{"title":1821,"path":1822,"stem":1823},"Безпечні альтернативи еліпсису","\u002Fcpp\u002F27a.ellipsis","02.cpp\u002F27a.ellipsis",{"title":1825,"path":1826,"stem":1827},"Аргументи командного рядка","\u002Fcpp\u002Fcommand-line-arguments","02.cpp\u002F28.command-line-arguments",{"title":1829,"path":1830,"stem":1831},"Перерахування (enum)","\u002Fcpp\u002Fenum","02.cpp\u002F29.enum",{"title":1833,"path":1834,"stem":1835},"Класи-перерахування (enum class)","\u002Fcpp\u002Fenum-class","02.cpp\u002F30.enum-class",{"title":1837,"path":1838,"stem":1839},"Псевдоніми типів (typedef і using)","\u002Fcpp\u002Ftype-aliases","02.cpp\u002F31.type-aliases",{"title":1841,"path":1842,"stem":1843},"Системи числення та двійкова арифметика","\u002Fcpp\u002Fnumber-systems","02.cpp\u002F32.number-systems",{"title":1845,"path":1846,"stem":1847},"Структури (struct): агрегування даних","\u002Fcpp\u002Fstruct","02.cpp\u002F33.struct",{"title":1849,"path":1850,"stem":1851},"Структури у функціях","\u002Fcpp\u002Fstruct-functions","02.cpp\u002F34.struct-functions",{"title":1853,"path":1854,"stem":1855},"Масиви структур і вкладені структури","\u002Fcpp\u002Fstruct-arrays","02.cpp\u002F35.struct-arrays",{"title":1857,"path":1858,"stem":1859},"Патерни struct та межі застосування","\u002Fcpp\u002Fstruct-patterns","02.cpp\u002F36.struct-patterns",{"title":1861,"path":1862,"stem":1863},"Символи та таблиця ASCII","\u002Fcpp\u002Fascii-characters","02.cpp\u002F37.ascii-characters",{"title":1865,"path":1866,"stem":1867},"Unicode та кодування UTF","\u002Fcpp\u002Funicode-utf","02.cpp\u002F38.unicode-utf",{"title":1869,"path":1870,"stem":1871},"C-style рядки","\u002Fcpp\u002Fc-strings","02.cpp\u002F39.c-strings",{"title":1873,"path":1874,"stem":1875},"Вступ до std::string","\u002Fcpp\u002Fstd-string-intro","02.cpp\u002F40.std-string-intro",{"title":1877,"path":1878,"stem":1879},"Довжина, ємність та доступ до символів std::string","\u002Fcpp\u002Fstd-string-capacity-access","02.cpp\u002F41.std-string-capacity-access",{"title":1881,"path":1882,"stem":1883},"Модифікація std::string: присвоювання, додавання, вставка, видалення та заміна","\u002Fcpp\u002Fstd-string-modification","02.cpp\u002F42.std-string-modification",{"title":1885,"path":1886,"stem":1887},"Пошук у std::string: find, npos та практичні патерни","\u002Fcpp\u002Fstd-string-search","02.cpp\u002F43.std-string-search",{"title":1889,"path":1890,"stem":1891},"std::string_view: невласницький погляд на рядок без копіювання","\u002Fcpp\u002Fstd-string-view","02.cpp\u002F44.std-string-view",{"title":1893,"path":1894,"stem":1895},"План навчання: Курс C++ — Продовження (Статті 29–60+)","\u002Fcpp\u002Fcurriculum-plan","02.cpp\u002Fcurriculum-plan",{"title":1897,"icon":1898,"path":1899,"stem":1900,"children":1901,"page":59},"JavaScript","i-devicon-javascript","\u002Fjavascript","03.javascript",[1902,1928,1982,2004,2308,2346],{"title":1903,"icon":1904,"path":1905,"stem":1906,"children":1907,"page":59},"Events","i-lucide-mouse-pointer-click","\u002Fjavascript\u002Fevents","03.javascript\u002F01.events",[1908,1912,1916,1920,1924],{"title":1909,"path":1910,"stem":1911},"Вступ до подій браузера","\u002Fjavascript\u002Fevents\u002Fintro","03.javascript\u002F01.events\u002F01.intro",{"title":1913,"path":1914,"stem":1915},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","\u002Fjavascript\u002Fevents\u002Fbubbling-capturing","03.javascript\u002F01.events\u002F02.bubbling-capturing",{"title":1917,"path":1918,"stem":1919},"Делегування подій (Event Delegation)","\u002Fjavascript\u002Fevents\u002Fdelegate-events","03.javascript\u002F01.events\u002F03.delegate-events",{"title":1921,"path":1922,"stem":1923},"Типові дії браузера та preventDefault()","\u002Fjavascript\u002Fevents\u002Fprevent-default","03.javascript\u002F01.events\u002F04.prevent-default",{"title":1925,"path":1926,"stem":1927},"Запуск користувацьких подій (Custom Events)","\u002Fjavascript\u002Fevents\u002Fcustom-events","03.javascript\u002F01.events\u002F05.custom-events",{"title":1929,"icon":1930,"path":1931,"stem":1932,"children":1933,"page":59},"Network","i-lucide-globe","\u002Fjavascript\u002Fnetwork","03.javascript\u002F02.network",[1934,1938,1942,1946,1950,1954,1958,1962,1966,1970,1974,1978],{"title":1935,"path":1936,"stem":1937},"Fetch API - Сучасний підхід до HTTP-запитів","\u002Fjavascript\u002Fnetwork\u002F01-fetch-api","03.javascript\u002F02.network\u002F01-fetch-api",{"title":1939,"path":1940,"stem":1941},"FormData - Робота з формами та файлами","\u002Fjavascript\u002Fnetwork\u002F02-formdata","03.javascript\u002F02.network\u002F02-formdata",{"title":1943,"path":1944,"stem":1945},"Відстеження прогресу завантаження","\u002Fjavascript\u002Fnetwork\u002F03-download-progress","03.javascript\u002F02.network\u002F03-download-progress",{"title":1947,"path":1948,"stem":1949},"Переривання fetch-запитів","\u002Fjavascript\u002Fnetwork\u002F04-abort-requests","03.javascript\u002F02.network\u002F04-abort-requests",{"title":1951,"path":1952,"stem":1953},"CORS - Запити між різними джерелами","\u002Fjavascript\u002Fnetwork\u002F05-cors","03.javascript\u002F02.network\u002F05-cors",{"title":1955,"path":1956,"stem":1957},"Fetch API - Повний довідник опцій","\u002Fjavascript\u002Fnetwork\u002F06-fetch-options","03.javascript\u002F02.network\u002F06-fetch-options",{"title":1959,"path":1960,"stem":1961},"URL Objects - Робота з посиланнями","\u002Fjavascript\u002Fnetwork\u002F07-url-objects","03.javascript\u002F02.network\u002F07-url-objects",{"title":1963,"path":1964,"stem":1965},"XMLHttpRequest - AJAX та низькорівневі запити","\u002Fjavascript\u002Fnetwork\u002F08-xmlhttprequest","03.javascript\u002F02.network\u002F08-xmlhttprequest",{"title":1967,"path":1968,"stem":1969},"Відновлюване завантаження файлів","\u002Fjavascript\u002Fnetwork\u002F09-resumable-upload","03.javascript\u002F02.network\u002F09-resumable-upload",{"title":1971,"path":1972,"stem":1973},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","\u002Fjavascript\u002Fnetwork\u002F10-cookies","03.javascript\u002F02.network\u002F10-cookies",{"title":1975,"path":1976,"stem":1977},"js-cookie: Керування Cookies без Болю","\u002Fjavascript\u002Fnetwork\u002F11-js-cookie","03.javascript\u002F02.network\u002F11-js-cookie",{"title":1979,"path":1980,"stem":1981},"Axios: Потужний HTTP-клієнт для JavaScript","\u002Fjavascript\u002Fnetwork\u002F12-axios","03.javascript\u002F02.network\u002F12-axios",{"title":1983,"icon":1984,"path":1985,"stem":1986,"children":1987,"page":59},"Bom","i-lucide-monitor","\u002Fjavascript\u002Fbom","03.javascript\u002F03.bom",[1988,1992,1996,2000],{"title":1989,"path":1990,"stem":1991},"LocalStorage, SessionStorage та patterns збереження даних","\u002Fjavascript\u002Fbom\u002F01-localstorage","03.javascript\u002F03.bom\u002F01-localstorage",{"title":1993,"path":1994,"stem":1995},"Location Object - Керування адресою сторінки","\u002Fjavascript\u002Fbom\u002F02-location-object","03.javascript\u002F03.bom\u002F02-location-object",{"title":1997,"path":1998,"stem":1999},"History API - Керування історією браузера","\u002Fjavascript\u002Fbom\u002F03-history-api","03.javascript\u002F03.bom\u002F03-history-api",{"title":2001,"path":2002,"stem":2003},"Navigator Object - Ідентифікація та Можливості Пристрою","\u002Fjavascript\u002Fbom\u002F04-navigator-object","03.javascript\u002F03.bom\u002F04-navigator-object",{"title":2005,"icon":2006,"path":2007,"stem":2008,"children":2009},"React","i-devicon-react","\u002Fjavascript\u002Freact","03.javascript\u002F04.react\u002Findex",[2010,2011,2015,2019,2023,2027,2090,2125,2277],{"title":2005,"path":2007,"stem":2008},{"title":2012,"path":2013,"stem":2014},"Робота з Формами в React","\u002Fjavascript\u002Freact\u002Freact-forms","03.javascript\u002F04.react\u002F01.react-forms",{"title":2016,"path":2017,"stem":2018},"React Hook Form: Професійна Робота з Формами","\u002Fjavascript\u002Freact\u002Freact-hook-form","03.javascript\u002F04.react\u002F02.react-hook-form",{"title":2020,"path":2021,"stem":2022},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","\u002Fjavascript\u002Freact\u002Freact-hook-form-new","03.javascript\u002F04.react\u002F02.react-hook-form-new",{"title":2024,"path":2025,"stem":2026},"Axios та React: Професійна Архітектура Запитів","\u002Fjavascript\u002Freact\u002Fdata-fetching-axios","03.javascript\u002F04.react\u002F03.data-fetching-axios",{"title":2028,"icon":132,"path":2029,"stem":2030,"children":2031},"Tanstack Query","\u002Fjavascript\u002Freact\u002Ftanstack-query","03.javascript\u002F04.react\u002F04.tanstack-query\u002Findex",[2032,2034,2038,2042,2046,2050,2054,2058,2062,2066,2070,2074,2078,2082,2086],{"title":2033,"path":2029,"stem":2030},"TanStack Query: Майстерність Керування Станом Сервера",{"title":2035,"path":2036,"stem":2037},"Парадигма Server State: Чому useEffect недостатньо","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-state-paradigm","03.javascript\u002F04.react\u002F04.tanstack-query\u002F01.server-state-paradigm",{"title":2039,"path":2040,"stem":2041},"Встановлення та Налаштування: Фундамент","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Finstallation-and-devtools","03.javascript\u002F04.react\u002F04.tanstack-query\u002F02.installation-and-devtools",{"title":2043,"path":2044,"stem":2045},"Основи Запитів та Магія Ключів","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fquery-basics-and-keys","03.javascript\u002F04.react\u002F04.tanstack-query\u002F03.query-basics-and-keys",{"title":2047,"path":2048,"stem":2049},"Синхронізація Даних: Життєвий Цикл Запиту","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fdata-synchronization","03.javascript\u002F04.react\u002F04.tanstack-query\u002F04.data-synchronization",{"title":2051,"path":2052,"stem":2053},"Мутації та Інвалідація: Зміна Даних","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fmutations-and-invalidation","03.javascript\u002F04.react\u002F04.tanstack-query\u002F05.mutations-and-invalidation",{"title":2055,"path":2056,"stem":2057},"Оптимістичні Оновлення: Швидше за Світло","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Foptimistic-updates","03.javascript\u002F04.react\u002F04.tanstack-query\u002F06.optimistic-updates",{"title":2059,"path":2060,"stem":2061},"Пагінація та Infinite Scroll","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fpagination-and-load-more","03.javascript\u002F04.react\u002F04.tanstack-query\u002F07.pagination-and-load-more",{"title":2063,"path":2064,"stem":2065},"Просунуті Патерни та Оптимізація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F04.tanstack-query\u002F08.advanced-patterns",{"title":2067,"path":2068,"stem":2069},"Архітектура та Best Practices","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Farchitecture-and-best-practices","03.javascript\u002F04.react\u002F04.tanstack-query\u002F09.architecture-and-best-practices",{"title":2071,"path":2072,"stem":2073},"Server-Side Rendering (SSR) та Гідратація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-side-rendering","03.javascript\u002F04.react\u002F04.tanstack-query\u002F10.server-side-rendering",{"title":2075,"path":2076,"stem":2077},"Стратегії Тестування","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Ftesting-strategies","03.javascript\u002F04.react\u002F04.tanstack-query\u002F11.testing-strategies",{"title":2079,"path":2080,"stem":2081},"Аутентифікація та Обробка Помилок","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fauthentication-and-errors","03.javascript\u002F04.react\u002F04.tanstack-query\u002F12.authentication-and-errors",{"title":2083,"path":2084,"stem":2085},"React Suspense та Майбутнє","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Freact-suspense","03.javascript\u002F04.react\u002F04.tanstack-query\u002F13.react-suspense",{"title":2087,"path":2088,"stem":2089},"Глибоке Занурення в Продуктивність","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fperformance-deep-dive","03.javascript\u002F04.react\u002F04.tanstack-query\u002F14.performance-deep-dive",{"title":2091,"icon":2006,"path":2092,"stem":2093,"children":2094},"React Router","\u002Fjavascript\u002Freact\u002Freact-router","03.javascript\u002F04.react\u002F05.react-router\u002Findex",[2095,2097,2101,2105,2109,2113,2117,2121],{"title":2096,"path":2092,"stem":2093},"React Router: Навігаційна система сучасного вебу",{"title":2098,"path":2099,"stem":2100},"Налаштування та Базовий Роутинг","\u002Fjavascript\u002Freact\u002Freact-router\u002Fsetup-and-basic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F01.setup-and-basic-routing",{"title":2102,"path":2103,"stem":2104},"Динамічна Навігація","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnavigation-and-links","03.javascript\u002F04.react\u002F05.react-router\u002F02.navigation-and-links",{"title":2106,"path":2107,"stem":2108},"Вкладені Маршрути та Макети","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnested-routes-and-layouts","03.javascript\u002F04.react\u002F05.react-router\u002F03.nested-routes-and-layouts",{"title":2110,"path":2111,"stem":2112},"Динамічні Маршрути та Параметри","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdynamic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F04.dynamic-routing",{"title":2114,"path":2115,"stem":2116},"Data APIs: Loaders та Actions","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdata-loading","03.javascript\u002F04.react\u002F05.react-router\u002F05.data-loading",{"title":2118,"path":2119,"stem":2120},"Просунуті Патерни","\u002Fjavascript\u002Freact\u002Freact-router\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F05.react-router\u002F06.advanced-patterns",{"title":2122,"path":2123,"stem":2124},"Legacy Routing: Компонентний підхід","\u002Fjavascript\u002Freact\u002Freact-router\u002Flegacy-routing","03.javascript\u002F04.react\u002F05.react-router\u002F07.legacy-routing",{"title":2126,"icon":132,"path":2127,"stem":2128,"children":2129},"Redux","\u002Fjavascript\u002Freact\u002Fredux","03.javascript\u002F04.react\u002F06.redux\u002Findex",[2130,2132,2148,2177,2186,2207,2223,2252],{"title":2131,"path":2127,"stem":2128},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2133,"stem":2134,"children":2135,"page":59},"\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals",[2136,2140,2144],{"title":2137,"path":2138,"stem":2139},"Вступ до State Management","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fintro-state-management","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F01.intro-state-management",{"title":2141,"path":2142,"stem":2143},"Філософія Redux та Три Принципи","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fredux-philosophy","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F02.redux-philosophy",{"title":2145,"path":2146,"stem":2147},"Чисті функції та Іммутабельність","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fpure-functions-immutability","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F03.pure-functions-immutability",{"title":2149,"icon":132,"path":2150,"stem":2151,"children":2152,"page":59},"Classic Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux",[2153,2157,2161,2165,2169,2173],{"title":2154,"path":2155,"stem":2156},"Створення Store (Classic Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fstore-setup","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F01.store-setup",{"title":2158,"path":2159,"stem":2160},"Actions, Constants та Action Creators","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Factions-constants","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F02.actions-constants",{"title":2162,"path":2163,"stem":2164},"Логіка Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freducers","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F03.reducers",{"title":2166,"path":2167,"stem":2168},"Комбінування Reducers (Root Reducer)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fdata-flow","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F04.data-flow",{"title":2170,"path":2171,"stem":2172},"Підключення до React (React-Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freact-redux-connection","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F05.react-redux-connection",{"title":2174,"path":2175,"stem":2176},"Middleware та Асинхронність (Redux Thunk)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fmiddleware-thunk","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F06.middleware-thunk",{"title":2178,"icon":132,"path":2179,"stem":2180,"children":2181,"page":59},"Transition To Rtk","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk",[2182],{"title":2183,"path":2184,"stem":2185},"Проблеми класичного Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk\u002Fproblems-with-classic","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk\u002F01.problems-with-classic",{"title":2187,"icon":132,"path":2188,"stem":2189,"children":2190,"page":59},"Redux Toolkit","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit",[2191,2195,2199,2203],{"title":2192,"path":2193,"stem":2194},"Налаштування Store з configureStore","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fconfigure-store","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F01.configure-store",{"title":2196,"path":2197,"stem":2198},"createSlice: Революція в Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fcreate-slice","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F02.create-slice",{"title":2200,"path":2201,"stem":2202},"Асинхронність з createAsyncThunk","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fasync-thunks","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F03.async-thunks",{"title":2204,"path":2205,"stem":2206},"04. Entity Adapter: Керування нормалізованим станом","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fentity-adapter","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F04.entity-adapter",{"title":2208,"icon":92,"path":2209,"stem":2210,"children":2211,"page":59},"Advanced","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced",[2212,2216,2220],{"title":2213,"path":2214,"stem":2215},"Мемоізація та Селектори: Повний Гайд по Reselect","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Fselectors-reselect","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F01.selectors-reselect",{"title":2217,"path":2218,"stem":2219},"RTK Query: Архітектура Серверного Кешу","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Frtk-query-intro","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F02.rtk-query-intro",{"title":2067,"path":2221,"stem":2222},"\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Farchitecture-best-practices","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F03.architecture-best-practices",{"title":2224,"icon":132,"path":2225,"stem":2226,"children":2227,"page":59},"Project Kanban","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban",[2228,2232,2236,2240,2244,2248],{"title":2229,"path":2230,"stem":2231},"Проєкт: Kanban Board (Trello Clone)","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fproject-overview","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F01.project-overview",{"title":2233,"path":2234,"stem":2235},"Налаштування та Типізація","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fsetup-and-types","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F02.setup-and-types",{"title":2237,"path":2238,"stem":2239},"Board Slice: Серце Дошки","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fboard-slice","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F03.board-slice",{"title":2241,"path":2242,"stem":2243},"Логіка Drag & Drop","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fdrag-and-drop-logic","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F04.drag-and-drop-logic",{"title":2245,"path":2246,"stem":2247},"Інтеграція з RTK Query","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Frtk-query-integration","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F05.rtk-query-integration",{"title":2249,"path":2250,"stem":2251},"Optimistic Updates","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Foptimistic-updates","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F06.optimistic-updates",{"title":2253,"icon":132,"path":2254,"stem":2255,"children":2256,"page":59},"Testing","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting","03.javascript\u002F04.react\u002F06.redux\u002F07.testing",[2257,2261,2265,2269,2273],{"title":2258,"path":2259,"stem":2260},"Тестування Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Fintro-testing","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F01.intro-testing",{"title":2262,"path":2263,"stem":2264},"Тестування Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-reducers","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F02.testing-reducers",{"title":2266,"path":2267,"stem":2268},"Тестування Селекторів","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-selectors","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F03.testing-selectors",{"title":2270,"path":2271,"stem":2272},"Тестування Компонентів (Integration)","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-components","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F04.testing-components",{"title":2274,"path":2275,"stem":2276},"Тестування Async Thunks","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-thunks","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F05.testing-thunks",{"title":2278,"icon":132,"path":2279,"stem":2280,"children":2281},"Ui Libraries","\u002Fjavascript\u002Freact\u002Fui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002Findex",[2282,2284,2288,2292,2296,2300,2304],{"title":2283,"path":2279,"stem":2280},"UI Бібліотеки в React",{"title":2285,"path":2286,"stem":2287},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fintroduction-to-ui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002F01.introduction-to-ui-libraries",{"title":2289,"path":2290,"stem":2291},"Філософія shadcn\u002Fui: \"Not a Component Library\"","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-philosophy","03.javascript\u002F04.react\u002F07.ui-libraries\u002F02.shadcn-philosophy",{"title":2293,"path":2294,"stem":2295},"Установка та Налаштування shadcn\u002Fui","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-installation","03.javascript\u002F04.react\u002F07.ui-libraries\u002F03.shadcn-installation",{"title":2297,"path":2298,"stem":2299},"Базові Компоненти shadcn\u002Fui: Фундамент Інтерфейсу","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-basics","03.javascript\u002F04.react\u002F07.ui-libraries\u002F04.shadcn-components-basics",{"title":2301,"path":2302,"stem":2303},"Компоненти Форм: Побудова Інтерактивних Form","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-forms","03.javascript\u002F04.react\u002F07.ui-libraries\u002F05.shadcn-components-forms",{"title":2305,"path":2306,"stem":2307},"Складні Компоненти: Dialog, Dropdown, Table та Command","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-advanced","03.javascript\u002F04.react\u002F07.ui-libraries\u002F06.shadcn-components-advanced",{"title":2309,"icon":2310,"path":2311,"stem":2312,"children":2313,"page":59},"TypeScript","i-devicon-typescript","\u002Fjavascript\u002Ftypescript","03.javascript\u002F05.typescript",[2314,2318,2322,2326,2330,2334,2338,2342],{"title":2315,"path":2316,"stem":2317},"TypeScript: Броня для вашого коду","\u002Fjavascript\u002Ftypescript\u002Fintro-and-basic-types","03.javascript\u002F05.typescript\u002F01.intro-and-basic-types",{"title":2319,"path":2320,"stem":2321},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","\u002Fjavascript\u002Ftypescript\u002Finterfaces-and-advanced-types","03.javascript\u002F05.typescript\u002F02.interfaces-and-advanced-types",{"title":2323,"path":2324,"stem":2325},"Алхімія Типів: Generics та Utility Types","\u002Fjavascript\u002Ftypescript\u002Fgenerics-and-utilities","03.javascript\u002F05.typescript\u002F03.generics-and-utilities",{"title":2327,"path":2328,"stem":2329},"Архітектура та Шаблони: Класи в TypeScript","\u002Fjavascript\u002Ftypescript\u002Fclasses-and-oop","03.javascript\u002F05.typescript\u002F04.classes-and-oop",{"title":2331,"path":2332,"stem":2333},"Продакшн та Екосистема: Advanced Config & Workflow","\u002Fjavascript\u002Ftypescript\u002Fadvanced-patterns-and-config","03.javascript\u002F05.typescript\u002F05.advanced-patterns-and-config",{"title":2335,"path":2336,"stem":2337},"TypeScript у світі React","\u002Fjavascript\u002Ftypescript\u002Freact-basics","03.javascript\u002F05.typescript\u002F06.react-basics",{"title":2339,"path":2340,"stem":2341},"React + TypeScript: Продвинуті патерни","\u002Fjavascript\u002Ftypescript\u002Freact-advanced","03.javascript\u002F05.typescript\u002F07.react-advanced",{"title":2343,"path":2344,"stem":2345},"React + TypeScript: Екосистема та бібліотеки","\u002Fjavascript\u002Ftypescript\u002Freact-ecosystem","03.javascript\u002F05.typescript\u002F08.react-ecosystem",{"title":2347,"path":2348,"stem":2349},"Atomic Design","\u002Fjavascript\u002Fatomic-design","03.javascript\u002F2.atomic-design",{"title":2351,"icon":2352,"path":2353,"stem":2354,"children":2355,"page":59},"Java","i-devicon-java","\u002Fjava","04.java",[2356,2359,2362,2366,2370,2374,2378],{"title":162,"path":2357,"stem":2358},"\u002Fjava\u002Fdata-mapper-part1","04.java\u002F01.data-mapper-part1",{"title":166,"path":2360,"stem":2361},"\u002Fjava\u002Fdata-mapper-part2","04.java\u002F02.data-mapper-part2",{"title":2363,"path":2364,"stem":2365},"Service Layer: Організація бізнес-логіки","\u002Fjava\u002Fservice-layer","04.java\u002F03.service-layer",{"title":2367,"path":2368,"stem":2369},"Rich Domain Model та State Pattern","\u002Fjava\u002Frich-domain-model","04.java\u002F04.rich-domain-model",{"title":2371,"path":2372,"stem":2373},"Патерни для складної бізнес-логіки","\u002Fjava\u002Fbusiness-logic-patterns","04.java\u002F05.business-logic-patterns",{"title":2375,"path":2376,"stem":2377},"Обробка помилок та валідація","\u002Fjava\u002Ferror-handling-validation","04.java\u002F06.error-handling-validation",{"title":2379,"path":2380,"stem":2381,"children":2382,"page":59},"Проектування баз даних","\u002Fjava\u002Fpr2","04.java\u002Fpr2",[2383,2387,2391,2395,2399,2403,2407,2411,2415,2419,2423,2427,2431,2435,2439,2443,2447,2451,2455,2459,2463,2467,2471,2475,2479,2483,2487,2491,2495,2499,2503,2507,2511,2515,2519,2523,2527],{"title":2384,"path":2385,"stem":2386},"Концептуальне моделювання: Мистецтво розуміння предметної області","\u002Fjava\u002Fpr2\u002Fconceptual-modeling","04.java\u002Fpr2\u002F01.conceptual-modeling",{"title":2388,"path":2389,"stem":2390},"Логічне моделювання: Від бізнес-ідей до структур даних","\u002Fjava\u002Fpr2\u002Flogical-modeling","04.java\u002Fpr2\u002F02.logical-modeling",{"title":2392,"path":2393,"stem":2394},"Нормалізація: Гігієна даних та боротьба з аномаліями","\u002Fjava\u002Fpr2\u002Fnormalization","04.java\u002Fpr2\u002F03.normalization",{"title":2396,"path":2397,"stem":2398},"Фізична схема: Від абстракції до DDL","\u002Fjava\u002Fpr2\u002Fphysical-schema","04.java\u002Fpr2\u002F04.physical-schema",{"title":2400,"path":2401,"stem":2402},"Архітектурна класифікація таблиць","\u002Fjava\u002Fpr2\u002Ftable-classification","04.java\u002Fpr2\u002F05.table-classification",{"title":2404,"path":2405,"stem":2406},"Database Migrations: Версіонування схеми з Flyway","\u002Fjava\u002Fpr2\u002Fdatabase-migrations","04.java\u002Fpr2\u002F06.database-migrations",{"title":2408,"path":2409,"stem":2410},"А що, якби це була не реляційна БД?","\u002Fjava\u002Fpr2\u002Fbeyond-relational","04.java\u002Fpr2\u002F07.beyond-relational",{"title":2412,"path":2413,"stem":2414},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","\u002Fjava\u002Fpr2\u002Fimpedance-mismatch","04.java\u002Fpr2\u002F09.impedance-mismatch",{"title":2416,"path":2417,"stem":2418},"JDBC: Перший контакт із базою даних","\u002Fjava\u002Fpr2\u002Fjdbc-fundamentals","04.java\u002Fpr2\u002F10.jdbc-fundamentals",{"title":2420,"path":2421,"stem":2422},"Якість коду: Spotless, SpotBugs та SonarQube","\u002Fjava\u002Fpr2\u002F10a.code-quality","04.java\u002Fpr2\u002F10a.code-quality",{"title":2424,"path":2425,"stem":2426},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","\u002Fjava\u002Fpr2\u002Fconnection-pool","04.java\u002Fpr2\u002F11.connection-pool",{"title":2428,"path":2429,"stem":2430},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","\u002Fjava\u002Fpr2\u002Frow-data-gateway","04.java\u002Fpr2\u002F12.row-data-gateway",{"title":2432,"path":2433,"stem":2434},"Table Data Gateway: Фасад таблиці як архітектурний відступ","\u002Fjava\u002Fpr2\u002Ftable-data-gateway","04.java\u002Fpr2\u002F13.table-data-gateway",{"title":2436,"path":2437,"stem":2438},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","\u002Fjava\u002Fpr2\u002Frepository-data-mapper","04.java\u002Fpr2\u002F14.repository-data-mapper",{"title":2440,"path":2441,"stem":2442},"Identity Map: Кешування сутностей у рамках сесії","\u002Fjava\u002Fpr2\u002Fidentity-map","04.java\u002Fpr2\u002F15.identity-map",{"title":2444,"path":2445,"stem":2446},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","\u002Fjava\u002Fpr2\u002Funit-of-work","04.java\u002Fpr2\u002F16.unit-of-work",{"title":2448,"path":2449,"stem":2450},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","\u002Fjava\u002Fpr2\u002Fstrategy-sql","04.java\u002Fpr2\u002F17.strategy-sql",{"title":2452,"path":2453,"stem":2454},"Proxy: Lazy Loading для One-To-Many колекцій","\u002Fjava\u002Fpr2\u002Fproxy-lazy-loading","04.java\u002Fpr2\u002F18.proxy-lazy-loading",{"title":2456,"path":2457,"stem":2458},"Generic Repository через Java Reflection: анотації та динамічний SQL","\u002Fjava\u002Fpr2\u002Fgeneric-repository-reflection","04.java\u002Fpr2\u002F19.generic-repository-reflection",{"title":2460,"path":2461,"stem":2462},"Specification Pattern: Композиція бізнес-правил для складних запитів","\u002Fjava\u002Fpr2\u002Fspecification-pattern","04.java\u002Fpr2\u002F20.specification-pattern",{"title":2464,"path":2465,"stem":2466},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","\u002Fjava\u002Fpr2\u002F20a.advanced-specifications","04.java\u002Fpr2\u002F20a.advanced-specifications",{"title":2468,"path":2469,"stem":2470},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","\u002Fjava\u002Fpr2\u002Fasynchronous-jdbc","04.java\u002Fpr2\u002F21.asynchronous-jdbc",{"title":2472,"path":2473,"stem":2474},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","\u002Fjava\u002Fpr2\u002Fintegration-testing-h2","04.java\u002Fpr2\u002F22.integration-testing-h2",{"title":2476,"path":2477,"stem":2478},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","\u002Fjava\u002Fpr2\u002Fintegration-testing-testcontainers","04.java\u002Fpr2\u002F23.integration-testing-testcontainers",{"title":2480,"path":2481,"stem":2482},"Google Guice: Впровадження залежностей у JavaFX-проєкті","\u002Fjava\u002Fpr2\u002Fdependency-injection-guice","04.java\u002Fpr2\u002F24.dependency-injection-guice",{"title":2484,"path":2485,"stem":2486},"JavaFX: Основи побудови графічних інтерфейсів","\u002Fjava\u002Fpr2\u002Fjavafx-fundamentals","04.java\u002Fpr2\u002F25.javafx-fundamentals",{"title":2488,"path":2489,"stem":2490},"Properties та Bindings: Реактивність у JavaFX","\u002Fjava\u002Fpr2\u002Fjavafx-properties-bindings","04.java\u002Fpr2\u002F26.javafx-properties-bindings",{"title":2492,"path":2493,"stem":2494},"MVC vs MVP vs MVVM: Еволюція архітектурних патернів UI","\u002Fjava\u002Fpr2\u002Fui-architecture-patterns","04.java\u002Fpr2\u002F27.ui-architecture-patterns",{"title":2496,"path":2497,"stem":2498},"MVVM на практиці: Побудова ViewModel","\u002Fjava\u002Fpr2\u002Fmvvm-viewmodel-implementation","04.java\u002Fpr2\u002F28.mvvm-viewmodel-implementation",{"title":2500,"path":2501,"stem":2502},"View та Controller: Зв'язування з ViewModel через FXML","\u002Fjava\u002Fpr2\u002Fmvvm-view-controller","04.java\u002Fpr2\u002F29.mvvm-view-controller",{"title":2504,"path":2505,"stem":2506},"Інтеграція MVVM з Guice: Автоматична ін'єкція залежностей","\u002Fjava\u002Fpr2\u002Fmvvm-guice-integration","04.java\u002Fpr2\u002F30.mvvm-guice-integration",{"title":2508,"path":2509,"stem":2510},"Валідація та обробка помилок у MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-validation-error-handling","04.java\u002Fpr2\u002F31.mvvm-validation-error-handling",{"title":2512,"path":2513,"stem":2514},"Навігація та управління екранами у JavaFX MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-navigation-screen-management","04.java\u002Fpr2\u002F32.mvvm-navigation-screen-management",{"title":2516,"path":2517,"stem":2518},"Тестування JavaFX MVVM-додатків","\u002Fjava\u002Fpr2\u002Fmvvm-testing","04.java\u002Fpr2\u002F33.mvvm-testing",{"title":2520,"path":2521,"stem":2522},"Стилізація та теми у JavaFX: CSS та User Experience","\u002Fjava\u002Fpr2\u002Fjavafx-styling-themes","04.java\u002Fpr2\u002F34.javafx-styling-themes",{"title":2524,"path":2525,"stem":2526},"AtlantaFX: Сучасні теми для JavaFX додатків","\u002Fjava\u002Fpr2\u002Fatlantafx-modern-themes","04.java\u002Fpr2\u002F35.atlantafx-modern-themes",{"title":2528,"path":2529,"stem":2530},"Пакування та розповсюдження JavaFX-додатків","\u002Fjava\u002Fpr2\u002Fjar-packaging-distribution","04.java\u002Fpr2\u002F36.jar-packaging-distribution",{"title":2532,"icon":2533,"path":2534,"stem":2535,"children":2536,"page":59},"Python","i-devicon-python","\u002Fpython","05.python",[2537,2541,2544,2548,2552,2556,2560,2564,2568],{"title":2538,"path":2539,"stem":2540},"Модулі, Пакети та Віртуальні Середовища","\u002Fpython\u002Fmodules-packages-venv","05.python\u002F00.modules-packages-venv",{"title":71,"path":2542,"stem":2543},"\u002Fpython\u002Fclasses-objects","05.python\u002F01.classes-objects",{"title":2545,"path":2546,"stem":2547},"Інкапсуляція, Керування Доступом та Властивості","\u002Fpython\u002Fencapsulation","05.python\u002F02.encapsulation",{"title":2549,"path":2550,"stem":2551},"Наслідування, MRO та суперсила super()","\u002Fpython\u002Finheritance-mro","05.python\u002F03.inheritance-mro",{"title":2553,"path":2554,"stem":2555},"Абстракція — ABC проти Статичних Протоколів (PEP 544)","\u002Fpython\u002Fabstraction-protocols","05.python\u002F04.abstraction-protocols",{"title":2557,"path":2558,"stem":2559},"Магічні методи (Dunder) та Емуляція протоколів","\u002Fpython\u002Fdunder-methods","05.python\u002F05.dunder-methods",{"title":2561,"path":2562,"stem":2563},"Декоратори та Керування життєвим циклом методів","\u002Fpython\u002Fdecorators-static-class","05.python\u002F06.decorators-static-class",{"title":2565,"path":2566,"stem":2567},"📦 Повний посібник з модулів, пакетів та віртуальних середовищ у Python","\u002Fpython\u002Flesson_9","05.python\u002Flesson_9",{"title":2569,"path":2570,"stem":2571},"[object Object]","\u002Fpython\u002Foop-plan","05.python\u002Foop-plan",{"title":2573,"icon":2574,"path":2575,"stem":2576,"children":2577,"page":59},"Бази даних","i-lucide-database","\u002Fdatabases","06.databases",[2578,2608,2631,2668,2697,2715,2749,2761,2770],{"title":2579,"icon":2580,"path":2581,"stem":2582,"children":2583,"page":59},"Intro","i-lucide-play","\u002Fdatabases\u002Fintro","06.databases\u002F01.intro",[2584,2588,2592,2596,2600,2604],{"title":2585,"path":2586,"stem":2587},"Введення в теорію баз даних","\u002Fdatabases\u002Fintro\u002Fintroduction-to-databases","06.databases\u002F01.intro\u002F01.introduction-to-databases",{"title":2589,"path":2590,"stem":2591},"Реляційна модель даних","\u002Fdatabases\u002Fintro\u002Frelational-model-theory","06.databases\u002F01.intro\u002F02.relational-model-theory",{"title":2593,"path":2594,"stem":2595},"ER-моделювання","\u002Fdatabases\u002Fintro\u002Fer-modeling","06.databases\u002F01.intro\u002F03.er-modeling",{"title":2597,"path":2598,"stem":2599},"Логічне проектування БД","\u002Fdatabases\u002Fintro\u002Flogical-schema","06.databases\u002F01.intro\u002F04.logical-schema",{"title":2601,"path":2602,"stem":2603},"Класифікація таблиць","\u002Fdatabases\u002Fintro\u002Ftable-classification","06.databases\u002F01.intro\u002F05.table-classification",{"title":2605,"path":2606,"stem":2607},"PlantUML для баз даних","\u002Fdatabases\u002Fintro\u002Fplantuml-diagrams","06.databases\u002F01.intro\u002F06.plantuml-diagrams",{"title":2609,"icon":2574,"path":2610,"stem":2611,"children":2612,"page":59},"MS SQL Server Start","\u002Fdatabases\u002Fms-sql-server-start","06.databases\u002F02.ms-sql-server-start",[2613,2617,2623,2627],{"title":2614,"path":2615,"stem":2616},"Типи даних у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fdata-types","06.databases\u002F02.ms-sql-server-start\u002F01.data-types",{"title":2618,"path":2619,"stem":2620,"children":2621},"Індекси у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-indexes","06.databases\u002F02.ms-sql-server-start\u002F02.sql-indexes",[2622],{"title":2618,"path":2619,"stem":2620},{"title":2624,"path":2625,"stem":2626},"Системні бази даних MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsystem-databases","06.databases\u002F02.ms-sql-server-start\u002F03.system-databases",{"title":2628,"path":2629,"stem":2630},"Огляд мови SQL та запитів","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-queries-overview","06.databases\u002F02.ms-sql-server-start\u002F04.sql-queries-overview",{"title":2632,"icon":2574,"path":2633,"stem":2634,"children":2635,"page":59},"SQL","\u002Fdatabases\u002Fsql","06.databases\u002F03.sql",[2636,2640,2644,2648,2652,2656,2660,2664],{"title":2637,"path":2638,"stem":2639},"Налаштування демонстраційної бази даних","\u002Fdatabases\u002Fsql\u002Fsample-database-setup","06.databases\u002F03.sql\u002F00.sample-database-setup",{"title":2641,"path":2642,"stem":2643},"DDL - Створення таблиць (CREATE TABLE)","\u002Fdatabases\u002Fsql\u002Fddl-create-table","06.databases\u002F03.sql\u002F01.ddl-create-table",{"title":2645,"path":2646,"stem":2647},"DDL - Зміна та видалення таблиць (ALTER, DROP)","\u002Fdatabases\u002Fsql\u002Fddl-alter-drop-table","06.databases\u002F03.sql\u002F02.ddl-alter-drop-table",{"title":2649,"path":2650,"stem":2651},"SELECT запити - Основи","\u002Fdatabases\u002Fsql\u002Fselect-queries-fundamentals","06.databases\u002F03.sql\u002F03.select-queries-fundamentals",{"title":2653,"path":2654,"stem":2655},"SELECT запити - Розширені можливості","\u002Fdatabases\u002Fsql\u002Fselect-queries-advanced","06.databases\u002F03.sql\u002F04.select-queries-advanced",{"title":2657,"path":2658,"stem":2659},"INSERT запити - Додавання даних","\u002Fdatabases\u002Fsql\u002Finsert-queries","06.databases\u002F03.sql\u002F05.insert-queries",{"title":2661,"path":2662,"stem":2663},"UPDATE та DELETE запити","\u002Fdatabases\u002Fsql\u002Fupdate-delete-queries","06.databases\u002F03.sql\u002F06.update-delete-queries",{"title":2665,"path":2666,"stem":2667},"Транзакції в SQL","\u002Fdatabases\u002Fsql\u002Ftransactions","06.databases\u002F03.sql\u002F07.transactions",{"title":2669,"icon":2574,"path":2670,"stem":2671,"children":2672,"page":59},"Multi Table Databases","\u002Fdatabases\u002Fmulti-table-databases","06.databases\u002F04.multi-table-databases",[2673,2677,2681,2685,2689,2693],{"title":2674,"path":2675,"stem":2676},"Зв'язки та нормалізація БД","\u002Fdatabases\u002Fmulti-table-databases\u002Frelationships-and-normalization","06.databases\u002F04.multi-table-databases\u002F00.relationships-and-normalization",{"title":2678,"path":2679,"stem":2680},"INNER JOIN - З'єднання таблиць","\u002Fdatabases\u002Fmulti-table-databases\u002Finner-join","06.databases\u002F04.multi-table-databases\u002F01.inner-join",{"title":2682,"path":2683,"stem":2684},"OUTER JOINs - LEFT, RIGHT, FULL","\u002Fdatabases\u002Fmulti-table-databases\u002Fouter-joins","06.databases\u002F04.multi-table-databases\u002F02.outer-joins",{"title":2686,"path":2687,"stem":2688},"CROSS та SELF JOINs","\u002Fdatabases\u002Fmulti-table-databases\u002Fcross-self-joins","06.databases\u002F04.multi-table-databases\u002F03.cross-self-joins",{"title":2690,"path":2691,"stem":2692},"Підзапити (Subqueries)","\u002Fdatabases\u002Fmulti-table-databases\u002Fsubqueries","06.databases\u002F04.multi-table-databases\u002F04.subqueries",{"title":2694,"path":2695,"stem":2696},"Агрегації з JOIN","\u002Fdatabases\u002Fmulti-table-databases\u002Faggregations-with-joins","06.databases\u002F04.multi-table-databases\u002F05.aggregations-with-joins",{"title":2698,"icon":2699,"path":2700,"stem":2701,"children":2702,"page":59},"Aggregate Functions","i-lucide-calculator","\u002Fdatabases\u002Faggregate-functions","06.databases\u002F05.aggregate-functions",[2703,2707,2711],{"title":2704,"path":2705,"stem":2706},"Функції агрегування в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fintroduction-aggregate-functions","06.databases\u002F05.aggregate-functions\u002F01.introduction-aggregate-functions",{"title":2708,"path":2709,"stem":2710},"Групування даних в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fgrouping-data","06.databases\u002F05.aggregate-functions\u002F02.grouping-data",{"title":2712,"path":2713,"stem":2714},"Підзапити з агрегатними функціями","\u002Fdatabases\u002Faggregate-functions\u002Fsubqueries-aggregates","06.databases\u002F05.aggregate-functions\u002F03.subqueries-aggregates",{"title":2716,"icon":2717,"path":2718,"stem":2719,"children":2720,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","\u002Fdatabases\u002Ftriggers-stored-procedures","06.databases\u002F07.triggers-stored-procedures",[2721,2725,2729,2733,2737,2741,2745],{"title":2722,"path":2723,"stem":2724},"DML-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fdml-triggers","06.databases\u002F07.triggers-stored-procedures\u002F01.dml-triggers",{"title":2726,"path":2727,"stem":2728},"DDL-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fddl-triggers","06.databases\u002F07.triggers-stored-procedures\u002F02.ddl-triggers",{"title":2730,"path":2731,"stem":2732},"Transact-SQL розширення","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransact-sql-extensions","06.databases\u002F07.triggers-stored-procedures\u002F03.transact-sql-extensions",{"title":2734,"path":2735,"stem":2736},"Транзакції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransactions","06.databases\u002F07.triggers-stored-procedures\u002F04.transactions",{"title":2738,"path":2739,"stem":2740},"Зберігаємі процедури","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fstored-procedures","06.databases\u002F07.triggers-stored-procedures\u002F05.stored-procedures",{"title":2742,"path":2743,"stem":2744},"Користувацькі функції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fuser-defined-functions","06.databases\u002F07.triggers-stored-procedures\u002F06.user-defined-functions",{"title":2746,"path":2747,"stem":2748},"Безпека баз даних","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fsecurity","06.databases\u002F07.triggers-stored-procedures\u002F08.security",{"title":2746,"icon":793,"path":2750,"stem":2751,"children":2752,"page":59},"\u002Fdatabases\u002Fsecurity","06.databases\u002F08.security",[2753,2757],{"title":2754,"path":2755,"stem":2756},"Вступ до безпеки баз даних","\u002Fdatabases\u002Fsecurity\u002Fintroduction","06.databases\u002F08.security\u002F01.introduction",{"title":2758,"path":2759,"stem":2760},"Системні представлення та метадані","\u002Fdatabases\u002Fsecurity\u002Fsystem-views","06.databases\u002F08.security\u002F02.system-views",{"title":2762,"icon":2763,"path":2764,"stem":2765,"children":2766,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","\u002Fdatabases\u002Fbackup-recovery","06.databases\u002F09.backup-recovery",[2767],{"title":2762,"path":2768,"stem":2769},"\u002Fdatabases\u002Fbackup-recovery\u002Fbackup-restore","06.databases\u002F09.backup-recovery\u002F01.backup-restore",{"title":2771,"icon":2772,"path":2773,"stem":2774,"children":2775,"page":59},"Повнотекстовий пошук","i-lucide-search","\u002Fdatabases\u002Ffull-text-search","06.databases\u002F10.full-text-search",[2776],{"title":2771,"path":2777,"stem":2778},"\u002Fdatabases\u002Ffull-text-search\u002Ffull-text-search","06.databases\u002F10.full-text-search\u002F01.full-text-search",{"title":2780,"icon":2781,"path":2782,"stem":2783,"children":2784,"page":59},"Tools","i-lucide-wrench","\u002Ftools","07.tools",[2785,2861],{"title":2786,"icon":2787,"path":2788,"stem":2789,"children":2790},"Docker","i-simple-icons-docker","\u002Ftools\u002Fdocker","07.tools\u002F01.docker\u002Findex",[2791,2793,2797,2801,2805,2809,2813,2817,2821,2825,2829,2833,2837,2841,2845,2849,2853,2857],{"title":2792,"path":2788,"stem":2789},"Docker: від нуля до production",{"title":2794,"path":2795,"stem":2796},"Контейнеризація — від проблеми до рішення","\u002Ftools\u002Fdocker\u002Fcontainerization-concept","07.tools\u002F01.docker\u002F01.containerization-concept",{"title":2798,"path":2799,"stem":2800},"Docker — що це і навіщо?","\u002Ftools\u002Fdocker\u002Fdocker-what-and-why","07.tools\u002F01.docker\u002F02.docker-what-and-why",{"title":2802,"path":2803,"stem":2804},"Архітектура Docker Engine","\u002Ftools\u002Fdocker\u002Fdocker-architecture","07.tools\u002F01.docker\u002F03.docker-architecture",{"title":2806,"path":2807,"stem":2808},"Встановлення Docker","\u002Ftools\u002Fdocker\u002Finstallation","07.tools\u002F01.docker\u002F04.installation",{"title":2810,"path":2811,"stem":2812},"Перший контейнер — docker run","\u002Ftools\u002Fdocker\u002Ffirst-container","07.tools\u002F01.docker\u002F05.first-container",{"title":2814,"path":2815,"stem":2816},"Життєвий цикл контейнера","\u002Ftools\u002Fdocker\u002Fcontainer-lifecycle","07.tools\u002F01.docker\u002F06.container-lifecycle",{"title":2818,"path":2819,"stem":2820},"Docker Images — фундаментальні концепції","\u002Ftools\u002Fdocker\u002Fdocker-images-fundamentals","07.tools\u002F01.docker\u002F07.docker-images-fundamentals",{"title":2822,"path":2823,"stem":2824},"Dockerfile — основи","\u002Ftools\u002Fdocker\u002Fdockerfile-basics","07.tools\u002F01.docker\u002F08.dockerfile-basics",{"title":2826,"path":2827,"stem":2828},"Dockerfile — просунуті техніки","\u002Ftools\u002Fdocker\u002Fdockerfile-advanced","07.tools\u002F01.docker\u002F09.dockerfile-advanced",{"title":2830,"path":2831,"stem":2832},"Build Context та кешування шарів","\u002Ftools\u002Fdocker\u002Fbuild-context-and-cache","07.tools\u002F01.docker\u002F10.build-context-and-cache",{"title":2834,"path":2835,"stem":2836},"Реєстри Docker-образів","\u002Ftools\u002Fdocker\u002Fimage-registries","07.tools\u002F01.docker\u002F11.image-registries",{"title":2838,"path":2839,"stem":2840},"Контейнеризація .NET додатків","\u002Ftools\u002Fdocker\u002Fdotnet-containerization","07.tools\u002F01.docker\u002F12.dotnet-containerization",{"title":2842,"path":2843,"stem":2844},"Томи та збереження даних","\u002Ftools\u002Fdocker\u002Fvolumes-and-data","07.tools\u002F01.docker\u002F13.volumes-and-data",{"title":2846,"path":2847,"stem":2848},"Основи мережі в Docker","\u002Ftools\u002Fdocker\u002Fnetworking-basics","07.tools\u002F01.docker\u002F14.networking-basics",{"title":2850,"path":2851,"stem":2852},"Змінні оточення та конфігурація","\u002Ftools\u002Fdocker\u002Fenvironment-and-configuration","07.tools\u002F01.docker\u002F15.environment-and-configuration",{"title":2854,"path":2855,"stem":2856},"Docker Compose — оркестрація контейнерів","\u002Ftools\u002Fdocker\u002Fdocker-compose-basics","07.tools\u002F01.docker\u002F16.docker-compose-basics",{"title":2858,"path":2859,"stem":2860},"Docker Compose — Multi-Service застосунки","\u002Ftools\u002Fdocker\u002Fcompose-multi-service","07.tools\u002F01.docker\u002F17.compose-multi-service",{"title":2862,"icon":2863,"path":2864,"stem":2865,"children":2866},"Kubernetes","simple-icons:kubernetes","\u002Ftools\u002Fkubernetes","07.tools\u002F02.kubernetes\u002Findex",[2867,2869,2873,2877,2881,2885,2889,2893,2897],{"title":2868,"path":2864,"stem":2865},"Kubernetes: від розробки до production",{"title":2870,"path":2871,"stem":2872},"Kubernetes — коли Docker Compose більше не вистачає","\u002Ftools\u002Fkubernetes\u002Fwhy-kubernetes","07.tools\u002F02.kubernetes\u002F01.why-kubernetes",{"title":2874,"path":2875,"stem":2876},"Архітектура Kubernetes — анатомія кластера","\u002Ftools\u002Fkubernetes\u002Fkubernetes-architecture","07.tools\u002F02.kubernetes\u002F02.kubernetes-architecture",{"title":2878,"path":2879,"stem":2880},"Локальне середовище — minikube, kind та k3s","\u002Ftools\u002Fkubernetes\u002Flocal-environment","07.tools\u002F02.kubernetes\u002F03.local-environment",{"title":2882,"path":2883,"stem":2884},"Pod — атомарна одиниця Kubernetes","\u002Ftools\u002Fkubernetes\u002Fpods-and-containers","07.tools\u002F02.kubernetes\u002F04.pods-and-containers",{"title":2886,"path":2887,"stem":2888},"Патерни використання Pod","\u002Ftools\u002Fkubernetes\u002Fpod-patterns","07.tools\u002F02.kubernetes\u002F05.pod-patterns",{"title":2890,"path":2891,"stem":2892},"Deployment — декларативне управління Pod","\u002Ftools\u002Fkubernetes\u002Fdeployment-basics","07.tools\u002F02.kubernetes\u002F06.deployment-basics",{"title":2894,"path":2895,"stem":2896},"Rolling Updates та управління життєвим циклом Deployment","\u002Ftools\u002Fkubernetes\u002Fdeployment-rolling-updates","07.tools\u002F02.kubernetes\u002F07.deployment-rolling-updates",{"title":2898,"path":2899,"stem":2900},"Service — мережева абстракція для Pod","\u002Ftools\u002Fkubernetes\u002Fservices-networking","07.tools\u002F02.kubernetes\u002F08.services-networking",{"title":2902,"icon":2903,"path":2904,"stem":2905,"children":2906,"page":59},"Software Engineering","i-lucide-code-2","\u002Fsoftware-engineering","09.software-engineering",[2907,2911,2915,2919,2923,2927,2931,2935,2939,2943,2947],{"title":2908,"path":2909,"stem":2910},"1. Аналіз предметної області. Експертні знання та складність","\u002Fsoftware-engineering\u002Fintro-subdomains","09.software-engineering\u002F01.intro-subdomains",{"title":2912,"path":2913,"stem":2914},"2. Обмежені контексти. Інтеграція обмежених контекстів","\u002Fsoftware-engineering\u002Fintegrating-limited-contexts","09.software-engineering\u002F02.integrating-limited-contexts",{"title":2916,"path":2917,"stem":2918},"3. Реалізація простої бізнес-логіки","\u002Fsoftware-engineering\u002Fsimple","09.software-engineering\u002F03.simple",{"title":2920,"path":2921,"stem":2922},"4. Опрацювання складної бізнес-логіки","\u002Fsoftware-engineering\u002Fcomplex-business-logic","09.software-engineering\u002F04.complex-business-logic",{"title":2924,"path":2925,"stem":2926},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","\u002Fsoftware-engineering\u002Fmodelling-the-time-factor","09.software-engineering\u002F05.modelling-the-time-factor",{"title":2928,"path":2929,"stem":2930},"6. Архітектурні патерни","\u002Fsoftware-engineering\u002Farchitectural-patterns","09.software-engineering\u002F06.architectural-patterns",{"title":2932,"path":2933,"stem":2934},"Паттерни взаємодії","\u002Fsoftware-engineering\u002Fpatterns-of-interaction","09.software-engineering\u002F07.patterns-of-interaction",{"title":2936,"path":2937,"stem":2938},"Евристика проєктування","\u002Fsoftware-engineering\u002Fdesign-heuristics","09.software-engineering\u002F08.design-heuristics",{"title":2940,"path":2941,"stem":2942},"Еволюція проєктних рішень","\u002Fsoftware-engineering\u002Fevolution-of-design-solutions","09.software-engineering\u002F09.evolution-of-design-solutions",{"title":2944,"path":2945,"stem":2946},"EventStorming","\u002Fsoftware-engineering\u002Feventstorming","09.software-engineering\u002F10.eventstorming",{"title":2948,"path":2949,"stem":2950},"DDD на практиці","\u002Fsoftware-engineering\u002Fddd-in-practice","09.software-engineering\u002F11.ddd-in-practice",{"title":2952,"icon":943,"path":2953,"stem":2954,"children":2955,"page":59},"DDD","\u002Fddd","10.ddd",[2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004],{"title":2957,"path":2958,"stem":2959},"Аналіз предметної області","\u002Fddd\u002Fdomain-analysis","10.ddd\u002F01.domain-analysis",{"title":2961,"path":2962,"stem":2963},"Експертні знання про предметну область","\u002Fddd\u002Fdomain-expert-knowledge","10.ddd\u002F02.domain-expert-knowledge",{"title":2965,"path":2966,"stem":2967},"Як осмислити складність предметної області","\u002Fddd\u002Fmanaging-domain-complexity","10.ddd\u002F03.managing-domain-complexity",{"title":2969,"path":2970,"stem":2971},"Інтеграція обмежених контекстів","\u002Fddd\u002Fbounded-context-integration","10.ddd\u002F04.bounded-context-integration",{"title":2973,"path":2974,"stem":2975},"Реалізація простої бізнес-логіки","\u002Fddd\u002Fsimple-business-logic","10.ddd\u002F05.simple-business-logic",{"title":2977,"path":2978,"stem":2979},"Обробка складної бізнес-логіки","\u002Fddd\u002Fcomplex-business-logic","10.ddd\u002F06.complex-business-logic",{"title":2981,"path":2982,"stem":2983},"Моделювання фактора часу","\u002Fddd\u002Ftime-modeling","10.ddd\u002F07.time-modeling",{"title":2985,"path":2986,"stem":2987},"Глава 8. Архітектурні Патерни","\u002Fddd\u002Farchitectural-patterns","10.ddd\u002F08.architectural-patterns",{"title":2989,"path":2990,"stem":2991},"Глава 9. Патерни Взаємодії","\u002Fddd\u002Finteraction-patterns","10.ddd\u002F09.interaction-patterns",{"title":2993,"path":2994,"stem":2995},"Глава 10. Проектні Евристики","\u002Fddd\u002Fdesign-heuristics","10.ddd\u002F10.design-heuristics",{"title":2997,"path":2998,"stem":2999},"Глава 11. Еволюція Проектних Рішень","\u002Fddd\u002Fevolution-of-design-decisions","10.ddd\u002F11.evolution-of-design-decisions",{"title":3001,"path":3002,"stem":3003},"Глава 12. EventStorming","\u002Fddd\u002Fevent-storming","10.ddd\u002F12.event-storming",{"title":3005,"path":3006,"stem":3007},"Глава 13. DDD на Практиці","\u002Fddd\u002Fddd-in-practice","10.ddd\u002F13.ddd-in-practice",{"title":3009,"icon":3010,"path":3011,"stem":3012,"children":3013,"page":59},"Media Streaming","i-lucide-video","\u002Fmedia-streaming","11.media-streaming",[3014,3018,3022,3026,3030,3034,3038],{"title":3015,"path":3016,"stem":3017},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","\u002Fmedia-streaming\u002Fintroduction","11.media-streaming\u002F01.introduction",{"title":3019,"path":3020,"stem":3021},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","\u002Fmedia-streaming\u002Faudio-video-anatomy","11.media-streaming\u002F02.audio-video-anatomy",{"title":3023,"path":3024,"stem":3025},"03. The Gym: FFmpeg Deep Dive","\u002Fmedia-streaming\u002Fffmpeg-gym","11.media-streaming\u002F03.ffmpeg-gym",{"title":3027,"path":3028,"stem":3029},"04. HLS Protocol: HTTP Live Streaming у Деталях","\u002Fmedia-streaming\u002Fhls-protocol","11.media-streaming\u002F04.hls-protocol",{"title":3031,"path":3032,"stem":3033},"05. DASH Protocol: Відкритий Стандарт","\u002Fmedia-streaming\u002Fdash-protocol","11.media-streaming\u002F05.dash-protocol",{"title":3035,"path":3036,"stem":3037},"06. Масштабування: CDN та Adaptive Bitrate","\u002Fmedia-streaming\u002Fcdn-and-adaptive-bitrate","11.media-streaming\u002F06.cdn-and-adaptive-bitrate",{"title":3039,"path":3040,"stem":3041},"07. Війна із Затримкою (Latency)","\u002Fmedia-streaming\u002Frealtime-latency","11.media-streaming\u002F07.realtime-latency",{"title":3043,"icon":3044,"path":3045,"stem":3046,"children":3047,"page":59},"HTML & CSS","i-devicon-html5","\u002Fhtml-css","12.html-css",[3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164],{"title":3049,"path":3050,"stem":3051},"Вступ до HTML. Структура документа","\u002Fhtml-css\u002Fintro-html-structure","12.html-css\u002F01.intro-html-structure",{"title":3053,"path":3054,"stem":3055},"Форматування тексту в HTML","\u002Fhtml-css\u002Fhtml-text-formatting","12.html-css\u002F02.html-text-formatting",{"title":3057,"path":3058,"stem":3059},"Посилання та зображення в HTML","\u002Fhtml-css\u002Fhtml-links-images","12.html-css\u002F03.html-links-images",{"title":3061,"path":3062,"stem":3063},"Списки та таблиці в HTML","\u002Fhtml-css\u002Fhtml-lists-tables","12.html-css\u002F04.html-lists-tables",{"title":3065,"path":3066,"stem":3067},"Форми в HTML","\u002Fhtml-css\u002Fhtml-forms","12.html-css\u002F05.html-forms",{"title":3069,"path":3070,"stem":3071},"Семантичні елементи HTML5","\u002Fhtml-css\u002Fhtml-semantic-elements","12.html-css\u002F06.html-semantic-elements",{"title":3073,"path":3074,"stem":3075},"Мультимедіа та розширені елементи HTML","\u002Fhtml-css\u002Fhtml-multimedia-advanced","12.html-css\u002F07.html-multimedia-advanced",{"title":3077,"path":3078,"stem":3079},"Мікророзмітка та SEO в HTML","\u002Fhtml-css\u002Fhtml-microdata-seo","12.html-css\u002F08.html-microdata-seo",{"title":3081,"path":3082,"stem":3083},"Вступ до CSS. Селектори та специфічність","\u002Fhtml-css\u002Fcss-intro-selectors","12.html-css\u002F09.css-intro-selectors",{"title":3085,"path":3086,"stem":3087},"Блокова модель CSS. Відступи. Box Sizing","\u002Fhtml-css\u002Fcss-box-model","12.html-css\u002F10.css-box-model",{"title":3089,"path":3090,"stem":3091},"Розміри у CSS: повний довідник одиниць і ключових слів","\u002Fhtml-css\u002F10a.css-sizing","12.html-css\u002F10a.css-sizing",{"title":3093,"path":3094,"stem":3095},"Типографіка в CSS. Шрифти та текст","\u002Fhtml-css\u002Fcss-typography","12.html-css\u002F11.css-typography",{"title":3097,"path":3098,"stem":3099},"Кольори та фони в CSS","\u002Fhtml-css\u002Fcss-colors-backgrounds","12.html-css\u002F12.css-colors-backgrounds",{"title":3101,"path":3102,"stem":3103},"Тіні та фільтри в CSS","\u002Fhtml-css\u002F12b.css-shadows-filters","12.html-css\u002F12b.css-shadows-filters",{"title":3105,"path":3106,"stem":3107},"CSS Flexbox: Фундамент гнучких макетів","\u002Fhtml-css\u002Fcss-flexbox-fundamentals","12.html-css\u002F13.css-flexbox-fundamentals",{"title":3109,"path":3110,"stem":3111},"CSS Flexbox: Вирівнювання та Позиціонування","\u002Fhtml-css\u002Fcss-flexbox-alignment-sizing-and-patterns","12.html-css\u002F14.css-flexbox-alignment-sizing-and-patterns",{"title":3113,"path":3114,"stem":3115},"CSS Grid. Двовимірний макет. Частина 1","\u002Fhtml-css\u002Fcss-layout-grid","12.html-css\u002F15.css-layout-grid",{"title":3117,"path":3118,"stem":3119},"CSS Grid. Двовимірний макет. Частина 2","\u002Fhtml-css\u002Fcss-layout-grid-advanced","12.html-css\u002F16.css-layout-grid-advanced",{"title":3121,"path":3122,"stem":3123},"Позиціонування в CSS. Z-index. Stacking Context","\u002Fhtml-css\u002Fcss-positioning","12.html-css\u002F17.css-positioning",{"title":3125,"path":3126,"stem":3127},"CSS Анімації та Переходи","\u002Fhtml-css\u002Fcss-animations-transitions","12.html-css\u002F18.css-animations-transitions",{"title":3129,"path":3130,"stem":3131},"Адаптивний дизайн. Media Queries. Частина 1","\u002Fhtml-css\u002Fcss-responsive-media-queries","12.html-css\u002F19.css-responsive-media-queries",{"title":3133,"path":3134,"stem":3135},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","\u002Fhtml-css\u002Fcss-responsive-advanced","12.html-css\u002F20.css-responsive-advanced",{"title":3137,"path":3138,"stem":3139},"CSS Custom Properties. Методології. Сучасний CSS","\u002Fhtml-css\u002Fcss-variables-methodologies","12.html-css\u002F21.css-variables-methodologies",{"title":3141,"path":3142,"stem":3143},"Сучасний CSS 2023–2025: Нові можливості","\u002Fhtml-css\u002Fcss-modern-features","12.html-css\u002F22.css-modern-features",{"title":3145,"path":3146,"stem":3147},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","\u002Fhtml-css\u002F22a.css-nesting-modern-syntax","12.html-css\u002F22a.css-nesting-modern-syntax",{"title":3149,"path":3150,"stem":3151},"CSS для форм та інтерактивних станів","\u002Fhtml-css\u002Fcss-forms-interactive-states","12.html-css\u002F23.css-forms-interactive-states",{"title":3153,"path":3154,"stem":3155},"Доступність у CSS (CSS Accessibility)","\u002Fhtml-css\u002Fcss-accessibility","12.html-css\u002F24.css-accessibility",{"title":3157,"path":3158,"stem":3159},"CSS-функції та сучасні sizing primitives","\u002Fhtml-css\u002Fcss-functions-sizing","12.html-css\u002F25.css-functions-sizing",{"title":3161,"path":3162,"stem":3163},"Rendering Pipeline і CSS Performance","\u002Fhtml-css\u002Fcss-rendering-performance","12.html-css\u002F26.css-rendering-performance",{"title":3165,"path":3166,"stem":3167},"CSS Best Practices: типові ситуації та правильні рішення","\u002Fhtml-css\u002Fcss-best-practices","12.html-css\u002F27.css-best-practices",{"title":3169,"path":3170,"stem":3171,"children":3172,"page":59},"AWS","\u002Faws","13.aws",[3173,3177,3181,3185,3189,3193,3197,3201,3205,3209,3213,3217,3221,3225,3229,3233,3237,3241],{"title":3174,"path":3175,"stem":3176},"Реєстрація AWS акаунту та студентські програми","\u002Faws\u002Faccount-registration","13.aws\u002F00.account-registration",{"title":3178,"path":3179,"stem":3180},"Вступ до хмарних обчислень та AWS","\u002Faws\u002Fintroduction-to-cloud","13.aws\u002F01.introduction-to-cloud",{"title":3182,"path":3183,"stem":3184},"AWS IAM — Identity and Access Management","\u002Faws\u002Fiam","13.aws\u002F02.iam",{"title":3186,"path":3187,"stem":3188},"AWS IAM CLI — Довідник команд","\u002Faws\u002F02a.iam-doc","13.aws\u002F02a.iam-doc",{"title":3190,"path":3191,"stem":3192},"Docker та контейнеризація в AWS — ECR, ECS та Fargate","\u002Faws\u002Fdocker-ecs","13.aws\u002F03.docker-ecs",{"title":3194,"path":3195,"stem":3196},"AWS ECR \u002F ECS CLI — Довідник команд","\u002Faws\u002F03a.docker-ecs-doc","13.aws\u002F03a.docker-ecs-doc",{"title":3198,"path":3199,"stem":3200},"Amazon EC2 — Elastic Compute Cloud","\u002Faws\u002Fec2","13.aws\u002F04.ec2",{"title":3202,"path":3203,"stem":3204},"AWS EC2 CLI — Довідник команд","\u002Faws\u002F04a.ec2-doc","13.aws\u002F04a.ec2-doc",{"title":3206,"path":3207,"stem":3208},"Elastic Load Balancing та Auto Scaling","\u002Faws\u002Falb-asg","13.aws\u002F05.alb-asg",{"title":3210,"path":3211,"stem":3212},"Amazon S3 — Simple Storage Service","\u002Faws\u002Fs3","13.aws\u002F06.s3",{"title":3214,"path":3215,"stem":3216},"Amazon CloudFront — Content Delivery Network","\u002Faws\u002Fcloudfront","13.aws\u002F07.cloudfront",{"title":3218,"path":3219,"stem":3220},"Amazon RDS — Relational Database Service","\u002Faws\u002Frds","13.aws\u002F08.rds",{"title":3222,"path":3223,"stem":3224},"Amazon DynamoDB — NoSQL Database","\u002Faws\u002Fdynamodb","13.aws\u002F09.dynamodb",{"title":3226,"path":3227,"stem":3228},"AWS Lambda та Serverless Compute","\u002Faws\u002Flambda","13.aws\u002F10.lambda",{"title":3230,"path":3231,"stem":3232},"Amazon Bedrock - Foundation Models, RAG та Agents","\u002Faws\u002Fbedrock","13.aws\u002F22.bedrock",{"title":3234,"path":3235,"stem":3236},"Amazon Rekognition - Комп'ютерний зір","\u002Faws\u002Frekognition","13.aws\u002F23.rekognition",{"title":3238,"path":3239,"stem":3240},"Amazon Textract - Інтелектуальний аналіз документів","\u002Faws\u002Ftextract","13.aws\u002F24.textract",{"title":3242,"path":3243,"stem":3244},"Amazon Polly, Transcribe, Comprehend та Translate","\u002Faws\u002Faudio-nlp-services","13.aws\u002F25.audio-nlp-services",{"title":3246,"path":3247,"stem":3248,"children":3249,"page":59},"Tailwind","\u002Ftailwind","21.tailwind",[3250,3254,3258,3262,3266,3270,3274,3278,3282,3286,3290,3294],{"title":3251,"path":3252,"stem":3253},"Що таке Tailwind CSS і навіщо він потрібен","\u002Ftailwind\u002Ftailwind-intro-philosophy","21.tailwind\u002F01.tailwind-intro-philosophy",{"title":3255,"path":3256,"stem":3257},"Встановлення та налаштування Tailwind CSS v4","\u002Ftailwind\u002Ftailwind-installation-setup","21.tailwind\u002F02.tailwind-installation-setup",{"title":3259,"path":3260,"stem":3261},"Utility-класи: основи та система Tailwind","\u002Ftailwind\u002Ftailwind-utility-classes-core","21.tailwind\u002F03.tailwind-utility-classes-core",{"title":3263,"path":3264,"stem":3265},"Layout: Flexbox та Grid через Tailwind","\u002Ftailwind\u002Ftailwind-flexbox-grid","21.tailwind\u002F04.tailwind-flexbox-grid",{"title":3267,"path":3268,"stem":3269},"Кастомізація теми через @theme у Tailwind v4","\u002Ftailwind\u002Ftailwind-theme-customization","21.tailwind\u002F05.tailwind-theme-customization",{"title":3271,"path":3272,"stem":3273},"Варіанти: hover, focus, responsive, dark mode та нові v4","\u002Ftailwind\u002Ftailwind-variants-states","21.tailwind\u002F06.tailwind-variants-states",{"title":3275,"path":3276,"stem":3277},"Типографіка та система кольорів у Tailwind v4","\u002Ftailwind\u002Ftailwind-typography-colors","21.tailwind\u002F07.tailwind-typography-colors",{"title":3279,"path":3280,"stem":3281},"Компоненти та повторюваність: @apply, @utility та патерни","\u002Ftailwind\u002Ftailwind-components-patterns","21.tailwind\u002F08.tailwind-components-patterns",{"title":3283,"path":3284,"stem":3285},"Темна тема та система дизайн-токенів у Tailwind v4","\u002Ftailwind\u002Ftailwind-dark-mode-theming","21.tailwind\u002F09.tailwind-dark-mode-theming",{"title":3287,"path":3288,"stem":3289},"Довільні значення та контейнерні запити у Tailwind v4","\u002Ftailwind\u002Ftailwind-arbitrary-container-queries","21.tailwind\u002F10.tailwind-arbitrary-container-queries",{"title":3291,"path":3292,"stem":3293},"Анімації, трансформації та 3D у Tailwind v4","\u002Ftailwind\u002Ftailwind-animations-transforms","21.tailwind\u002F11.tailwind-animations-transforms",{"title":3295,"path":3296,"stem":3297},"Tailwind CLI, PostCSS та інтеграція з фреймворками","\u002Ftailwind\u002Ftailwind-cli-tooling","21.tailwind\u002F12.tailwind-cli-tooling",{"title":3299,"path":3300,"stem":3301},"Тестування компонентів діаграм","\u002Ftest-components","98.test-components",{"id":3303,"title":1696,"body":3304,"description":20795,"extension":20796,"links":20797,"meta":20798,"navigation":3441,"path":1697,"seo":20799,"stem":1698,"__hash__":20800},"docs\u002F01.csharp\u002F13.network-programming\u002F10.websockets.md",{"type":3305,"value":3306,"toc":20751},"minimark",[3307,3311,3316,3325,3328,3334,3340,3346,3352,3363,3366,3370,3373,3378,3381,3392,3395,3399,3406,3580,3583,3587,3594,3600,3619,3623,3800,3804,3806,3810,3814,3824,3827,3973,3980,3997,4004,4006,4010,4017,4022,4132,4142,4147,4202,4215,4262,4418,4420,4424,4431,4437,4440,4549,4553,4556,4701,4718,4725,4727,4731,4735,4748,4761,4768,4780,4787,4801,4805,4808,4972,4987,5158,5177,5179,5183,5187,5201,5212,5315,5319,5326,5439,5443,5449,5458,5464,5480,5500,5502,5506,5510,5517,5625,5628,5648,5652,5663,5666,5691,5693,5697,5704,5708,5928,5931,6021,6023,6027,6032,6098,6107,6313,6321,7277,7285,8798,8806,9977,9979,9983,9998,10005,12219,12221,12225,12431,12514,12516,12520,12523,12573,12583,12585,12589,12603,12607,12613,12641,12780,12791,12793,12797,12814,12820,12822,12828,20137,20139,20143,20149,20232,20245,20251,20324,20327,20347,20355,20443,20452,20457,20530,20537,20539,20543,20598,20604,20606,20610,20748],[3308,3309,1696],"h1",{"id":3310},"websocket-повнодуплексний-протокол-реального-часу",[3312,3313,3315],"h2",{"id":3314},"фундаментальна-обмеженість-http-у-контексті-реального-часу","Фундаментальна обмеженість HTTP у контексті реального часу",[3317,3318,3319,3320,3324],"p",{},"Веб-розробка протягом десятиліть розвивалась навколо моделі ",[3321,3322,3323],"strong",{},"запит–відповідь"," (request–response), що є серцем HTTP. Ця модель є інтуїтивно зрозумілою та ефективною для переважної більшості веб-застосунків: клієнт запитує ресурс, сервер відповідає, з'єднання закривається. Однак сучасні вимоги до веб-застосунків давно вийшли за межі цієї моделі.",[3317,3326,3327],{},"Розглянемо декілька поширених сценаріїв, де модель запит–відповідь виявляється принципово недостатньою:",[3317,3329,3330,3333],{},[3321,3331,3332],{},"Чат-застосунок."," Коли Аліса надсилає повідомлення Бобу, сервер повинен негайно сповістити браузер Боба. Браузер Боба не надсилав жодного запиту — він просто чекає. За класичним HTTP-підходом це неможливо: сервер не може «штовхнути» дані клієнту без попереднього запиту.",[3317,3335,3336,3339],{},[3321,3337,3338],{},"Фінансова торгівля."," Котирування акцій змінюються сотні разів на секунду. Для відображення актуальної ціни клієнт мав би опитувати сервер з частотою, що не поступається частоті оновлення даних. Кожен такий «порожній» запит (де дані не змінились) — це марна трата ресурсів.",[3317,3341,3342,3345],{},[3321,3343,3344],{},"Онлайн-ігри."," Позиція гравця, стан ігрового поля, дії противника — всі ці дані мають передаватися з мінімальною затримкою в обох напрямках. Затримка у 100–200 мс, спричинена накладними витратами HTTP, руйнує ігровий досвід.",[3317,3347,3348,3351],{},[3321,3349,3350],{},"Спільне редагування."," Google Docs, Figma, Notion — при одночасній роботі кількох користувачів кожна зміна має миттєво відображатися у всіх підключених клієнтів.",[3353,3354,3355,3358,3359,3362],"note",{},[3321,3356,3357],{},"Ключова ідея розділу:"," HTTP є протоколом однонаправленої комунікації, ініційованої клієнтом. WebSocket — це протокол ",[3321,3360,3361],{},"повнодуплексного"," (full-duplex) з'єднання, де і клієнт, і сервер можуть ініціювати передачу даних у будь-який момент після встановлення з'єднання.",[3364,3365],"hr",{},[3312,3367,3369],{"id":3368},"еволюція-підходів-до-real-time-від-костилів-до-стандарту","Еволюція підходів до real-time: від костилів до стандарту",[3317,3371,3372],{},"Перш ніж WebSocket став стандартом (RFC 6455, 2011), розробники використовували низку хитрощів поверх HTTP для симуляції real-time поведінки. Розуміння цих підходів та їх обмежень пояснює, чому WebSocket є революційним кроком.",[3374,3375,3377],"h3",{"id":3376},"short-polling-найпростіший-і-найдорожчий-підхід","Short Polling: найпростіший і найдорожчий підхід",[3317,3379,3380],{},"Найпримітивніший спосіб отримати «нові дані» — регулярно запитувати сервер через рівні проміжки часу:",[3382,3383,3388],"pre",{"className":3384,"code":3386,"language":3387},[3385],"language-text","Кожні 2 секунди:\n  Client → Server: GET \u002Fapi\u002Fmessages?since=1234567890\n  Server → Client: 200 OK (порожній масив, якщо нових немає)\n","text",[3389,3390,3386],"code",{"__ignoreMap":3391},"",[3317,3393,3394],{},"Цей підхід надзвичайно марнотратний. Якщо частота оновлення даних нижча за частоту polling, переважна більшість запитів повертає порожню відповідь. Кожен запит несе повний HTTP-overhead: встановлення TCP-з'єднання (якщо нема Keep-Alive), HTTP-заголовки (~500 байт мінімум), обробка на сервері. При 1000 одночасних клієнтах із polling кожні 2 секунди — 500 запитів\u002Fсекунду лише для перевірки «чи є щось нове».",[3374,3396,3398],{"id":3397},"long-polling-розумніший-компроміс","Long Polling: розумніший компроміс",[3317,3400,3401,3402,3405],{},"Long Polling покращує ситуацію: клієнт надсилає запит, але сервер ",[3321,3403,3404],{},"затримує відповідь"," до моменту появи нових даних або до таймауту:",[3407,3408,3409],"plant-uml",{},[3382,3410,3414],{"className":3411,"code":3412,"language":3413,"meta":3391,"style":3391},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"Клієнт\" as client #e3f2fd\nparticipant \"Сервер\" as server #e8f5e9\n\n== Long Polling ==\nclient -> server : GET \u002Fapi\u002Fevents (запит)\nnote right of server\n  Сервер утримує з'єднання\n  до появи нових даних\n  (або 30-секундний таймаут)\nend note\n\nserver --> client : 200 OK\\n{event: \"message\", data: \"...\"}\nnote right of client\n  Відразу після отримання\n  відповіді — новий запит\nend note\n\nclient -> server : GET \u002Fapi\u002Fevents (новий запит)\nserver --> client : 200 OK (через 25с таймаут, немає даних)\n\nclient -> server : GET \u002Fapi\u002Fevents (новий запит)\nserver --> client : 200 OK\\n{event: \"user_joined\"}\n\n@enduml\n","plantuml",[3389,3415,3416,3424,3430,3436,3443,3449,3455,3460,3466,3472,3478,3484,3490,3496,3502,3507,3513,3519,3525,3531,3536,3541,3547,3553,3558,3563,3569,3574],{"__ignoreMap":3391},[3417,3418,3421],"span",{"class":3419,"line":3420},"line",1,[3417,3422,3423],{},"@startuml\n",[3417,3425,3427],{"class":3419,"line":3426},2,[3417,3428,3429],{},"skinparam style plain\n",[3417,3431,3433],{"class":3419,"line":3432},3,[3417,3434,3435],{},"skinparam backgroundColor #ffffff\n",[3417,3437,3439],{"class":3419,"line":3438},4,[3417,3440,3442],{"emptyLinePlaceholder":3441},true,"\n",[3417,3444,3446],{"class":3419,"line":3445},5,[3417,3447,3448],{},"participant \"Клієнт\" as client #e3f2fd\n",[3417,3450,3452],{"class":3419,"line":3451},6,[3417,3453,3454],{},"participant \"Сервер\" as server #e8f5e9\n",[3417,3456,3458],{"class":3419,"line":3457},7,[3417,3459,3442],{"emptyLinePlaceholder":3441},[3417,3461,3463],{"class":3419,"line":3462},8,[3417,3464,3465],{},"== Long Polling ==\n",[3417,3467,3469],{"class":3419,"line":3468},9,[3417,3470,3471],{},"client -> server : GET \u002Fapi\u002Fevents (запит)\n",[3417,3473,3475],{"class":3419,"line":3474},10,[3417,3476,3477],{},"note right of server\n",[3417,3479,3481],{"class":3419,"line":3480},11,[3417,3482,3483],{},"  Сервер утримує з'єднання\n",[3417,3485,3487],{"class":3419,"line":3486},12,[3417,3488,3489],{},"  до появи нових даних\n",[3417,3491,3493],{"class":3419,"line":3492},13,[3417,3494,3495],{},"  (або 30-секундний таймаут)\n",[3417,3497,3499],{"class":3419,"line":3498},14,[3417,3500,3501],{},"end note\n",[3417,3503,3505],{"class":3419,"line":3504},15,[3417,3506,3442],{"emptyLinePlaceholder":3441},[3417,3508,3510],{"class":3419,"line":3509},16,[3417,3511,3512],{},"server --> client : 200 OK\\n{event: \"message\", data: \"...\"}\n",[3417,3514,3516],{"class":3419,"line":3515},17,[3417,3517,3518],{},"note right of client\n",[3417,3520,3522],{"class":3419,"line":3521},18,[3417,3523,3524],{},"  Відразу після отримання\n",[3417,3526,3528],{"class":3419,"line":3527},19,[3417,3529,3530],{},"  відповіді — новий запит\n",[3417,3532,3534],{"class":3419,"line":3533},20,[3417,3535,3501],{},[3417,3537,3539],{"class":3419,"line":3538},21,[3417,3540,3442],{"emptyLinePlaceholder":3441},[3417,3542,3544],{"class":3419,"line":3543},22,[3417,3545,3546],{},"client -> server : GET \u002Fapi\u002Fevents (новий запит)\n",[3417,3548,3550],{"class":3419,"line":3549},23,[3417,3551,3552],{},"server --> client : 200 OK (через 25с таймаут, немає даних)\n",[3417,3554,3556],{"class":3419,"line":3555},24,[3417,3557,3442],{"emptyLinePlaceholder":3441},[3417,3559,3561],{"class":3419,"line":3560},25,[3417,3562,3546],{},[3417,3564,3566],{"class":3419,"line":3565},26,[3417,3567,3568],{},"server --> client : 200 OK\\n{event: \"user_joined\"}\n",[3417,3570,3572],{"class":3419,"line":3571},27,[3417,3573,3442],{"emptyLinePlaceholder":3441},[3417,3575,3577],{"class":3419,"line":3576},28,[3417,3578,3579],{},"@enduml\n",[3317,3581,3582],{},"Long Polling суттєво зменшує кількість запитів, але не вирішує фундаментальних проблем: кожна відповідь потребує нового TCP-з'єднання (або повторного використання Keep-Alive), HTTP-заголовки дублюються у кожному запиті, а передача даних залишається однонаправленою (лише сервер→клієнт через polling). Затримка в один цикл round-trip завжди присутня між появою події та її доставкою.",[3374,3584,3586],{"id":3585},"server-sent-events-sse-однонаправлений-потік-від-сервера","Server-Sent Events (SSE): однонаправлений потік від сервера",[3317,3588,3589,3590,3593],{},"SSE (RFC 8898) є більш елегантним рішенням для сценаріїв, де потрібна лише однонаправлена передача (сервер→клієнт). Клієнт відкриває одне HTTP-з'єднання, і сервер безперервно надсилає події у форматі ",[3389,3591,3592],{},"text\u002Fevent-stream",":",[3382,3595,3598],{"className":3596,"code":3597,"language":3387},[3385],"HTTP\u002F1.1 200 OK\nContent-Type: text\u002Fevent-stream\nCache-Control: no-cache\n\nid: 1\nevent: message\ndata: {\"user\": \"Alice\", \"text\": \"Привіт!\"}\n\nid: 2\nevent: message\ndata: {\"user\": \"Bob\", \"text\": \"Привіт, Аліс!\"}\n",[3389,3599,3597],{"__ignoreMap":3391},[3317,3601,3602,3603,3606,3607,3610,3611,3614,3615,3618],{},"SSE автоматично підтримує ",[3321,3604,3605],{},"reconnect"," (клієнт перепідключається при розриві з'єднання), ",[3321,3608,3609],{},"event ID"," для відновлення з місця зупинки, та ",[3321,3612,3613],{},"named events"," для маршрутизації. Браузери підтримують SSE через нативний клас ",[3389,3616,3617],{},"EventSource",". SSE — відмінний вибір для dashboards, стрічок новин, live-оновлень статусу. Але для двостороннього спілкування (клієнт↔сервер) SSE не підходить: клієнт для відправки даних мусить використовувати окремі HTTP-запити.",[3374,3620,3622],{"id":3621},"порівняльна-таблиця-підходів","Порівняльна таблиця підходів",[3624,3625,3626,3650],"table",{},[3627,3628,3629],"thead",{},[3630,3631,3632,3636,3639,3642,3645],"tr",{},[3633,3634,3635],"th",{},"Критерій",[3633,3637,3638],{},"Short Polling",[3633,3640,3641],{},"Long Polling",[3633,3643,3644],{},"SSE",[3633,3646,3647],{},[3321,3648,3649],{},"WebSocket",[3651,3652,3653,3672,3691,3709,3728,3746,3764,3781],"tbody",{},[3630,3654,3655,3661,3664,3667,3669],{},[3656,3657,3658],"td",{},[3321,3659,3660],{},"Напрямок",[3656,3662,3663],{},"Клієнт→Сервер",[3656,3665,3666],{},"Сервер→Клієнт",[3656,3668,3666],{},[3656,3670,3671],{},"Обидва напрямки",[3630,3673,3674,3679,3682,3685,3688],{},[3656,3675,3676],{},[3321,3677,3678],{},"Затримка",[3656,3680,3681],{},"Висока (інтервал)",[3656,3683,3684],{},"Середня (1 RTT)",[3656,3686,3687],{},"Низька",[3656,3689,3690],{},"Мінімальна",[3630,3692,3693,3698,3701,3704,3707],{},[3656,3694,3695],{},[3321,3696,3697],{},"HTTP overhead",[3656,3699,3700],{},"Кожен запит",[3656,3702,3703],{},"Кожен цикл",[3656,3705,3706],{},"Один раз",[3656,3708,3706],{},[3630,3710,3711,3716,3719,3722,3725],{},[3656,3712,3713],{},[3321,3714,3715],{},"TCP з'єднань",[3656,3717,3718],{},"Одне на запит",[3656,3720,3721],{},"Одне на цикл",[3656,3723,3724],{},"Одне",[3656,3726,3727],{},"Одне постійне",[3630,3729,3730,3735,3738,3741,3744],{},[3656,3731,3732],{},[3321,3733,3734],{},"Складність",[3656,3736,3737],{},"Дуже проста",[3656,3739,3740],{},"Середня",[3656,3742,3743],{},"Проста",[3656,3745,3740],{},[3630,3747,3748,3753,3756,3759,3762],{},[3656,3749,3750],{},[3321,3751,3752],{},"Reconnect",[3656,3754,3755],{},"Вбудований",[3656,3757,3758],{},"Вручну",[3656,3760,3761],{},"Автоматичний",[3656,3763,3758],{},[3630,3765,3766,3771,3774,3776,3779],{},[3656,3767,3768],{},[3321,3769,3770],{},"Підтримка браузерів",[3656,3772,3773],{},"Усі",[3656,3775,3773],{},[3656,3777,3778],{},"Усі сучасні",[3656,3780,3778],{},[3630,3782,3783,3788,3791,3794,3797],{},[3656,3784,3785],{},[3321,3786,3787],{},"Ідеально для",[3656,3789,3790],{},"Рідкі оновлення",[3656,3792,3793],{},"Помірна активність",[3656,3795,3796],{},"Live feeds",[3656,3798,3799],{},"Чат, ігри, співпраця",[3801,3802,3803],"tip",{},"SSE та WebSocket не є конкурентами — вони доповнюють одне одного. SSE простіший, автоматично reconnect, працює через HTTP\u002F2 (мультиплексування). WebSocket — для справді двосторонньої комунікації з мінімальною затримкою. Обирайте SSE для read-heavy сценаріїв, WebSocket — для interactive real-time.",[3364,3805],{},[3312,3807,3809],{"id":3808},"протокол-websocket-зсередини","Протокол WebSocket зсередини",[3374,3811,3813],{"id":3812},"стандарт-та-місце-в-стеку-протоколів","Стандарт та місце в стеку протоколів",[3317,3815,3816,3819,3820,3823],{},[3321,3817,3818],{},"WebSocket (RFC 6455)"," — протокол прикладного рівня, стандартизований IETF у грудні 2011 року. На відміну від HTTP, WebSocket є ",[3321,3821,3822],{},"повнодуплексним",": обидва боки з'єднання можуть незалежно одне від одного надсилати дані у будь-який момент, не чекаючи запиту від іншої сторони.",[3317,3825,3826],{},"Принципово важливо розуміти місце WebSocket у мережевому стеку:",[3407,3828,3829],{},[3382,3830,3832],{"className":3411,"code":3831,"language":3413,"meta":3391,"style":3391},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nrectangle \"Прикладний рівень\" #e3f2fd {\n    rectangle \"WebSocket\\n(RFC 6455)\\nws:\u002F\u002F \u002F wss:\u002F\u002F\" as ws #bbdefb\n    rectangle \"HTTP\u002F1.1\\n(RFC 7230)\\nhttp:\u002F\u002F \u002F https:\u002F\u002F\" as http #bbdefb\n}\n\nrectangle \"Транспортний рівень\" #e8f5e9 {\n    rectangle \"TCP\\n(RFC 793)\" as tcp #c8e6c9\n}\n\nrectangle \"TLS (опціонально)\" #fff3e0 {\n    rectangle \"TLS 1.3\\n(RFC 8446)\" as tls #ffecb3\n}\n\nws --> tls : wss:\u002F\u002F\\n(WebSocket Secure)\nws --> tcp : ws:\u002F\u002F\\n(незахищений)\nhttp --> tls : https:\u002F\u002F\nhttp --> tcp : http:\u002F\u002F\ntls --> tcp\n\nnote right of ws\n  WebSocket використовує\n  той самий TCP-порт 80\u002F443\n  що і HTTP!\nend note\n\n@enduml\n",[3389,3833,3834,3838,3842,3846,3850,3855,3860,3865,3870,3874,3879,3884,3888,3892,3897,3902,3906,3910,3915,3920,3925,3930,3935,3939,3944,3949,3954,3959,3963,3968],{"__ignoreMap":3391},[3417,3835,3836],{"class":3419,"line":3420},[3417,3837,3423],{},[3417,3839,3840],{"class":3419,"line":3426},[3417,3841,3429],{},[3417,3843,3844],{"class":3419,"line":3432},[3417,3845,3435],{},[3417,3847,3848],{"class":3419,"line":3438},[3417,3849,3442],{"emptyLinePlaceholder":3441},[3417,3851,3852],{"class":3419,"line":3445},[3417,3853,3854],{},"rectangle \"Прикладний рівень\" #e3f2fd {\n",[3417,3856,3857],{"class":3419,"line":3451},[3417,3858,3859],{},"    rectangle \"WebSocket\\n(RFC 6455)\\nws:\u002F\u002F \u002F wss:\u002F\u002F\" as ws #bbdefb\n",[3417,3861,3862],{"class":3419,"line":3457},[3417,3863,3864],{},"    rectangle \"HTTP\u002F1.1\\n(RFC 7230)\\nhttp:\u002F\u002F \u002F https:\u002F\u002F\" as http #bbdefb\n",[3417,3866,3867],{"class":3419,"line":3462},[3417,3868,3869],{},"}\n",[3417,3871,3872],{"class":3419,"line":3468},[3417,3873,3442],{"emptyLinePlaceholder":3441},[3417,3875,3876],{"class":3419,"line":3474},[3417,3877,3878],{},"rectangle \"Транспортний рівень\" #e8f5e9 {\n",[3417,3880,3881],{"class":3419,"line":3480},[3417,3882,3883],{},"    rectangle \"TCP\\n(RFC 793)\" as tcp #c8e6c9\n",[3417,3885,3886],{"class":3419,"line":3486},[3417,3887,3869],{},[3417,3889,3890],{"class":3419,"line":3492},[3417,3891,3442],{"emptyLinePlaceholder":3441},[3417,3893,3894],{"class":3419,"line":3498},[3417,3895,3896],{},"rectangle \"TLS (опціонально)\" #fff3e0 {\n",[3417,3898,3899],{"class":3419,"line":3504},[3417,3900,3901],{},"    rectangle \"TLS 1.3\\n(RFC 8446)\" as tls #ffecb3\n",[3417,3903,3904],{"class":3419,"line":3509},[3417,3905,3869],{},[3417,3907,3908],{"class":3419,"line":3515},[3417,3909,3442],{"emptyLinePlaceholder":3441},[3417,3911,3912],{"class":3419,"line":3521},[3417,3913,3914],{},"ws --> tls : wss:\u002F\u002F\\n(WebSocket Secure)\n",[3417,3916,3917],{"class":3419,"line":3527},[3417,3918,3919],{},"ws --> tcp : ws:\u002F\u002F\\n(незахищений)\n",[3417,3921,3922],{"class":3419,"line":3533},[3417,3923,3924],{},"http --> tls : https:\u002F\u002F\n",[3417,3926,3927],{"class":3419,"line":3538},[3417,3928,3929],{},"http --> tcp : http:\u002F\u002F\n",[3417,3931,3932],{"class":3419,"line":3543},[3417,3933,3934],{},"tls --> tcp\n",[3417,3936,3937],{"class":3419,"line":3549},[3417,3938,3442],{"emptyLinePlaceholder":3441},[3417,3940,3941],{"class":3419,"line":3555},[3417,3942,3943],{},"note right of ws\n",[3417,3945,3946],{"class":3419,"line":3560},[3417,3947,3948],{},"  WebSocket використовує\n",[3417,3950,3951],{"class":3419,"line":3565},[3417,3952,3953],{},"  той самий TCP-порт 80\u002F443\n",[3417,3955,3956],{"class":3419,"line":3571},[3417,3957,3958],{},"  що і HTTP!\n",[3417,3960,3961],{"class":3419,"line":3576},[3417,3962,3501],{},[3417,3964,3966],{"class":3419,"line":3965},29,[3417,3967,3442],{"emptyLinePlaceholder":3441},[3417,3969,3971],{"class":3419,"line":3970},30,[3417,3972,3579],{},[3317,3974,3975,3976,3979],{},"WebSocket ",[3321,3977,3978],{},"починається як HTTP-запит"," і потім «оновлюється» до WebSocket-протоколу. Це не випадково: дизайн WebSocket навмисно побудований поверх HTTP, щоб:",[3981,3982,3983,3987,3990],"ol",{},[3984,3985,3986],"li",{},"Працювати через стандартні HTTP-порти (80 і 443) без змін у мережевій інфраструктурі",[3984,3988,3989],{},"Проходити через HTTP-проксі та firewall, що пропускають HTTP-трафік",[3984,3991,3992,3993,3996],{},"Використовувати існуючу TLS-інфраструктуру для безпечного режиму (",[3389,3994,3995],{},"wss:\u002F\u002F",")",[3317,3998,3999,4000,4003],{},"Після успішного «оновлення» (Upgrade) TCP-з'єднання переходить у WebSocket-режим, і HTTP-протокол більше не використовується. З цього моменту обидва боки спілкуються безпосередньо через ",[3321,4001,4002],{},"WebSocket-фрейми",", що є принципово іншим форматом від HTTP.",[3364,4005],{},[3374,4007,4009],{"id":4008},"websocket-handshake-відкриття-зєднання","WebSocket Handshake: відкриття з'єднання",[3317,4011,4012,4013,4016],{},"Процес встановлення WebSocket-з'єднання складається з одного HTTP round-trip і називається ",[3321,4014,4015],{},"Opening Handshake",". Розглянемо його детально:",[3317,4018,4019],{},[3321,4020,4021],{},"Крок 1. Клієнт надсилає HTTP Upgrade Request:",[3382,4023,4027],{"className":4024,"code":4025,"language":4026,"meta":3391,"style":3391},"language-http shiki shiki-themes light-plus dark-plus dark-plus","GET \u002Fchat HTTP\u002F1.1\nHost: server.example.com\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nSec-WebSocket-Version: 13\nSec-WebSocket-Protocol: chat, superchat\nSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\nOrigin: https:\u002F\u002Fexample.com\n","http",[3389,4028,4029,4050,4062,4072,4082,4092,4102,4112,4122],{"__ignoreMap":3391},[3417,4030,4031,4035,4039,4043,4046],{"class":3419,"line":3420},[3417,4032,4034],{"class":4033},"s8xlr","GET",[3417,4036,4038],{"class":4037},"sHH4Y"," \u002Fchat ",[3417,4040,4042],{"class":4041},"su1O8","HTTP",[3417,4044,4045],{"class":4037},"\u002F",[3417,4047,4049],{"class":4048},"sJj4R","1.1\n",[3417,4051,4052,4056,4058],{"class":3419,"line":3426},[3417,4053,4055],{"class":4054},"sKtos","Host",[3417,4057,3593],{"class":4041},[3417,4059,4061],{"class":4060},"sbdoH"," server.example.com\n",[3417,4063,4064,4067,4069],{"class":3419,"line":3432},[3417,4065,4066],{"class":4054},"Upgrade",[3417,4068,3593],{"class":4041},[3417,4070,4071],{"class":4060}," websocket\n",[3417,4073,4074,4077,4079],{"class":3419,"line":3438},[3417,4075,4076],{"class":4054},"Connection",[3417,4078,3593],{"class":4041},[3417,4080,4081],{"class":4060}," Upgrade\n",[3417,4083,4084,4087,4089],{"class":3419,"line":3445},[3417,4085,4086],{"class":4054},"Sec-WebSocket-Key",[3417,4088,3593],{"class":4041},[3417,4090,4091],{"class":4060}," dGhlIHNhbXBsZSBub25jZQ==\n",[3417,4093,4094,4097,4099],{"class":3419,"line":3451},[3417,4095,4096],{"class":4054},"Sec-WebSocket-Version",[3417,4098,3593],{"class":4041},[3417,4100,4101],{"class":4060}," 13\n",[3417,4103,4104,4107,4109],{"class":3419,"line":3457},[3417,4105,4106],{"class":4054},"Sec-WebSocket-Protocol",[3417,4108,3593],{"class":4041},[3417,4110,4111],{"class":4060}," chat, superchat\n",[3417,4113,4114,4117,4119],{"class":3419,"line":3462},[3417,4115,4116],{"class":4054},"Sec-WebSocket-Extensions",[3417,4118,3593],{"class":4041},[3417,4120,4121],{"class":4060}," permessage-deflate; client_max_window_bits\n",[3417,4123,4124,4127,4129],{"class":3419,"line":3468},[3417,4125,4126],{"class":4054},"Origin",[3417,4128,3593],{"class":4041},[3417,4130,4131],{"class":4060}," https:\u002F\u002Fexample.com\n",[3317,4133,4134,4135,4137,4138,4141],{},"Це звичайний HTTP GET-запит із декількома спеціальними заголовками. Браузер чи клієнт сигналізує серверу про бажання оновити протокол. Заголовок ",[3389,4136,4086],{}," містить випадковий 16-байтовий nonce, закодований у Base64. Він генерується клієнтом для кожного нового з'єднання і слугує для ",[3321,4139,4140],{},"перевірки сервера"," — що сервер справді є WebSocket-сервером, а не HTTP-сервером, що випадково повертає неправильну відповідь.",[3317,4143,4144],{},[3321,4145,4146],{},"Крок 2. Сервер підтверджує Upgrade:",[3382,4148,4150],{"className":4024,"code":4149,"language":4026,"meta":3391,"style":3391},"HTTP\u002F1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\nSec-WebSocket-Protocol: chat\n",[3389,4151,4152,4167,4175,4183,4193],{"__ignoreMap":3391},[3417,4153,4154,4156,4158,4161,4164],{"class":3419,"line":3420},[3417,4155,4042],{"class":4041},[3417,4157,4045],{"class":4037},[3417,4159,4160],{"class":4048},"1.1",[3417,4162,4163],{"class":4048}," 101",[3417,4165,4166],{"class":4060}," Switching Protocols\n",[3417,4168,4169,4171,4173],{"class":3419,"line":3426},[3417,4170,4066],{"class":4054},[3417,4172,3593],{"class":4041},[3417,4174,4071],{"class":4060},[3417,4176,4177,4179,4181],{"class":3419,"line":3432},[3417,4178,4076],{"class":4054},[3417,4180,3593],{"class":4041},[3417,4182,4081],{"class":4060},[3417,4184,4185,4188,4190],{"class":3419,"line":3438},[3417,4186,4187],{"class":4054},"Sec-WebSocket-Accept",[3417,4189,3593],{"class":4041},[3417,4191,4192],{"class":4060}," s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\n",[3417,4194,4195,4197,4199],{"class":3419,"line":3445},[3417,4196,4106],{"class":4054},[3417,4198,3593],{"class":4041},[3417,4200,4201],{"class":4060}," chat\n",[3317,4203,4204,4205,4208,4209,4211,4212,4214],{},"Код статусу ",[3321,4206,4207],{},"101 Switching Protocols"," є унікальним для WebSocket Handshake. Заголовок ",[3389,4210,4187],{}," — це сервер доводить, що він отримав і обробив саме той ",[3389,4213,4086],{},", що надіслав клієнт.",[4216,4217,4218,4223,4235,4254],"field-group",{},[4219,4220,4222],"field",{"name":4086,"type":4221},"Base64(random 16 bytes)","Випадковий nonce клієнта. Не є засобом безпеки у традиційному розумінні — він не захищає від перехоплення (це роль TLS). Його мета — запобігти тому, щоб кешувальні HTTP-проксі випадково «пограли» роль WebSocket-сервера, відповідаючи кешованою HTTP-відповіддю на WebSocket-запит.",[4219,4224,4226,4227,4230,4231,4234],{"name":4187,"type":4225},"Base64(SHA-1(key + GUID))","Сервер обчислює: ",[3389,4228,4229],{},"Base64(SHA-1(Sec-WebSocket-Key + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"))",". Магічний GUID ",[3389,4232,4233],{},"258EAFA5-..."," вшито в специфікацію RFC 6455. Клієнт перевіряє це значення — якщо не збігається, з'єднання відхиляється.",[4219,4236,4238,4239,4242,4243,4246,4247,4246,4250,4253],{"name":4106,"type":4237},"string (субпротокол)","Необов'язковий заголовок для узгодження прикладного субпротоколу поверх WebSocket. Клієнт пропонує список (",[3389,4240,4241],{},"chat, superchat","), сервер обирає один. Це лише ідентифікатор — реальна інтерпретація на рівні застосунку. Приклади стандартних субпротоколів: ",[3389,4244,4245],{},"graphql-transport-ws",", ",[3389,4248,4249],{},"mqtt",[3389,4251,4252],{},"stomp",".",[4219,4255,4257,4258,4261],{"name":4116,"type":4256},"список розширень","Переговори про розширення протоколу. Найважливіше: ",[3389,4259,4260],{},"permessage-deflate"," — стиснення повідомлень. При увімкненні кожне повідомлення стискається алгоритмом Deflate (LZ77 + Huffman). Дуже ефективно для текстових JSON-повідомлень (50–70% зменшення). Для вже стиснутих бінарних даних (PNG, JPEG, ZIP) — контрпродуктивно.",[3407,4263,4264],{},[3382,4265,4267],{"className":3411,"code":4266,"language":3413,"meta":3391,"style":3391},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"Браузер \u002F Client\" as client #e3f2fd\nparticipant \"WebSocket Server\" as server #e8f5e9\n\n== Opening Handshake ==\nclient -> server : HTTP GET \u002Fchat\\nUpgrade: websocket\\nConnection: Upgrade\\nSec-WebSocket-Key: abc123==\\nSec-WebSocket-Version: 13\n\nserver -> server : SHA-1(abc123== + GUID)\\n→ Base64 → accept_value\n\nserver --> client : HTTP 101 Switching Protocols\\nUpgrade: websocket\\nConnection: Upgrade\\nSec-WebSocket-Accept: xyz456==\n\nnote over client, server\n  З цього моменту HTTP більше не використовується.\n  TCP-з'єднання тепер — WebSocket-тунель.\nend note\n\n== WebSocket Full-Duplex ==\nclient -> server : WS Frame: \"Привіт!\"\nserver -> client : WS Frame: \"Привіт у відповідь!\"\nserver -> client : WS Frame: \"Нова подія від іншого клієнта\"\nclient -> server : WS Frame: ping\nserver --> client : WS Frame: pong\n\n== Closing Handshake ==\nclient -> server : WS Close Frame (code: 1000 Normal)\nserver --> client : WS Close Frame (echo)\nnote over client, server: TCP FIN \u002F закриття з'єднання\n\n@enduml\n",[3389,4268,4269,4273,4277,4281,4285,4290,4295,4299,4304,4309,4313,4318,4322,4327,4331,4336,4341,4346,4350,4354,4359,4364,4369,4374,4379,4384,4388,4393,4398,4403,4408,4413],{"__ignoreMap":3391},[3417,4270,4271],{"class":3419,"line":3420},[3417,4272,3423],{},[3417,4274,4275],{"class":3419,"line":3426},[3417,4276,3429],{},[3417,4278,4279],{"class":3419,"line":3432},[3417,4280,3435],{},[3417,4282,4283],{"class":3419,"line":3438},[3417,4284,3442],{"emptyLinePlaceholder":3441},[3417,4286,4287],{"class":3419,"line":3445},[3417,4288,4289],{},"participant \"Браузер \u002F Client\" as client #e3f2fd\n",[3417,4291,4292],{"class":3419,"line":3451},[3417,4293,4294],{},"participant \"WebSocket Server\" as server #e8f5e9\n",[3417,4296,4297],{"class":3419,"line":3457},[3417,4298,3442],{"emptyLinePlaceholder":3441},[3417,4300,4301],{"class":3419,"line":3462},[3417,4302,4303],{},"== Opening Handshake ==\n",[3417,4305,4306],{"class":3419,"line":3468},[3417,4307,4308],{},"client -> server : HTTP GET \u002Fchat\\nUpgrade: websocket\\nConnection: Upgrade\\nSec-WebSocket-Key: abc123==\\nSec-WebSocket-Version: 13\n",[3417,4310,4311],{"class":3419,"line":3474},[3417,4312,3442],{"emptyLinePlaceholder":3441},[3417,4314,4315],{"class":3419,"line":3480},[3417,4316,4317],{},"server -> server : SHA-1(abc123== + GUID)\\n→ Base64 → accept_value\n",[3417,4319,4320],{"class":3419,"line":3486},[3417,4321,3442],{"emptyLinePlaceholder":3441},[3417,4323,4324],{"class":3419,"line":3492},[3417,4325,4326],{},"server --> client : HTTP 101 Switching Protocols\\nUpgrade: websocket\\nConnection: Upgrade\\nSec-WebSocket-Accept: xyz456==\n",[3417,4328,4329],{"class":3419,"line":3498},[3417,4330,3442],{"emptyLinePlaceholder":3441},[3417,4332,4333],{"class":3419,"line":3504},[3417,4334,4335],{},"note over client, server\n",[3417,4337,4338],{"class":3419,"line":3509},[3417,4339,4340],{},"  З цього моменту HTTP більше не використовується.\n",[3417,4342,4343],{"class":3419,"line":3515},[3417,4344,4345],{},"  TCP-з'єднання тепер — WebSocket-тунель.\n",[3417,4347,4348],{"class":3419,"line":3521},[3417,4349,3501],{},[3417,4351,4352],{"class":3419,"line":3527},[3417,4353,3442],{"emptyLinePlaceholder":3441},[3417,4355,4356],{"class":3419,"line":3533},[3417,4357,4358],{},"== WebSocket Full-Duplex ==\n",[3417,4360,4361],{"class":3419,"line":3538},[3417,4362,4363],{},"client -> server : WS Frame: \"Привіт!\"\n",[3417,4365,4366],{"class":3419,"line":3543},[3417,4367,4368],{},"server -> client : WS Frame: \"Привіт у відповідь!\"\n",[3417,4370,4371],{"class":3419,"line":3549},[3417,4372,4373],{},"server -> client : WS Frame: \"Нова подія від іншого клієнта\"\n",[3417,4375,4376],{"class":3419,"line":3555},[3417,4377,4378],{},"client -> server : WS Frame: ping\n",[3417,4380,4381],{"class":3419,"line":3560},[3417,4382,4383],{},"server --> client : WS Frame: pong\n",[3417,4385,4386],{"class":3419,"line":3565},[3417,4387,3442],{"emptyLinePlaceholder":3441},[3417,4389,4390],{"class":3419,"line":3571},[3417,4391,4392],{},"== Closing Handshake ==\n",[3417,4394,4395],{"class":3419,"line":3576},[3417,4396,4397],{},"client -> server : WS Close Frame (code: 1000 Normal)\n",[3417,4399,4400],{"class":3419,"line":3965},[3417,4401,4402],{},"server --> client : WS Close Frame (echo)\n",[3417,4404,4405],{"class":3419,"line":3970},[3417,4406,4407],{},"note over client, server: TCP FIN \u002F закриття з'єднання\n",[3417,4409,4411],{"class":3419,"line":4410},31,[3417,4412,3442],{"emptyLinePlaceholder":3441},[3417,4414,4416],{"class":3419,"line":4415},32,[3417,4417,3579],{},[3364,4419],{},[3374,4421,4423],{"id":4422},"структура-websocket-фрейму","Структура WebSocket-фрейму",[3317,4425,4426,4427,4430],{},"Після встановлення з'єднання всі дані передаються у вигляді ",[3321,4428,4429],{},"фреймів"," (frames). Формат фрейму визначено у RFC 6455, §5 і є бінарним — на відміну від текстового HTTP. Це ключова причина мінімальної затримки WebSocket:",[3382,4432,4435],{"className":4433,"code":4434,"language":3387},[3385]," 0                   1                   2                   3\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n+-+-+-+-+-------+-+-------------+-------------------------------+\n|F|R|R|R| opcode|M| Payload len |    Extended payload length    |\n|I|S|S|S|  (4)  |A|     (7)     |             (16\u002F64)           |\n|N|V|V|V|       |S|             |   (if payload len==126\u002F127)   |\n| |1|2|3|       |K|             |                               |\n+-+-+-+-+-------+-+-------------+-------------------------------+\n|     Extended payload length continued, if payload len == 127  |\n+---------------------------------------------------------------+\n|              Masking-key, if MASK set to 1                    |\n+---------------------------------------------------------------+\n|    Masking-key (cont.)    |          Payload Data             |\n+---------------------------------------------------------------+\n|                     Payload Data continued                    |\n+---------------------------------------------------------------+\n",[3389,4436,4434],{"__ignoreMap":3391},[3317,4438,4439],{},"Це виглядає складно, але насправді кожне поле виконує конкретну функцію:",[4216,4441,4442,4471,4482,4507,4523,4540],{},[4219,4443,4446,4447,4450,4451,4454,4455,4458,4459,4462,4463,4466,4467,4470],{"name":4444,"type":4445},"FIN (1 біт)","Fragment indicator","Прапорець «фінального фрейму». Якщо встановлено ",[3389,4448,4449],{},"1"," — фрейм є останнім (або єдиним) фрагментом повідомлення. Якщо ",[3389,4452,4453],{},"0"," — за ним слідуватимуть continuation frames. WebSocket підтримує ",[3321,4456,4457],{},"фрагментацію"," великих повідомлень на менші фрейми. Перший фрагмент має opcode ",[3389,4460,4461],{},"0x1"," або ",[3389,4464,4465],{},"0x2",", всі наступні — opcode ",[3389,4468,4469],{},"0x0"," (continuation), останній — FIN=1.",[4219,4472,4475,4476,4478,4479,4481],{"name":4473,"type":4474},"RSV1, RSV2, RSV3 (по 1 біту)","Reserved bits","Зарезервовані біти. Стандартно мають бути ",[3389,4477,4453],{},". Використовуються розширеннями: ",[3389,4480,4260],{}," встановлює RSV1=1 для стиснутих повідомлень. Якщо сервер отримує ненульові RSV-біти без узгодженого розширення — з'єднання закривається з кодом помилки.",[4219,4483,4486,4487,4489,4490,4493,4494,4496,4497,4500,4501,4496,4504,4506],{"name":4484,"type":4485},"Opcode (4 біти)","Тип фрейму","Визначає тип фрейму та правила його обробки. Значення від ",[3389,4488,4469],{}," до ",[3389,4491,4492],{},"0xF",". Розподіл: ",[3389,4495,4469],{},"–",[3389,4498,4499],{},"0x7"," — data frames (фрейми даних), ",[3389,4502,4503],{},"0x8",[3389,4505,4492],{}," — control frames (управляючі фрейми). Control frames не можна фрагментувати і їх розмір обмежений 125 байтами.",[4219,4508,4511,4512,4514,4515,4518,4519,4522],{"name":4509,"type":4510},"MASK (1 біт)","Masking flag","Якщо встановлено ",[3389,4513,4449],{}," — payload замаскований. За специфікацією RFC 6455: ",[3321,4516,4517],{},"клієнт зобов'язаний"," маскувати всі фрейми, що надсилаються серверу. ",[3321,4520,4521],{},"Сервер не повинен"," маскувати фрейми, що надсилаються клієнту. Причина маскування — захист від атак на проміжні HTTP-проксі.",[4219,4524,4527,4528,4531,4532,4535,4536,4539],{"name":4525,"type":4526},"Payload Length (7 + 0\u002F16\u002F64 біт)","Розмір payload","Три варіанти кодування: ",[3389,4529,4530],{},"0–125"," — довжина вміщується в 7 бітах. ",[3389,4533,4534],{},"126"," — наступні 2 байти (uint16) містять справжню довжину (до 65535 байт). ",[3389,4537,4538],{},"127"," — наступні 8 байт (uint64) містять справжню довжину (до ~18 ексабайт). Це компактне представлення: маленькі повідомлення займають лише 2 байти заголовку.",[4219,4541,4544,4545,4548],{"name":4542,"type":4543},"Masking Key (32 біти)","Ключ маскування (тільки від клієнта)","Випадковий 4-байтовий ключ, що генерується для кожного фрейму окремо. Payload XOR-ується з ключем циклічно: ",[3389,4546,4547],{},"masked[i] = data[i] XOR key[i % 4]",". Маскування — симетрична операція: XOR двічі повертає оригінал. Сервер розмасковує тим самим ключем. Це не шифрування — воно не захищає від перехоплення, а лише від певних типів атак на кеш-проксі.",[3374,4550,4552],{"id":4551},"websocket-opcodes-мова-фреймів","WebSocket Opcodes: мова фреймів",[3317,4554,4555],{},"Чотири біти opcode визначають один із 16 можливих типів фрейму:",[3624,4557,4558,4574],{},[3627,4559,4560],{},[3630,4561,4562,4565,4568,4571],{},[3633,4563,4564],{},"Opcode",[3633,4566,4567],{},"Hex",[3633,4569,4570],{},"Назва",[3633,4572,4573],{},"Призначення",[3651,4575,4576,4590,4604,4619,4637,4652,4668,4684],{},[3630,4577,4578,4580,4584,4587],{},[3656,4579,4453],{},[3656,4581,4582],{},[3389,4583,4469],{},[3656,4585,4586],{},"Continuation",[3656,4588,4589],{},"Продовження фрагментованого повідомлення",[3630,4591,4592,4594,4598,4601],{},[3656,4593,4449],{},[3656,4595,4596],{},[3389,4597,4461],{},[3656,4599,4600],{},"Text",[3656,4602,4603],{},"Текстові дані (UTF-8 обов'язково)",[3630,4605,4606,4609,4613,4616],{},[3656,4607,4608],{},"2",[3656,4610,4611],{},[3389,4612,4465],{},[3656,4614,4615],{},"Binary",[3656,4617,4618],{},"Бінарні дані (довільний формат)",[3630,4620,4621,4624,4631,4634],{},[3656,4622,4623],{},"3–7",[3656,4625,4626,4496,4629],{},[3389,4627,4628],{},"0x3",[3389,4630,4499],{},[3656,4632,4633],{},"—",[3656,4635,4636],{},"Зарезервовано для майбутніх data frames",[3630,4638,4639,4642,4646,4649],{},[3656,4640,4641],{},"8",[3656,4643,4644],{},[3389,4645,4503],{},[3656,4647,4648],{},"Close",[3656,4650,4651],{},"Ініціювання закриття з'єднання",[3630,4653,4654,4657,4662,4665],{},[3656,4655,4656],{},"9",[3656,4658,4659],{},[3389,4660,4661],{},"0x9",[3656,4663,4664],{},"Ping",[3656,4666,4667],{},"Перевірка живучості з'єднання",[3630,4669,4670,4673,4678,4681],{},[3656,4671,4672],{},"10",[3656,4674,4675],{},[3389,4676,4677],{},"0xA",[3656,4679,4680],{},"Pong",[3656,4682,4683],{},"Відповідь на Ping",[3630,4685,4686,4689,4696,4698],{},[3656,4687,4688],{},"11–15",[3656,4690,4691,4496,4694],{},[3389,4692,4693],{},"0xB",[3389,4695,4492],{},[3656,4697,4633],{},[3656,4699,4700],{},"Зарезервовано для майбутніх control frames",[3317,4702,4703,4704,4706,4707,4709,4710,4713,4714,4717],{},"Розподіл на ",[3321,4705,4600],{}," та ",[3321,4708,4615],{}," є принциповим. Text-фрейми зобов'язані містити валідний ",[3321,4711,4712],{},"UTF-8"," — якщо сервер або клієнт отримує Text-фрейм із некоректним UTF-8, з'єднання закривається з кодом ",[3389,4715,4716],{},"1007 Invalid frame payload data",". Binary-фрейми можуть містити будь-які байти — Protocol Buffers, MessagePack, стиснуті дані, зображення.",[3317,4719,4720,4721,4724],{},"На практиці більшість веб-застосунків використовує ",[3321,4722,4723],{},"Text-фрейми з JSON",". Це зручно для debugging і сумісності, але менш ефективно за бінарний формат. Для систем з жорсткими вимогами до продуктивності (онлайн-ігри, фінансові дані) використовують Binary-фрейми з Protocol Buffers або FlatBuffers.",[3364,4726],{},[3312,4728,4730],{"id":4729},"lifecycle-websocket-зєднання","Lifecycle WebSocket-з'єднання",[3374,4732,4734],{"id":4733},"pingpong-перевірка-живучості","Ping\u002FPong: перевірка живучості",[3317,4736,4737,4738,4741,4742,4744,4745,4747],{},"WebSocket-з'єднання є постійним, але мережа — ненадійна. Мобільні пристрої переходять між мережами, NAT-таблиці очищуються через неактивність, проміжні проксі розривають «мертві» з'єднання. Механізм ",[3321,4739,4740],{},"Ping\u002FPong"," (opcodes ",[3389,4743,4661],{}," і ",[3389,4746,4677],{},") вирішує цю проблему.",[3317,4749,4750,4751,4753,4754,4757,4758,4760],{},"Будь-яка зі сторін може надіслати ",[3321,4752,4664],{},"-фрейм. Одержувач ",[3321,4755,4756],{},"зобов'язаний"," відповісти ",[3321,4759,4680],{},"-фреймом із тим самим payload. Якщо Pong не надходить протягом розумного часу — з'єднання вважається розірваним і закривається.",[3317,4762,4763,4764,4767],{},"На практиці саме ",[3321,4765,4766],{},"сервер"," надсилає Ping клієнтам через певний інтервал (зазвичай 30–60 секунд). Це дозволяє:",[4769,4770,4771,4774,4777],"ul",{},[3984,4772,4773],{},"Виявити «зомбі»-з'єднання (клієнт завис, але TCP-з'єднання формально відкрите)",[3984,4775,4776],{},"Утримувати з'єднання через NAT і проксі, що закривають неактивні з'єднання",[3984,4778,4779],{},"Вимірювати round-trip latency між сервером і клієнтом",[3317,4781,4782,4783,4786],{},"Клієнт також може надсилати ",[3321,4784,4785],{},"unsolicited Pong"," (Pong без попереднього Ping) — це допустимо за RFC 6455 і слугує як keepalive-сигнал від клієнта.",[3353,4788,4789,4790,4793,4794,4796,4797,4800],{},"Браузерний JavaScript ",[3321,4791,4792],{},"не дозволяє"," застосунку безпосередньо надсилати Ping або обробляти Pong через API ",[3389,4795,3649],{},". Браузер обробляє Ping\u002FPong автоматично на рівні реалізації. Цей контроль є лише у нативних клієнтах (наприклад, ",[3389,4798,4799],{},"ClientWebSocket"," у .NET).",[3374,4802,4804],{"id":4803},"closing-handshake-коректне-закриття","Closing Handshake: коректне закриття",[3317,4806,4807],{},"Коректне закриття WebSocket-з'єднання — це двосторонній процес, аналогічний TCP FIN\u002FFIN-ACK, але на рівні протоколу:",[3407,4809,4810],{},[3382,4811,4813],{"className":3411,"code":4812,"language":3413,"meta":3391,"style":3391},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"Ініціатор\\n(Client або Server)\" as initiator #e3f2fd\nparticipant \"Одержувач\" as receiver #e8f5e9\n\n== Коректне закриття (Graceful Close) ==\ninitiator -> receiver : Close Frame\\n[code: 1000, reason: \"Normal closure\"]\nnote right of receiver\n  Одержувач отримав Close Frame.\n  Він відправляє власний Close Frame\n  і більше не надсилає даних.\nend note\n\nreceiver -> receiver : Завершує обробку\\nнезавершених повідомлень\nreceiver --> initiator : Close Frame\\n[code: 1000, echo]\n\ninitiator -> receiver : TCP FIN\nreceiver --> initiator : TCP FIN-ACK\n\nnote over initiator, receiver\n  З'єднання повністю закрито.\nend note\n\n== Аварійне закриття (Abnormal Close) ==\ninitiator -> receiver : TCP RST (або з'єднання обривається без Close Frame)\nnote right of receiver\n  Одержувач виявляє розрив з'єднання.\n  Close Frame не отримано.\n  Стан: ABNORMAL CLOSURE (code: 1006)\nend note\n\n@enduml\n",[3389,4814,4815,4819,4823,4827,4831,4836,4841,4845,4850,4855,4860,4865,4870,4875,4879,4883,4888,4893,4897,4902,4907,4911,4916,4921,4925,4929,4934,4939,4943,4948,4953,4958,4962,4967],{"__ignoreMap":3391},[3417,4816,4817],{"class":3419,"line":3420},[3417,4818,3423],{},[3417,4820,4821],{"class":3419,"line":3426},[3417,4822,3429],{},[3417,4824,4825],{"class":3419,"line":3432},[3417,4826,3435],{},[3417,4828,4829],{"class":3419,"line":3438},[3417,4830,3442],{"emptyLinePlaceholder":3441},[3417,4832,4833],{"class":3419,"line":3445},[3417,4834,4835],{},"participant \"Ініціатор\\n(Client або Server)\" as initiator #e3f2fd\n",[3417,4837,4838],{"class":3419,"line":3451},[3417,4839,4840],{},"participant \"Одержувач\" as receiver #e8f5e9\n",[3417,4842,4843],{"class":3419,"line":3457},[3417,4844,3442],{"emptyLinePlaceholder":3441},[3417,4846,4847],{"class":3419,"line":3462},[3417,4848,4849],{},"== Коректне закриття (Graceful Close) ==\n",[3417,4851,4852],{"class":3419,"line":3468},[3417,4853,4854],{},"initiator -> receiver : Close Frame\\n[code: 1000, reason: \"Normal closure\"]\n",[3417,4856,4857],{"class":3419,"line":3474},[3417,4858,4859],{},"note right of receiver\n",[3417,4861,4862],{"class":3419,"line":3480},[3417,4863,4864],{},"  Одержувач отримав Close Frame.\n",[3417,4866,4867],{"class":3419,"line":3486},[3417,4868,4869],{},"  Він відправляє власний Close Frame\n",[3417,4871,4872],{"class":3419,"line":3492},[3417,4873,4874],{},"  і більше не надсилає даних.\n",[3417,4876,4877],{"class":3419,"line":3498},[3417,4878,3501],{},[3417,4880,4881],{"class":3419,"line":3504},[3417,4882,3442],{"emptyLinePlaceholder":3441},[3417,4884,4885],{"class":3419,"line":3509},[3417,4886,4887],{},"receiver -> receiver : Завершує обробку\\nнезавершених повідомлень\n",[3417,4889,4890],{"class":3419,"line":3515},[3417,4891,4892],{},"receiver --> initiator : Close Frame\\n[code: 1000, echo]\n",[3417,4894,4895],{"class":3419,"line":3521},[3417,4896,3442],{"emptyLinePlaceholder":3441},[3417,4898,4899],{"class":3419,"line":3527},[3417,4900,4901],{},"initiator -> receiver : TCP FIN\n",[3417,4903,4904],{"class":3419,"line":3533},[3417,4905,4906],{},"receiver --> initiator : TCP FIN-ACK\n",[3417,4908,4909],{"class":3419,"line":3538},[3417,4910,3442],{"emptyLinePlaceholder":3441},[3417,4912,4913],{"class":3419,"line":3543},[3417,4914,4915],{},"note over initiator, receiver\n",[3417,4917,4918],{"class":3419,"line":3549},[3417,4919,4920],{},"  З'єднання повністю закрито.\n",[3417,4922,4923],{"class":3419,"line":3555},[3417,4924,3501],{},[3417,4926,4927],{"class":3419,"line":3560},[3417,4928,3442],{"emptyLinePlaceholder":3441},[3417,4930,4931],{"class":3419,"line":3565},[3417,4932,4933],{},"== Аварійне закриття (Abnormal Close) ==\n",[3417,4935,4936],{"class":3419,"line":3571},[3417,4937,4938],{},"initiator -> receiver : TCP RST (або з'єднання обривається без Close Frame)\n",[3417,4940,4941],{"class":3419,"line":3576},[3417,4942,4859],{},[3417,4944,4945],{"class":3419,"line":3965},[3417,4946,4947],{},"  Одержувач виявляє розрив з'єднання.\n",[3417,4949,4950],{"class":3419,"line":3970},[3417,4951,4952],{},"  Close Frame не отримано.\n",[3417,4954,4955],{"class":3419,"line":4410},[3417,4956,4957],{},"  Стан: ABNORMAL CLOSURE (code: 1006)\n",[3417,4959,4960],{"class":3419,"line":4415},[3417,4961,3501],{},[3417,4963,4965],{"class":3419,"line":4964},33,[3417,4966,3442],{"emptyLinePlaceholder":3441},[3417,4968,4970],{"class":3419,"line":4969},34,[3417,4971,3579],{},[3317,4973,4974,4975,4978,4979,4982,4983,4986],{},"Після отримання Close Frame жодна зі сторін не повинна надсилати нових ",[3321,4976,4977],{},"data frames",". Close Frame може містити ",[3321,4980,4981],{},"Close Code"," (uint16) та необов'язковий текстовий ",[3321,4984,4985],{},"reason"," (UTF-8, до 123 байт):",[3624,4988,4989,5000],{},[3627,4990,4991],{},[3630,4992,4993,4995,4997],{},[3633,4994,4981],{},[3633,4996,4570],{},[3633,4998,4999],{},"Значення",[3651,5001,5002,5015,5028,5041,5054,5067,5080,5093,5106,5119,5132,5145],{},[3630,5003,5004,5009,5012],{},[3656,5005,5006],{},[3321,5007,5008],{},"1000",[3656,5010,5011],{},"Normal Closure",[3656,5013,5014],{},"Коректне закриття. З'єднання виконало своє завдання",[3630,5016,5017,5022,5025],{},[3656,5018,5019],{},[3321,5020,5021],{},"1001",[3656,5023,5024],{},"Going Away",[3656,5026,5027],{},"Сервер перезавантажується або клієнт закриває сторінку",[3630,5029,5030,5035,5038],{},[3656,5031,5032],{},[3321,5033,5034],{},"1002",[3656,5036,5037],{},"Protocol Error",[3656,5039,5040],{},"Порушення протоколу WebSocket",[3630,5042,5043,5048,5051],{},[3656,5044,5045],{},[3321,5046,5047],{},"1003",[3656,5049,5050],{},"Unsupported Data",[3656,5052,5053],{},"Неприйнятний тип даних (наприклад, binary замість text)",[3630,5055,5056,5061,5064],{},[3656,5057,5058],{},[3321,5059,5060],{},"1005",[3656,5062,5063],{},"No Status",[3656,5065,5066],{},"Зарезервований: Close Frame отримано без коду (внутрішнє)",[3630,5068,5069,5074,5077],{},[3656,5070,5071],{},[3321,5072,5073],{},"1006",[3656,5075,5076],{},"Abnormal Closure",[3656,5078,5079],{},"Зарезервований: з'єднання розірвано без Close Frame",[3630,5081,5082,5087,5090],{},[3656,5083,5084],{},[3321,5085,5086],{},"1007",[3656,5088,5089],{},"Invalid Payload",[3656,5091,5092],{},"Некоректне кодування (не UTF-8 у text frame)",[3630,5094,5095,5100,5103],{},[3656,5096,5097],{},[3321,5098,5099],{},"1008",[3656,5101,5102],{},"Policy Violation",[3656,5104,5105],{},"Порушення політики сервера (занадто великий payload тощо)",[3630,5107,5108,5113,5116],{},[3656,5109,5110],{},[3321,5111,5112],{},"1009",[3656,5114,5115],{},"Message Too Big",[3656,5117,5118],{},"Повідомлення перевищує ліміт розміру сервера",[3630,5120,5121,5126,5129],{},[3656,5122,5123],{},[3321,5124,5125],{},"1011",[3656,5127,5128],{},"Server Error",[3656,5130,5131],{},"Внутрішня помилка сервера, неможливо обробити запит",[3630,5133,5134,5139,5142],{},[3656,5135,5136],{},[3321,5137,5138],{},"1012",[3656,5140,5141],{},"Service Restart",[3656,5143,5144],{},"Сервер перезавантажується; клієнт може повторити спробу",[3630,5146,5147,5152,5155],{},[3656,5148,5149],{},[3321,5150,5151],{},"4000–4999",[3656,5153,5154],{},"Application",[3656,5156,5157],{},"Зарезервовано для прикладних кодів (визначається застосунком)",[5159,5160,5161,5162,4246,5165,4246,5167,4246,5169,5172,5173,5176],"warning",{},"Коди ",[3389,5163,5164],{},"1004",[3389,5166,5060],{},[3389,5168,5073],{},[3389,5170,5171],{},"1015"," зарезервовані RFC і ",[3321,5174,5175],{},"ніколи не повинні"," передаватися у Wire (реальному Close Frame). Вони використовуються лише внутрішньо у реалізаціях WebSocket для позначення певних станів.",[3364,5178],{},[3312,5180,5182],{"id":5181},"безпека-websocket","Безпека WebSocket",[3374,5184,5186],{"id":5185},"wss-websocket-secure","WSS — WebSocket Secure",[3317,5188,5189,5190,5193,5194,5197,5198,5200],{},"Незахищений WebSocket (",[3389,5191,5192],{},"ws:\u002F\u002F",") передає всі дані у відкритому вигляді, включно з будь-якими токенами автентифікації та чутливими даними. У будь-якому продакшн-середовищі ",[3321,5195,5196],{},"обов'язково"," використовуйте ",[3389,5199,3995],{}," — WebSocket поверх TLS.",[3317,5202,5203,5205,5206,5208,5209,5211],{},[3389,5204,3995],{}," працює ідентично ",[3389,5207,3995],{}," HTTPS: TLS-з'єднання встановлюється першим, HTTP Upgrade Handshake відбувається вже всередині шифрованого тунелю. З точки зору мережевої інфраструктури, ",[3389,5210,3995],{}," трафік є невідрізним від HTTPS-трафіку.",[3407,5213,5214],{},[3382,5215,5217],{"className":3411,"code":5216,"language":3413,"meta":3391,"style":3391},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nparticipant \"Client\" as client #e3f2fd\nparticipant \"Nginx\u002FLoad Balancer\\n(TLS Termination)\" as lb #fff3e0\nparticipant \"ASP.NET Core\\n(ws:\u002F\u002F internally)\" as app #e8f5e9\n\nclient -> lb : wss:\u002F\u002Fchat.example.com\u002Fws\\n(TLS 1.3 шифрування)\nlb -> lb : TLS Termination:\\nРозшифровує трафік\nlb -> app : ws:\u002F\u002Flocalhost:5000\u002Fws\\n(plaintext у внутрішній мережі)\napp --> lb : ws:\u002F\u002F frames\nlb --> client : wss:\u002F\u002F frames (зашифровано)\n\nnote right of lb\n  Типова продакшн-схема:\n  TLS termination на Nginx\u002FCaddy.\n  Внутрішньо — plaintext ws:\u002F\u002F.\nend note\n\n@enduml\n",[3389,5218,5219,5223,5227,5231,5235,5240,5245,5250,5254,5259,5264,5269,5274,5279,5283,5288,5293,5298,5303,5307,5311],{"__ignoreMap":3391},[3417,5220,5221],{"class":3419,"line":3420},[3417,5222,3423],{},[3417,5224,5225],{"class":3419,"line":3426},[3417,5226,3429],{},[3417,5228,5229],{"class":3419,"line":3432},[3417,5230,3435],{},[3417,5232,5233],{"class":3419,"line":3438},[3417,5234,3442],{"emptyLinePlaceholder":3441},[3417,5236,5237],{"class":3419,"line":3445},[3417,5238,5239],{},"participant \"Client\" as client #e3f2fd\n",[3417,5241,5242],{"class":3419,"line":3451},[3417,5243,5244],{},"participant \"Nginx\u002FLoad Balancer\\n(TLS Termination)\" as lb #fff3e0\n",[3417,5246,5247],{"class":3419,"line":3457},[3417,5248,5249],{},"participant \"ASP.NET Core\\n(ws:\u002F\u002F internally)\" as app #e8f5e9\n",[3417,5251,5252],{"class":3419,"line":3462},[3417,5253,3442],{"emptyLinePlaceholder":3441},[3417,5255,5256],{"class":3419,"line":3468},[3417,5257,5258],{},"client -> lb : wss:\u002F\u002Fchat.example.com\u002Fws\\n(TLS 1.3 шифрування)\n",[3417,5260,5261],{"class":3419,"line":3474},[3417,5262,5263],{},"lb -> lb : TLS Termination:\\nРозшифровує трафік\n",[3417,5265,5266],{"class":3419,"line":3480},[3417,5267,5268],{},"lb -> app : ws:\u002F\u002Flocalhost:5000\u002Fws\\n(plaintext у внутрішній мережі)\n",[3417,5270,5271],{"class":3419,"line":3486},[3417,5272,5273],{},"app --> lb : ws:\u002F\u002F frames\n",[3417,5275,5276],{"class":3419,"line":3492},[3417,5277,5278],{},"lb --> client : wss:\u002F\u002F frames (зашифровано)\n",[3417,5280,5281],{"class":3419,"line":3498},[3417,5282,3442],{"emptyLinePlaceholder":3441},[3417,5284,5285],{"class":3419,"line":3504},[3417,5286,5287],{},"note right of lb\n",[3417,5289,5290],{"class":3419,"line":3509},[3417,5291,5292],{},"  Типова продакшн-схема:\n",[3417,5294,5295],{"class":3419,"line":3515},[3417,5296,5297],{},"  TLS termination на Nginx\u002FCaddy.\n",[3417,5299,5300],{"class":3419,"line":3521},[3417,5301,5302],{},"  Внутрішньо — plaintext ws:\u002F\u002F.\n",[3417,5304,5305],{"class":3419,"line":3527},[3417,5306,3501],{},[3417,5308,5309],{"class":3419,"line":3533},[3417,5310,3442],{"emptyLinePlaceholder":3441},[3417,5312,5313],{"class":3419,"line":3538},[3417,5314,3579],{},[3374,5316,5318],{"id":5317},"аутентифікація-websocket-зєднань","Аутентифікація WebSocket-з'єднань",[3317,5320,5321,5322,5325],{},"WebSocket не має вбудованого механізму аутентифікації — на відміну від HTTP, де кожен запит може нести ",[3389,5323,5324],{},"Authorization"," заголовок. Існують три поширені підходи:",[5327,5328,5329,5350,5378],"accordion",{},[5330,5331,5334,5337,5343],"accordion-item",{"icon":5332,"label":5333},"i-lucide-link","Query Parameter (найпростіший, але небезпечний)",[3317,5335,5336],{},"Токен передається у URL при відкритті з'єднання:",[3382,5338,5341],{"className":5339,"code":5340,"language":3387},[3385],"ws:\u002F\u002Fserver.com\u002Fchat?token=eyJhbGciOiJIUzI1NiJ9...\n",[3389,5342,5340],{"__ignoreMap":3391},[3317,5344,5345,5346,5349],{},"Переваги: простота реалізації, підтримується всюди. ",[3321,5347,5348],{},"Недоліки:"," URL логується у server access logs, проксі-серверах, браузерній історії. Токен у відкритому вигляді у логах — критична вразливість. Прийнятно лише для short-lived токенів (\u003C 30 секунд дії) або у нечутливих сценаріях.",[5330,5351,5354,5361],{"icon":5352,"label":5353},"i-lucide-cookie","Cookie (найкращий для браузерів)",[3317,5355,5356,5357,5360],{},"Браузер автоматично надсилає cookies із WebSocket Upgrade Request — так само, як і зі звичайними HTTP-запитами. Якщо HTTP-сесія вже аутентифікована через ",[3389,5358,5359],{},"HttpOnly"," cookie, WebSocket-з'єднання автоматично успадковує цю автентифікацію.",[3317,5362,5363,5364,5367,5368,5370,5371,5373,5374,5377],{},"Сервер перевіряє cookie у момент Upgrade Handshake. Якщо cookie відсутній або недійсний — повертає ",[3389,5365,5366],{},"401 Unauthorized"," замість ",[3389,5369,4207],{},". Це найбезпечніший підхід для браузерних клієнтів, оскільки cookie захищений ",[3389,5372,5359],{}," (недоступний через JavaScript) та ",[3389,5375,5376],{},"Secure"," (тільки HTTPS).",[5330,5379,5382,5389,5423],{"icon":5380,"label":5381},"i-lucide-message-square","Перший Message після підключення (для нативних клієнтів)",[3317,5383,5384,5385,5388],{},"Клієнт підключається до WebSocket без аутентифікації, але ",[3321,5386,5387],{},"першим повідомленням"," надсилає токен:",[3382,5390,5394],{"className":5391,"code":5392,"language":5393,"meta":3391,"style":3391},"language-json shiki shiki-themes light-plus dark-plus dark-plus","{\"type\": \"auth\", \"token\": \"eyJhbGciOiJIUzI1NiJ9...\"}\n","json",[3389,5395,5396],{"__ignoreMap":3391},[3417,5397,5398,5401,5405,5408,5411,5413,5416,5418,5421],{"class":3419,"line":3420},[3417,5399,5400],{"class":4037},"{",[3417,5402,5404],{"class":5403},"sLwNe","\"type\"",[3417,5406,5407],{"class":4037},": ",[3417,5409,5410],{"class":4060},"\"auth\"",[3417,5412,4246],{"class":4037},[3417,5414,5415],{"class":5403},"\"token\"",[3417,5417,5407],{"class":4037},[3417,5419,5420],{"class":4060},"\"eyJhbGciOiJIUzI1NiJ9...\"",[3417,5422,3869],{"class":4037},[3317,5424,5425,5426,5429,5430,5432,5433,5436,5437,4800],{},"Сервер чекає це повідомлення протягом певного таймауту (наприклад, 5 секунд). Якщо токен відсутній або недійсний — закриває з'єднання з кодом ",[3389,5427,5428],{},"4001",". Цей підхід популярний для нативних клієнтів і API-з'єднань, де cookies недоступні. Перевага: заголовок ",[3389,5431,5324],{}," стандартно ",[3321,5434,5435],{},"не підтримується"," у WebSocket Upgrade Request браузерним API (але підтримується ",[3389,5438,4799],{},[3374,5440,5442],{"id":5441},"захист-від-cross-site-websocket-hijacking-cswsh","Захист від Cross-Site WebSocket Hijacking (CSWSH)",[3317,5444,5445,5448],{},[3321,5446,5447],{},"CSWSH"," — аналог CSRF для WebSocket. Зловмисник може змусити браузер жертви відкрити WebSocket-з'єднання до сервера, якщо браузер автоматично додає cookies автентифікації.",[3317,5450,5451,5452,5457],{},"На відміну від CSRF, тут немає CSRF-токенів «з коробки». Основний захист — ",[3321,5453,5454,5455],{},"перевірка заголовку ",[3389,5456,4126],{}," у момент Handshake:",[3382,5459,5462],{"className":5460,"code":5461,"language":3387},[3385],"Запит браузера: Origin: https:\u002F\u002Fevil.com\nСервер перевіряє: чи є evil.com у whitelist дозволених Origins?\nЯкщо ні → HTTP 403 Forbidden (відмовляємо у Upgrade)\n",[3389,5463,5461],{"__ignoreMap":3391},[3317,5465,5466,5467,5470,5471,5473,5474,5476,5477,5479],{},"Браузер ",[3321,5468,5469],{},"завжди"," надсилає заголовок ",[3389,5472,4126],{}," у WebSocket Upgrade Request і ",[3321,5475,4792],{}," JavaScript змінити його. Тому перевірка ",[3389,5478,4126],{}," на сервері є надійним захистом від CSWSH.",[5481,5482,5483,5484,5486,5487,5490,5491,5493,5494,5496,5497,5499],"caution",{},"Не плутайте ",[3389,5485,4126],{}," із ",[3389,5488,5489],{},"Referer",". ",[3389,5492,4126],{}," надійний — браузер не дозволяє його підробити. Нативні клієнти (curl, ",[3389,5495,4799],{},") можуть надсилати будь-який ",[3389,5498,4126],{},", але вони і так контролюються розробником, а не стороннім сайтом.",[3364,5501],{},[3312,5503,5505],{"id":5504},"масштабування-websocket-виклики-і-рішення","Масштабування WebSocket: виклики і рішення",[3374,5507,5509],{"id":5508},"проблема-sticky-sessions-та-горизонтального-масштабування","Проблема sticky sessions та горизонтального масштабування",[3317,5511,5512,5513,5516],{},"Кожне WebSocket-з'єднання є ",[3321,5514,5515],{},"стійким TCP-з'єднанням"," до конкретного екземпляра сервера. Якщо у вас три екземпляри ASP.NET Core, кожен підключений клієнт «прив'язаний» до одного з них. Коли Аліса надсилає повідомлення Бобу, Аліса підключена до Server-1, але Боб може бути підключеним до Server-2 або Server-3.",[3407,5518,5519],{},[3382,5520,5522],{"className":3411,"code":5521,"language":3413,"meta":3391,"style":3391},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\nactor \"Alice (підключена до Server-1)\" as alice\nactor \"Bob (підключена до Server-2)\" as bob\nparticipant \"Server-1\\n(ASP.NET Core)\" as s1 #e3f2fd\nparticipant \"Server-2\\n(ASP.NET Core)\" as s2 #e3f2fd\nparticipant \"Message Broker\\n(Redis Pub\u002FSub)\" as redis #e8f5e9\n\nalice -> s1 : WS: \"Привіт, Боб!\"\ns1 -> redis : PUBLISH channel:bob \"Привіт, Боб!\"\nredis -> s2 : Повідомлення у channel:bob\ns2 -> bob : WS: \"Привіт, Боб!\"\n\nnote over s1, s2\n  Обидва сервери підписані\n  на Redis Pub\u002FSub канали.\n  Redis є \"шиною\" між серверами.\nend note\n\n@enduml\n",[3389,5523,5524,5528,5532,5536,5540,5545,5550,5555,5560,5565,5569,5574,5579,5584,5589,5593,5598,5603,5608,5613,5617,5621],{"__ignoreMap":3391},[3417,5525,5526],{"class":3419,"line":3420},[3417,5527,3423],{},[3417,5529,5530],{"class":3419,"line":3426},[3417,5531,3429],{},[3417,5533,5534],{"class":3419,"line":3432},[3417,5535,3435],{},[3417,5537,5538],{"class":3419,"line":3438},[3417,5539,3442],{"emptyLinePlaceholder":3441},[3417,5541,5542],{"class":3419,"line":3445},[3417,5543,5544],{},"actor \"Alice (підключена до Server-1)\" as alice\n",[3417,5546,5547],{"class":3419,"line":3451},[3417,5548,5549],{},"actor \"Bob (підключена до Server-2)\" as bob\n",[3417,5551,5552],{"class":3419,"line":3457},[3417,5553,5554],{},"participant \"Server-1\\n(ASP.NET Core)\" as s1 #e3f2fd\n",[3417,5556,5557],{"class":3419,"line":3462},[3417,5558,5559],{},"participant \"Server-2\\n(ASP.NET Core)\" as s2 #e3f2fd\n",[3417,5561,5562],{"class":3419,"line":3468},[3417,5563,5564],{},"participant \"Message Broker\\n(Redis Pub\u002FSub)\" as redis #e8f5e9\n",[3417,5566,5567],{"class":3419,"line":3474},[3417,5568,3442],{"emptyLinePlaceholder":3441},[3417,5570,5571],{"class":3419,"line":3480},[3417,5572,5573],{},"alice -> s1 : WS: \"Привіт, Боб!\"\n",[3417,5575,5576],{"class":3419,"line":3486},[3417,5577,5578],{},"s1 -> redis : PUBLISH channel:bob \"Привіт, Боб!\"\n",[3417,5580,5581],{"class":3419,"line":3492},[3417,5582,5583],{},"redis -> s2 : Повідомлення у channel:bob\n",[3417,5585,5586],{"class":3419,"line":3498},[3417,5587,5588],{},"s2 -> bob : WS: \"Привіт, Боб!\"\n",[3417,5590,5591],{"class":3419,"line":3504},[3417,5592,3442],{"emptyLinePlaceholder":3441},[3417,5594,5595],{"class":3419,"line":3509},[3417,5596,5597],{},"note over s1, s2\n",[3417,5599,5600],{"class":3419,"line":3515},[3417,5601,5602],{},"  Обидва сервери підписані\n",[3417,5604,5605],{"class":3419,"line":3521},[3417,5606,5607],{},"  на Redis Pub\u002FSub канали.\n",[3417,5609,5610],{"class":3419,"line":3527},[3417,5611,5612],{},"  Redis є \"шиною\" між серверами.\n",[3417,5614,5615],{"class":3419,"line":3533},[3417,5616,3501],{},[3417,5618,5619],{"class":3419,"line":3538},[3417,5620,3442],{"emptyLinePlaceholder":3441},[3417,5622,5623],{"class":3419,"line":3543},[3417,5624,3579],{},[3317,5626,5627],{},"Типові рішення для масштабування:",[4769,5629,5630,5636,5642],{},[3984,5631,5632,5635],{},[3321,5633,5634],{},"Redis Pub\u002FSub"," — найпоширеніший підхід. SignalR у .NET має вбудовану підтримку Redis Backplane. При нативному підході — реалізується вручну.",[3984,5637,5638,5641],{},[3321,5639,5640],{},"Sticky Sessions (IP Affinity)"," — балансувальник завжди направляє одного клієнта до одного сервера. Простіше, але погано для failover: якщо Server-1 впаде, всі його клієнти з'єднання розірвуться.",[3984,5643,5644,5647],{},[3321,5645,5646],{},"Message Queue (RabbitMQ, Kafka)"," — повноцінна асинхронна черга повідомлень для high-throughput систем.",[3374,5649,5651],{"id":5650},"обмеження-одночасних-зєднань","Обмеження одночасних з'єднань",[3317,5653,5654,5655,5658,5659,5662],{},"Кожне WebSocket-з'єднання — це відкритий файловий дескриптор та виділений буфер пам'яті. На рівні ОС кількість одночасних відкритих з'єднань обмежена ",[3389,5656,5657],{},"ulimit -n"," (за замовчуванням 1024 на Linux, змінюється до мільйонів). ASP.NET Core використовує асинхронну модель вводу-виводу через ",[3389,5660,5661],{},"libuv","\u002FIO completion ports, тому один потік може обслуговувати тисячі WebSocket-з'єднань.",[3317,5664,5665],{},"Практичні орієнтири для ASP.NET Core на сучасному сервері (8-16 ядер, 32 ГБ RAM):",[4769,5667,5668,5675,5685],{},[3984,5669,5670,5671,5674],{},"До ",[3321,5672,5673],{},"10 000"," одночасних з'єднань — стандартна конфігурація без налаштувань",[3984,5676,5670,5677,5680,5681,5684],{},[3321,5678,5679],{},"100 000"," — потрібне налаштування ",[3389,5682,5683],{},"ulimit",", Kestrel limits та moніторинг пам'яті",[3984,5686,5687,5688,5690],{},"Понад ",[3321,5689,5679],{}," — горизонтальне масштабування обов'язкове",[3364,5692],{},[3312,5694,5696],{"id":5695},"практичний-проєкт-real-time-чат-від-а-до-я","Практичний проєкт: Real-Time чат від А до Я",[3317,5698,5699,5700,5703],{},"Перейдемо від теорії до повноцінного застосунку. Ми побудуємо ",[3321,5701,5702],{},"консольний чат"," на нативних засобах .NET 10 — без SignalR, без сторонніх бібліотек WebSocket. Це дозволить зрозуміти, як WebSocket-протокол працює на найнижчому рівні, доступному через стандартний .NET API.",[3374,5705,5707],{"id":5706},"архітектура-проєкту","Архітектура проєкту",[3407,5709,5710],{},[3382,5711,5713],{"className":3411,"code":5712,"language":3413,"meta":3391,"style":3391},"@startuml\nskinparam style plain\nskinparam backgroundColor #ffffff\n\npackage \"ChatServer (HttpListener)\" {\n    class \"WebSocketHandler\" as handler {\n        +HandleAsync(WebSocket, username)\n        -ReceiveLoopAsync()\n        -SendToAllAsync(message)\n        -SendToUserAsync(target, message)\n    }\n\n    class \"ChatRoom\" as room {\n        -_connections: ConcurrentDictionary\n        +AddClient(username, ws)\n        +RemoveClient(username)\n        +BroadcastAsync(message)\n        +GetOnlineUsers(): List\u003Cstring>\n    }\n\n    class \"ChatMessage\" as msg {\n        +Type: string\n        +From: string\n        +To: string?\n        +Text: string\n        +Timestamp: DateTimeOffset\n    }\n\n    handler --> room : використовує\n    handler --> msg : серіалізує\u002Fдесеріалізує\n}\n\npackage \"ChatClient (Console .NET)\" {\n    class \"ChatClient\" as client {\n        +ConnectAsync(url, username)\n        +SendMessageAsync(text)\n        -ReceiveLoopAsync()\n    }\n}\n\nclient --> handler : ws:\u002F\u002Flocalhost:5000\u002Fws?user=Alice\nhandler --> client : WS Text Frames (JSON)\n\n@enduml\n",[3389,5714,5715,5719,5723,5727,5731,5736,5741,5746,5751,5756,5761,5766,5770,5775,5780,5785,5790,5795,5800,5804,5808,5813,5818,5823,5828,5833,5838,5842,5846,5851,5856,5860,5864,5869,5874,5880,5886,5891,5896,5901,5906,5912,5918,5923],{"__ignoreMap":3391},[3417,5716,5717],{"class":3419,"line":3420},[3417,5718,3423],{},[3417,5720,5721],{"class":3419,"line":3426},[3417,5722,3429],{},[3417,5724,5725],{"class":3419,"line":3432},[3417,5726,3435],{},[3417,5728,5729],{"class":3419,"line":3438},[3417,5730,3442],{"emptyLinePlaceholder":3441},[3417,5732,5733],{"class":3419,"line":3445},[3417,5734,5735],{},"package \"ChatServer (HttpListener)\" {\n",[3417,5737,5738],{"class":3419,"line":3451},[3417,5739,5740],{},"    class \"WebSocketHandler\" as handler {\n",[3417,5742,5743],{"class":3419,"line":3457},[3417,5744,5745],{},"        +HandleAsync(WebSocket, username)\n",[3417,5747,5748],{"class":3419,"line":3462},[3417,5749,5750],{},"        -ReceiveLoopAsync()\n",[3417,5752,5753],{"class":3419,"line":3468},[3417,5754,5755],{},"        -SendToAllAsync(message)\n",[3417,5757,5758],{"class":3419,"line":3474},[3417,5759,5760],{},"        -SendToUserAsync(target, message)\n",[3417,5762,5763],{"class":3419,"line":3480},[3417,5764,5765],{},"    }\n",[3417,5767,5768],{"class":3419,"line":3486},[3417,5769,3442],{"emptyLinePlaceholder":3441},[3417,5771,5772],{"class":3419,"line":3492},[3417,5773,5774],{},"    class \"ChatRoom\" as room {\n",[3417,5776,5777],{"class":3419,"line":3498},[3417,5778,5779],{},"        -_connections: ConcurrentDictionary\n",[3417,5781,5782],{"class":3419,"line":3504},[3417,5783,5784],{},"        +AddClient(username, ws)\n",[3417,5786,5787],{"class":3419,"line":3509},[3417,5788,5789],{},"        +RemoveClient(username)\n",[3417,5791,5792],{"class":3419,"line":3515},[3417,5793,5794],{},"        +BroadcastAsync(message)\n",[3417,5796,5797],{"class":3419,"line":3521},[3417,5798,5799],{},"        +GetOnlineUsers(): List\u003Cstring>\n",[3417,5801,5802],{"class":3419,"line":3527},[3417,5803,5765],{},[3417,5805,5806],{"class":3419,"line":3533},[3417,5807,3442],{"emptyLinePlaceholder":3441},[3417,5809,5810],{"class":3419,"line":3538},[3417,5811,5812],{},"    class \"ChatMessage\" as msg {\n",[3417,5814,5815],{"class":3419,"line":3543},[3417,5816,5817],{},"        +Type: string\n",[3417,5819,5820],{"class":3419,"line":3549},[3417,5821,5822],{},"        +From: string\n",[3417,5824,5825],{"class":3419,"line":3555},[3417,5826,5827],{},"        +To: string?\n",[3417,5829,5830],{"class":3419,"line":3560},[3417,5831,5832],{},"        +Text: string\n",[3417,5834,5835],{"class":3419,"line":3565},[3417,5836,5837],{},"        +Timestamp: DateTimeOffset\n",[3417,5839,5840],{"class":3419,"line":3571},[3417,5841,5765],{},[3417,5843,5844],{"class":3419,"line":3576},[3417,5845,3442],{"emptyLinePlaceholder":3441},[3417,5847,5848],{"class":3419,"line":3965},[3417,5849,5850],{},"    handler --> room : використовує\n",[3417,5852,5853],{"class":3419,"line":3970},[3417,5854,5855],{},"    handler --> msg : серіалізує\u002Fдесеріалізує\n",[3417,5857,5858],{"class":3419,"line":4410},[3417,5859,3869],{},[3417,5861,5862],{"class":3419,"line":4415},[3417,5863,3442],{"emptyLinePlaceholder":3441},[3417,5865,5866],{"class":3419,"line":4964},[3417,5867,5868],{},"package \"ChatClient (Console .NET)\" {\n",[3417,5870,5871],{"class":3419,"line":4969},[3417,5872,5873],{},"    class \"ChatClient\" as client {\n",[3417,5875,5877],{"class":3419,"line":5876},35,[3417,5878,5879],{},"        +ConnectAsync(url, username)\n",[3417,5881,5883],{"class":3419,"line":5882},36,[3417,5884,5885],{},"        +SendMessageAsync(text)\n",[3417,5887,5889],{"class":3419,"line":5888},37,[3417,5890,5750],{},[3417,5892,5894],{"class":3419,"line":5893},38,[3417,5895,5765],{},[3417,5897,5899],{"class":3419,"line":5898},39,[3417,5900,3869],{},[3417,5902,5904],{"class":3419,"line":5903},40,[3417,5905,3442],{"emptyLinePlaceholder":3441},[3417,5907,5909],{"class":3419,"line":5908},41,[3417,5910,5911],{},"client --> handler : ws:\u002F\u002Flocalhost:5000\u002Fws?user=Alice\n",[3417,5913,5915],{"class":3419,"line":5914},42,[3417,5916,5917],{},"handler --> client : WS Text Frames (JSON)\n",[3417,5919,5921],{"class":3419,"line":5920},43,[3417,5922,3442],{"emptyLinePlaceholder":3441},[3417,5924,5926],{"class":3419,"line":5925},44,[3417,5927,3579],{},[3317,5929,5930],{},"Протокол обміну повідомленнями (JSON поверх Text WebSocket frames):",[3624,5932,5933,5945],{},[3627,5934,5935],{},[3630,5936,5937,5940,5942],{},[3633,5938,5939],{},"Тип",[3633,5941,3660],{},[3633,5943,5944],{},"Структура",[3651,5946,5947,5962,5977,5991,6006],{},[3630,5948,5949,5954,5957],{},[3656,5950,5951],{},[3389,5952,5953],{},"chat",[3656,5955,5956],{},"обидва",[3656,5958,5959],{},[3389,5960,5961],{},"{type, from, text, timestamp}",[3630,5963,5964,5969,5972],{},[3656,5965,5966],{},[3389,5967,5968],{},"private",[3656,5970,5971],{},"Client→Server",[3656,5973,5974],{},[3389,5975,5976],{},"{type, to, text}",[3630,5978,5979,5983,5986],{},[3656,5980,5981],{},[3389,5982,5968],{},[3656,5984,5985],{},"Server→Client",[3656,5987,5988],{},[3389,5989,5990],{},"{type, from, to, text, timestamp}",[3630,5992,5993,5998,6000],{},[3656,5994,5995],{},[3389,5996,5997],{},"system",[3656,5999,5985],{},[3656,6001,6002,6005],{},[3389,6003,6004],{},"{type, text}"," — сповіщення про вхід\u002Fвихід",[3630,6007,6008,6013,6015],{},[3656,6009,6010],{},[3389,6011,6012],{},"users",[3656,6014,5985],{},[3656,6016,6017,6020],{},[3389,6018,6019],{},"{type, users: [...]}"," — список онлайн",[3364,6022],{},[3374,6024,6026],{"id":6025},"серверна-частина-chatserver","Серверна частина: ChatServer",[3317,6028,6029],{},[3321,6030,6031],{},"Ініціалізація проєктів:",[3382,6033,6037],{"className":6034,"code":6035,"language":6036,"meta":3391,"style":3391},"language-bash shiki shiki-themes light-plus dark-plus dark-plus","dotnet new sln -n ChatDemo\ndotnet new console -n ChatServer\ndotnet new console -n ChatClient\ndotnet sln add ChatServer ChatClient\n","bash",[3389,6038,6039,6057,6071,6084],{"__ignoreMap":3391},[3417,6040,6041,6045,6048,6051,6054],{"class":3419,"line":3420},[3417,6042,6044],{"class":6043},"s8Opu","dotnet",[3417,6046,6047],{"class":4060}," new",[3417,6049,6050],{"class":4060}," sln",[3417,6052,6053],{"class":4041}," -n",[3417,6055,6056],{"class":4060}," ChatDemo\n",[3417,6058,6059,6061,6063,6066,6068],{"class":3419,"line":3426},[3417,6060,6044],{"class":6043},[3417,6062,6047],{"class":4060},[3417,6064,6065],{"class":4060}," console",[3417,6067,6053],{"class":4041},[3417,6069,6070],{"class":4060}," ChatServer\n",[3417,6072,6073,6075,6077,6079,6081],{"class":3419,"line":3432},[3417,6074,6044],{"class":6043},[3417,6076,6047],{"class":4060},[3417,6078,6065],{"class":4060},[3417,6080,6053],{"class":4041},[3417,6082,6083],{"class":4060}," ChatClient\n",[3417,6085,6086,6088,6090,6093,6096],{"class":3419,"line":3438},[3417,6087,6044],{"class":6043},[3417,6089,6050],{"class":4060},[3417,6091,6092],{"class":4060}," add",[3417,6094,6095],{"class":4060}," ChatServer",[3417,6097,6083],{"class":4060},[3317,6099,6100,6103,6104,3593],{},[3321,6101,6102],{},"Модель повідомлення"," — ",[3389,6105,6106],{},"ChatServer\u002FModels\u002FChatMessage.cs",[3382,6108,6113],{"className":6109,"code":6110,"language":6111,"meta":6112,"style":3391},"language-csharp shiki shiki-themes light-plus dark-plus dark-plus","namespace ChatServer.Models;\n\npublic record ChatMessage\n{\n    public required string Type { get; init; }   \u002F\u002F \"chat\" | \"private\" | \"system\" | \"users\"\n    public string? From { get; init; }\n    public string? To { get; init; }\n    public string? Text { get; init; }\n    public List\u003Cstring>? Users { get; init; }\n    public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;\n}\n","csharp","showLineNumbers",[3389,6114,6115,6131,6135,6146,6151,6185,6208,6229,6249,6278,6309],{"__ignoreMap":3391},[3417,6116,6117,6120,6123,6125,6128],{"class":3419,"line":3420},[3417,6118,6119],{"class":4041},"namespace",[3417,6121,6095],{"class":6122},"sN1BT",[3417,6124,4253],{"class":4037},[3417,6126,6127],{"class":6122},"Models",[3417,6129,6130],{"class":4037},";\n",[3417,6132,6133],{"class":3419,"line":3426},[3417,6134,3442],{"emptyLinePlaceholder":3441},[3417,6136,6137,6140,6143],{"class":3419,"line":3432},[3417,6138,6139],{"class":4041},"public",[3417,6141,6142],{"class":4041}," record",[3417,6144,6145],{"class":6122}," ChatMessage\n",[3417,6147,6148],{"class":3419,"line":3438},[3417,6149,6150],{"class":4037},"{\n",[3417,6152,6153,6156,6159,6162,6166,6169,6172,6175,6178,6181],{"class":3419,"line":3445},[3417,6154,6155],{"class":4041},"    public",[3417,6157,6158],{"class":4041}," required",[3417,6160,6161],{"class":4041}," string",[3417,6163,6165],{"class":6164},"siwwj"," Type",[3417,6167,6168],{"class":4037}," { ",[3417,6170,6171],{"class":4041},"get",[3417,6173,6174],{"class":4037},"; ",[3417,6176,6177],{"class":4041},"init",[3417,6179,6180],{"class":4037},"; }   ",[3417,6182,6184],{"class":6183},"spJ8K","\u002F\u002F \"chat\" | \"private\" | \"system\" | \"users\"\n",[3417,6186,6187,6189,6191,6194,6197,6199,6201,6203,6205],{"class":3419,"line":3451},[3417,6188,6155],{"class":4041},[3417,6190,6161],{"class":4041},[3417,6192,6193],{"class":4037},"? ",[3417,6195,6196],{"class":6164},"From",[3417,6198,6168],{"class":4037},[3417,6200,6171],{"class":4041},[3417,6202,6174],{"class":4037},[3417,6204,6177],{"class":4041},[3417,6206,6207],{"class":4037},"; }\n",[3417,6209,6210,6212,6214,6216,6219,6221,6223,6225,6227],{"class":3419,"line":3457},[3417,6211,6155],{"class":4041},[3417,6213,6161],{"class":4041},[3417,6215,6193],{"class":4037},[3417,6217,6218],{"class":6164},"To",[3417,6220,6168],{"class":4037},[3417,6222,6171],{"class":4041},[3417,6224,6174],{"class":4037},[3417,6226,6177],{"class":4041},[3417,6228,6207],{"class":4037},[3417,6230,6231,6233,6235,6237,6239,6241,6243,6245,6247],{"class":3419,"line":3462},[3417,6232,6155],{"class":4041},[3417,6234,6161],{"class":4041},[3417,6236,6193],{"class":4037},[3417,6238,4600],{"class":6164},[3417,6240,6168],{"class":4037},[3417,6242,6171],{"class":4041},[3417,6244,6174],{"class":4037},[3417,6246,6177],{"class":4041},[3417,6248,6207],{"class":4037},[3417,6250,6251,6253,6256,6259,6262,6265,6268,6270,6272,6274,6276],{"class":3419,"line":3468},[3417,6252,6155],{"class":4041},[3417,6254,6255],{"class":6122}," List",[3417,6257,6258],{"class":4037},"\u003C",[3417,6260,6261],{"class":4041},"string",[3417,6263,6264],{"class":4037},">? ",[3417,6266,6267],{"class":6164},"Users",[3417,6269,6168],{"class":4037},[3417,6271,6171],{"class":4041},[3417,6273,6174],{"class":4037},[3417,6275,6177],{"class":4041},[3417,6277,6207],{"class":4037},[3417,6279,6280,6282,6285,6288,6290,6292,6294,6296,6299,6302,6304,6307],{"class":3419,"line":3474},[3417,6281,6155],{"class":4041},[3417,6283,6284],{"class":6122}," DateTimeOffset",[3417,6286,6287],{"class":6164}," Timestamp",[3417,6289,6168],{"class":4037},[3417,6291,6171],{"class":4041},[3417,6293,6174],{"class":4037},[3417,6295,6177],{"class":4041},[3417,6297,6298],{"class":4037},"; } = ",[3417,6300,6301],{"class":6164},"DateTimeOffset",[3417,6303,4253],{"class":4037},[3417,6305,6306],{"class":6164},"UtcNow",[3417,6308,6130],{"class":4037},[3417,6310,6311],{"class":3419,"line":3480},[3417,6312,3869],{"class":4037},[3317,6314,6315,6103,6318,3593],{},[3321,6316,6317],{},"ChatRoom — управління з'єднаннями",[3389,6319,6320],{},"ChatServer\u002FServices\u002FChatRoom.cs",[3382,6322,6324],{"className":6109,"code":6323,"language":6111,"meta":6112,"style":3391},"using System.Collections.Concurrent;\nusing System.Net.WebSockets;\nusing System.Text;\nusing System.Text.Json;\nusing ChatServer.Models;\n\nnamespace ChatServer.Services;\n\npublic class ChatRoom\n{\n    private readonly ConcurrentDictionary\u003Cstring, WebSocket> _connections = new();\n    private static readonly JsonSerializerOptions JsonOptions = new()\n    {\n        PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n    };\n\n    public bool TryAddClient(string username, WebSocket socket)\n        => _connections.TryAdd(username, socket);\n\n    public void RemoveClient(string username)\n        => _connections.TryRemove(username, out _);\n\n    public IReadOnlyList\u003Cstring> GetOnlineUsers()\n        => [.. _connections.Keys];\n\n    public async Task BroadcastAsync(ChatMessage message, string? excludeUsername = null)\n    {\n        byte[] bytes = Encoding.UTF8.GetBytes(\n            JsonSerializer.Serialize(message, JsonOptions));\n        ArraySegment\u003Cbyte> segment = new(bytes);\n\n        \u002F\u002F Task.WhenAll — паралельне відправлення всім клієнтам одночасно\n        var tasks = _connections\n            .Where(kv => kv.Key != excludeUsername\n                      && kv.Value.State == WebSocketState.Open)\n            .Select(kv => SafeSendAsync(kv.Value, segment));\n\n        await Task.WhenAll(tasks);\n    }\n\n    public async Task\u003Cbool> SendToUserAsync(string username, ChatMessage message)\n    {\n        if (!_connections.TryGetValue(username, out WebSocket? socket)\n            || socket.State != WebSocketState.Open)\n            return false;\n\n        byte[] bytes = Encoding.UTF8.GetBytes(\n            JsonSerializer.Serialize(message, JsonOptions));\n        await SafeSendAsync(socket, new ArraySegment\u003Cbyte>(bytes));\n        return true;\n    }\n\n    \u002F\u002F WebSocket.SendAsync не є thread-safe — використовуйте SemaphoreSlim\n    \u002F\u002F у продакшні для захисту від concurrent writes до одного сокету\n    private static async Task SafeSendAsync(WebSocket socket, ArraySegment\u003Cbyte> data)\n    {\n        try\n        {\n            await socket.SendAsync(data, WebSocketMessageType.Text,\n                endOfMessage: true, CancellationToken.None);\n        }\n        catch (WebSocketException) { \u002F* з'єднання вже закрито *\u002F }\n    }\n}\n",[3389,6325,6326,6346,6364,6376,6393,6405,6409,6422,6426,6436,6440,6474,6496,6501,6516,6521,6525,6553,6578,6582,6600,6625,6629,6647,6662,6666,6703,6707,6736,6759,6784,6788,6793,6806,6835,6865,6895,6899,6918,6922,6926,6958,6962,6994,7015,7026,7031,7054,7073,7103,7114,7119,7124,7130,7136,7171,7176,7182,7188,7217,7240,7246,7267,7272],{"__ignoreMap":3391},[3417,6327,6328,6331,6334,6336,6339,6341,6344],{"class":3419,"line":3420},[3417,6329,6330],{"class":4033},"using",[3417,6332,6333],{"class":6122}," System",[3417,6335,4253],{"class":4037},[3417,6337,6338],{"class":6122},"Collections",[3417,6340,4253],{"class":4037},[3417,6342,6343],{"class":6122},"Concurrent",[3417,6345,6130],{"class":4037},[3417,6347,6348,6350,6352,6354,6357,6359,6362],{"class":3419,"line":3426},[3417,6349,6330],{"class":4033},[3417,6351,6333],{"class":6122},[3417,6353,4253],{"class":4037},[3417,6355,6356],{"class":6122},"Net",[3417,6358,4253],{"class":4037},[3417,6360,6361],{"class":6122},"WebSockets",[3417,6363,6130],{"class":4037},[3417,6365,6366,6368,6370,6372,6374],{"class":3419,"line":3432},[3417,6367,6330],{"class":4033},[3417,6369,6333],{"class":6122},[3417,6371,4253],{"class":4037},[3417,6373,4600],{"class":6122},[3417,6375,6130],{"class":4037},[3417,6377,6378,6380,6382,6384,6386,6388,6391],{"class":3419,"line":3438},[3417,6379,6330],{"class":4033},[3417,6381,6333],{"class":6122},[3417,6383,4253],{"class":4037},[3417,6385,4600],{"class":6122},[3417,6387,4253],{"class":4037},[3417,6389,6390],{"class":6122},"Json",[3417,6392,6130],{"class":4037},[3417,6394,6395,6397,6399,6401,6403],{"class":3419,"line":3445},[3417,6396,6330],{"class":4033},[3417,6398,6095],{"class":6122},[3417,6400,4253],{"class":4037},[3417,6402,6127],{"class":6122},[3417,6404,6130],{"class":4037},[3417,6406,6407],{"class":3419,"line":3451},[3417,6408,3442],{"emptyLinePlaceholder":3441},[3417,6410,6411,6413,6415,6417,6420],{"class":3419,"line":3457},[3417,6412,6119],{"class":4041},[3417,6414,6095],{"class":6122},[3417,6416,4253],{"class":4037},[3417,6418,6419],{"class":6122},"Services",[3417,6421,6130],{"class":4037},[3417,6423,6424],{"class":3419,"line":3462},[3417,6425,3442],{"emptyLinePlaceholder":3441},[3417,6427,6428,6430,6433],{"class":3419,"line":3468},[3417,6429,6139],{"class":4041},[3417,6431,6432],{"class":4041}," class",[3417,6434,6435],{"class":6122}," ChatRoom\n",[3417,6437,6438],{"class":3419,"line":3474},[3417,6439,6150],{"class":4037},[3417,6441,6442,6445,6448,6451,6453,6455,6457,6459,6462,6465,6468,6471],{"class":3419,"line":3480},[3417,6443,6444],{"class":4041},"    private",[3417,6446,6447],{"class":4041}," readonly",[3417,6449,6450],{"class":6122}," ConcurrentDictionary",[3417,6452,6258],{"class":4037},[3417,6454,6261],{"class":4041},[3417,6456,4246],{"class":4037},[3417,6458,3649],{"class":6122},[3417,6460,6461],{"class":4037},"> ",[3417,6463,6464],{"class":6164},"_connections",[3417,6466,6467],{"class":4037}," = ",[3417,6469,6470],{"class":4041},"new",[3417,6472,6473],{"class":4037},"();\n",[3417,6475,6476,6478,6481,6483,6486,6489,6491,6493],{"class":3419,"line":3486},[3417,6477,6444],{"class":4041},[3417,6479,6480],{"class":4041}," static",[3417,6482,6447],{"class":4041},[3417,6484,6485],{"class":6122}," JsonSerializerOptions",[3417,6487,6488],{"class":6164}," JsonOptions",[3417,6490,6467],{"class":4037},[3417,6492,6470],{"class":4041},[3417,6494,6495],{"class":4037},"()\n",[3417,6497,6498],{"class":3419,"line":3492},[3417,6499,6500],{"class":4037},"    {\n",[3417,6502,6503,6506,6508,6511,6513],{"class":3419,"line":3498},[3417,6504,6505],{"class":6164},"        PropertyNamingPolicy",[3417,6507,6467],{"class":4037},[3417,6509,6510],{"class":6164},"JsonNamingPolicy",[3417,6512,4253],{"class":4037},[3417,6514,6515],{"class":6164},"CamelCase\n",[3417,6517,6518],{"class":3419,"line":3504},[3417,6519,6520],{"class":4037},"    };\n",[3417,6522,6523],{"class":3419,"line":3509},[3417,6524,3442],{"emptyLinePlaceholder":3441},[3417,6526,6527,6529,6532,6535,6538,6540,6543,6545,6547,6550],{"class":3419,"line":3515},[3417,6528,6155],{"class":4041},[3417,6530,6531],{"class":4041}," bool",[3417,6533,6534],{"class":6043}," TryAddClient",[3417,6536,6537],{"class":4037},"(",[3417,6539,6261],{"class":4041},[3417,6541,6542],{"class":6164}," username",[3417,6544,4246],{"class":4037},[3417,6546,3649],{"class":6122},[3417,6548,6549],{"class":6164}," socket",[3417,6551,6552],{"class":4037},")\n",[3417,6554,6555,6558,6560,6562,6565,6567,6570,6572,6575],{"class":3419,"line":3521},[3417,6556,6557],{"class":4037},"        => ",[3417,6559,6464],{"class":6164},[3417,6561,4253],{"class":4037},[3417,6563,6564],{"class":6043},"TryAdd",[3417,6566,6537],{"class":4037},[3417,6568,6569],{"class":6164},"username",[3417,6571,4246],{"class":4037},[3417,6573,6574],{"class":6164},"socket",[3417,6576,6577],{"class":4037},");\n",[3417,6579,6580],{"class":3419,"line":3527},[3417,6581,3442],{"emptyLinePlaceholder":3441},[3417,6583,6584,6586,6589,6592,6594,6596,6598],{"class":3419,"line":3533},[3417,6585,6155],{"class":4041},[3417,6587,6588],{"class":4041}," void",[3417,6590,6591],{"class":6043}," RemoveClient",[3417,6593,6537],{"class":4037},[3417,6595,6261],{"class":4041},[3417,6597,6542],{"class":6164},[3417,6599,6552],{"class":4037},[3417,6601,6602,6604,6606,6608,6611,6613,6615,6617,6620,6623],{"class":3419,"line":3538},[3417,6603,6557],{"class":4037},[3417,6605,6464],{"class":6164},[3417,6607,4253],{"class":4037},[3417,6609,6610],{"class":6043},"TryRemove",[3417,6612,6537],{"class":4037},[3417,6614,6569],{"class":6164},[3417,6616,4246],{"class":4037},[3417,6618,6619],{"class":4041},"out",[3417,6621,6622],{"class":6164}," _",[3417,6624,6577],{"class":4037},[3417,6626,6627],{"class":3419,"line":3543},[3417,6628,3442],{"emptyLinePlaceholder":3441},[3417,6630,6631,6633,6636,6638,6640,6642,6645],{"class":3419,"line":3549},[3417,6632,6155],{"class":4041},[3417,6634,6635],{"class":6122}," IReadOnlyList",[3417,6637,6258],{"class":4037},[3417,6639,6261],{"class":4041},[3417,6641,6461],{"class":4037},[3417,6643,6644],{"class":6043},"GetOnlineUsers",[3417,6646,6495],{"class":4037},[3417,6648,6649,6652,6654,6656,6659],{"class":3419,"line":3555},[3417,6650,6651],{"class":4037},"        => [.. ",[3417,6653,6464],{"class":6164},[3417,6655,4253],{"class":4037},[3417,6657,6658],{"class":6164},"Keys",[3417,6660,6661],{"class":4037},"];\n",[3417,6663,6664],{"class":3419,"line":3560},[3417,6665,3442],{"emptyLinePlaceholder":3441},[3417,6667,6668,6670,6673,6676,6679,6681,6684,6687,6689,6691,6693,6696,6698,6701],{"class":3419,"line":3565},[3417,6669,6155],{"class":4041},[3417,6671,6672],{"class":4041}," async",[3417,6674,6675],{"class":6122}," Task",[3417,6677,6678],{"class":6043}," BroadcastAsync",[3417,6680,6537],{"class":4037},[3417,6682,6683],{"class":6122},"ChatMessage",[3417,6685,6686],{"class":6164}," message",[3417,6688,4246],{"class":4037},[3417,6690,6261],{"class":4041},[3417,6692,6193],{"class":4037},[3417,6694,6695],{"class":6164},"excludeUsername",[3417,6697,6467],{"class":4037},[3417,6699,6700],{"class":4041},"null",[3417,6702,6552],{"class":4037},[3417,6704,6705],{"class":3419,"line":3571},[3417,6706,6500],{"class":4037},[3417,6708,6709,6712,6715,6718,6720,6723,6725,6728,6730,6733],{"class":3419,"line":3576},[3417,6710,6711],{"class":4041},"        byte",[3417,6713,6714],{"class":4037},"[] ",[3417,6716,6717],{"class":6164},"bytes",[3417,6719,6467],{"class":4037},[3417,6721,6722],{"class":6164},"Encoding",[3417,6724,4253],{"class":4037},[3417,6726,6727],{"class":6164},"UTF8",[3417,6729,4253],{"class":4037},[3417,6731,6732],{"class":6043},"GetBytes",[3417,6734,6735],{"class":4037},"(\n",[3417,6737,6738,6741,6743,6746,6748,6751,6753,6756],{"class":3419,"line":3965},[3417,6739,6740],{"class":6164},"            JsonSerializer",[3417,6742,4253],{"class":4037},[3417,6744,6745],{"class":6043},"Serialize",[3417,6747,6537],{"class":4037},[3417,6749,6750],{"class":6164},"message",[3417,6752,4246],{"class":4037},[3417,6754,6755],{"class":6164},"JsonOptions",[3417,6757,6758],{"class":4037},"));\n",[3417,6760,6761,6764,6766,6769,6771,6774,6776,6778,6780,6782],{"class":3419,"line":3970},[3417,6762,6763],{"class":6122},"        ArraySegment",[3417,6765,6258],{"class":4037},[3417,6767,6768],{"class":4041},"byte",[3417,6770,6461],{"class":4037},[3417,6772,6773],{"class":6164},"segment",[3417,6775,6467],{"class":4037},[3417,6777,6470],{"class":4041},[3417,6779,6537],{"class":4037},[3417,6781,6717],{"class":6164},[3417,6783,6577],{"class":4037},[3417,6785,6786],{"class":3419,"line":4410},[3417,6787,3442],{"emptyLinePlaceholder":3441},[3417,6789,6790],{"class":3419,"line":4415},[3417,6791,6792],{"class":6183},"        \u002F\u002F Task.WhenAll — паралельне відправлення всім клієнтам одночасно\n",[3417,6794,6795,6798,6801,6803],{"class":3419,"line":4964},[3417,6796,6797],{"class":4041},"        var",[3417,6799,6800],{"class":6164}," tasks",[3417,6802,6467],{"class":4037},[3417,6804,6805],{"class":6164},"_connections\n",[3417,6807,6808,6811,6814,6816,6819,6822,6824,6826,6829,6832],{"class":3419,"line":4969},[3417,6809,6810],{"class":4037},"            .",[3417,6812,6813],{"class":6043},"Where",[3417,6815,6537],{"class":4037},[3417,6817,6818],{"class":6164},"kv",[3417,6820,6821],{"class":4037}," => ",[3417,6823,6818],{"class":6164},[3417,6825,4253],{"class":4037},[3417,6827,6828],{"class":6164},"Key",[3417,6830,6831],{"class":4037}," != ",[3417,6833,6834],{"class":6164},"excludeUsername\n",[3417,6836,6837,6840,6842,6844,6847,6849,6852,6855,6858,6860,6863],{"class":3419,"line":5876},[3417,6838,6839],{"class":4037},"                      && ",[3417,6841,6818],{"class":6164},[3417,6843,4253],{"class":4037},[3417,6845,6846],{"class":6164},"Value",[3417,6848,4253],{"class":4037},[3417,6850,6851],{"class":6164},"State",[3417,6853,6854],{"class":4037}," == ",[3417,6856,6857],{"class":6164},"WebSocketState",[3417,6859,4253],{"class":4037},[3417,6861,6862],{"class":6164},"Open",[3417,6864,6552],{"class":4037},[3417,6866,6867,6869,6872,6874,6876,6878,6881,6883,6885,6887,6889,6891,6893],{"class":3419,"line":5882},[3417,6868,6810],{"class":4037},[3417,6870,6871],{"class":6043},"Select",[3417,6873,6537],{"class":4037},[3417,6875,6818],{"class":6164},[3417,6877,6821],{"class":4037},[3417,6879,6880],{"class":6043},"SafeSendAsync",[3417,6882,6537],{"class":4037},[3417,6884,6818],{"class":6164},[3417,6886,4253],{"class":4037},[3417,6888,6846],{"class":6164},[3417,6890,4246],{"class":4037},[3417,6892,6773],{"class":6164},[3417,6894,6758],{"class":4037},[3417,6896,6897],{"class":3419,"line":5888},[3417,6898,3442],{"emptyLinePlaceholder":3441},[3417,6900,6901,6904,6906,6908,6911,6913,6916],{"class":3419,"line":5893},[3417,6902,6903],{"class":4041},"        await",[3417,6905,6675],{"class":6164},[3417,6907,4253],{"class":4037},[3417,6909,6910],{"class":6043},"WhenAll",[3417,6912,6537],{"class":4037},[3417,6914,6915],{"class":6164},"tasks",[3417,6917,6577],{"class":4037},[3417,6919,6920],{"class":3419,"line":5898},[3417,6921,5765],{"class":4037},[3417,6923,6924],{"class":3419,"line":5903},[3417,6925,3442],{"emptyLinePlaceholder":3441},[3417,6927,6928,6930,6932,6934,6936,6939,6941,6944,6946,6948,6950,6952,6954,6956],{"class":3419,"line":5908},[3417,6929,6155],{"class":4041},[3417,6931,6672],{"class":4041},[3417,6933,6675],{"class":6122},[3417,6935,6258],{"class":4037},[3417,6937,6938],{"class":4041},"bool",[3417,6940,6461],{"class":4037},[3417,6942,6943],{"class":6043},"SendToUserAsync",[3417,6945,6537],{"class":4037},[3417,6947,6261],{"class":4041},[3417,6949,6542],{"class":6164},[3417,6951,4246],{"class":4037},[3417,6953,6683],{"class":6122},[3417,6955,6686],{"class":6164},[3417,6957,6552],{"class":4037},[3417,6959,6960],{"class":3419,"line":5914},[3417,6961,6500],{"class":4037},[3417,6963,6964,6967,6970,6972,6974,6977,6979,6981,6983,6985,6988,6990,6992],{"class":3419,"line":5920},[3417,6965,6966],{"class":4033},"        if",[3417,6968,6969],{"class":4037}," (!",[3417,6971,6464],{"class":6164},[3417,6973,4253],{"class":4037},[3417,6975,6976],{"class":6043},"TryGetValue",[3417,6978,6537],{"class":4037},[3417,6980,6569],{"class":6164},[3417,6982,4246],{"class":4037},[3417,6984,6619],{"class":4041},[3417,6986,6987],{"class":6122}," WebSocket",[3417,6989,6193],{"class":4037},[3417,6991,6574],{"class":6164},[3417,6993,6552],{"class":4037},[3417,6995,6996,6999,7001,7003,7005,7007,7009,7011,7013],{"class":3419,"line":5925},[3417,6997,6998],{"class":4037},"            || ",[3417,7000,6574],{"class":6164},[3417,7002,4253],{"class":4037},[3417,7004,6851],{"class":6164},[3417,7006,6831],{"class":4037},[3417,7008,6857],{"class":6164},[3417,7010,4253],{"class":4037},[3417,7012,6862],{"class":6164},[3417,7014,6552],{"class":4037},[3417,7016,7018,7021,7024],{"class":3419,"line":7017},45,[3417,7019,7020],{"class":4033},"            return",[3417,7022,7023],{"class":4041}," false",[3417,7025,6130],{"class":4037},[3417,7027,7029],{"class":3419,"line":7028},46,[3417,7030,3442],{"emptyLinePlaceholder":3441},[3417,7032,7034,7036,7038,7040,7042,7044,7046,7048,7050,7052],{"class":3419,"line":7033},47,[3417,7035,6711],{"class":4041},[3417,7037,6714],{"class":4037},[3417,7039,6717],{"class":6164},[3417,7041,6467],{"class":4037},[3417,7043,6722],{"class":6164},[3417,7045,4253],{"class":4037},[3417,7047,6727],{"class":6164},[3417,7049,4253],{"class":4037},[3417,7051,6732],{"class":6043},[3417,7053,6735],{"class":4037},[3417,7055,7057,7059,7061,7063,7065,7067,7069,7071],{"class":3419,"line":7056},48,[3417,7058,6740],{"class":6164},[3417,7060,4253],{"class":4037},[3417,7062,6745],{"class":6043},[3417,7064,6537],{"class":4037},[3417,7066,6750],{"class":6164},[3417,7068,4246],{"class":4037},[3417,7070,6755],{"class":6164},[3417,7072,6758],{"class":4037},[3417,7074,7076,7078,7081,7083,7085,7087,7089,7092,7094,7096,7099,7101],{"class":3419,"line":7075},49,[3417,7077,6903],{"class":4041},[3417,7079,7080],{"class":6043}," SafeSendAsync",[3417,7082,6537],{"class":4037},[3417,7084,6574],{"class":6164},[3417,7086,4246],{"class":4037},[3417,7088,6470],{"class":4041},[3417,7090,7091],{"class":6122}," ArraySegment",[3417,7093,6258],{"class":4037},[3417,7095,6768],{"class":4041},[3417,7097,7098],{"class":4037},">(",[3417,7100,6717],{"class":6164},[3417,7102,6758],{"class":4037},[3417,7104,7106,7109,7112],{"class":3419,"line":7105},50,[3417,7107,7108],{"class":4033},"        return",[3417,7110,7111],{"class":4041}," true",[3417,7113,6130],{"class":4037},[3417,7115,7117],{"class":3419,"line":7116},51,[3417,7118,5765],{"class":4037},[3417,7120,7122],{"class":3419,"line":7121},52,[3417,7123,3442],{"emptyLinePlaceholder":3441},[3417,7125,7127],{"class":3419,"line":7126},53,[3417,7128,7129],{"class":6183},"    \u002F\u002F WebSocket.SendAsync не є thread-safe — використовуйте SemaphoreSlim\n",[3417,7131,7133],{"class":3419,"line":7132},54,[3417,7134,7135],{"class":6183},"    \u002F\u002F у продакшні для захисту від concurrent writes до одного сокету\n",[3417,7137,7139,7141,7143,7145,7147,7149,7151,7153,7155,7157,7160,7162,7164,7166,7169],{"class":3419,"line":7138},55,[3417,7140,6444],{"class":4041},[3417,7142,6480],{"class":4041},[3417,7144,6672],{"class":4041},[3417,7146,6675],{"class":6122},[3417,7148,7080],{"class":6043},[3417,7150,6537],{"class":4037},[3417,7152,3649],{"class":6122},[3417,7154,6549],{"class":6164},[3417,7156,4246],{"class":4037},[3417,7158,7159],{"class":6122},"ArraySegment",[3417,7161,6258],{"class":4037},[3417,7163,6768],{"class":4041},[3417,7165,6461],{"class":4037},[3417,7167,7168],{"class":6164},"data",[3417,7170,6552],{"class":4037},[3417,7172,7174],{"class":3419,"line":7173},56,[3417,7175,6500],{"class":4037},[3417,7177,7179],{"class":3419,"line":7178},57,[3417,7180,7181],{"class":4033},"        try\n",[3417,7183,7185],{"class":3419,"line":7184},58,[3417,7186,7187],{"class":4037},"        {\n",[3417,7189,7191,7194,7196,7198,7201,7203,7205,7207,7210,7212,7214],{"class":3419,"line":7190},59,[3417,7192,7193],{"class":4041},"            await",[3417,7195,6549],{"class":6164},[3417,7197,4253],{"class":4037},[3417,7199,7200],{"class":6043},"SendAsync",[3417,7202,6537],{"class":4037},[3417,7204,7168],{"class":6164},[3417,7206,4246],{"class":4037},[3417,7208,7209],{"class":6164},"WebSocketMessageType",[3417,7211,4253],{"class":4037},[3417,7213,4600],{"class":6164},[3417,7215,7216],{"class":4037},",\n",[3417,7218,7220,7223,7225,7228,7230,7233,7235,7238],{"class":3419,"line":7219},60,[3417,7221,7222],{"class":6164},"                endOfMessage",[3417,7224,5407],{"class":4037},[3417,7226,7227],{"class":4041},"true",[3417,7229,4246],{"class":4037},[3417,7231,7232],{"class":6164},"CancellationToken",[3417,7234,4253],{"class":4037},[3417,7236,7237],{"class":6164},"None",[3417,7239,6577],{"class":4037},[3417,7241,7243],{"class":3419,"line":7242},61,[3417,7244,7245],{"class":4037},"        }\n",[3417,7247,7249,7252,7255,7258,7261,7264],{"class":3419,"line":7248},62,[3417,7250,7251],{"class":4033},"        catch",[3417,7253,7254],{"class":4037}," (",[3417,7256,7257],{"class":6122},"WebSocketException",[3417,7259,7260],{"class":4037},") { ",[3417,7262,7263],{"class":6183},"\u002F* з'єднання вже закрито *\u002F",[3417,7265,7266],{"class":4037}," }\n",[3417,7268,7270],{"class":3419,"line":7269},63,[3417,7271,5765],{"class":4037},[3417,7273,7275],{"class":3419,"line":7274},64,[3417,7276,3869],{"class":4037},[3317,7278,7279,6103,7282,3593],{},[3321,7280,7281],{},"WebSocketHandler",[3389,7283,7284],{},"ChatServer\u002FHandlers\u002FWebSocketHandler.cs",[3382,7286,7288],{"className":6109,"code":7287,"language":6111,"meta":6112,"style":3391},"using System.Net.WebSockets;\nusing System.Text;\nusing System.Text.Json;\nusing ChatServer.Models;\nusing ChatServer.Services;\n\nnamespace ChatServer.Handlers;\n\npublic class WebSocketHandler(ChatRoom room)\n{\n    private static readonly JsonSerializerOptions JsonOptions = new()\n    {\n        PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n    };\n\n    public async Task HandleAsync(WebSocket ws, string username,\n        CancellationToken ct = default)\n    {\n        if (!room.TryAddClient(username, ws))\n        {\n            await ws.CloseAsync((WebSocketCloseStatus)4000,\n                $\"Username '{username}' is already taken\", ct);\n            return;\n        }\n\n        Console.WriteLine($\"[+] {username} підключився. Онлайн: {room.GetOnlineUsers().Count}\");\n\n        \u002F\u002F Сповіщаємо всіх про нового учасника\n        await room.BroadcastAsync(new ChatMessage\n        {\n            Type = \"system\",\n            Text = $\"🟢 {username} приєднався до чату\"\n        }, excludeUsername: username);\n\n        \u002F\u002F Новому клієнту — список онлайн\n        await room.SendToUserAsync(username, new ChatMessage\n        {\n            Type = \"users\",\n            Users = [.. room.GetOnlineUsers()]\n        });\n\n        try { await ReceiveLoopAsync(ws, username, ct); }\n        finally\n        {\n            room.RemoveClient(username);\n            Console.WriteLine($\"[-] {username} відключився\");\n            await room.BroadcastAsync(new ChatMessage\n            {\n                Type = \"system\",\n                Text = $\"🔴 {username} покинув чат\"\n            });\n        }\n    }\n\n    private async Task ReceiveLoopAsync(WebSocket ws, string username,\n        CancellationToken ct)\n    {\n        byte[] buffer = new byte[4096];\n\n        while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested)\n        {\n            using var ms = new MemoryStream();\n            WebSocketReceiveResult result;\n\n            \u002F\u002F Збираємо фрагментоване повідомлення в єдиний MemoryStream\n            do\n            {\n                result = await ws.ReceiveAsync(new ArraySegment\u003Cbyte>(buffer), ct);\n\n                if (result.MessageType == WebSocketMessageType.Close)\n                {\n                    await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, \"OK\", ct);\n                    return;\n                }\n\n                ms.Write(buffer, 0, result.Count);\n            } while (!result.EndOfMessage);\n\n            if (result.MessageType != WebSocketMessageType.Text) continue;\n\n            await ProcessMessageAsync(username,\n                Encoding.UTF8.GetString(ms.ToArray()));\n        }\n    }\n\n    private async Task ProcessMessageAsync(string from, string json)\n    {\n        ChatMessage? msg;\n        try { msg = JsonSerializer.Deserialize\u003CChatMessage>(json, JsonOptions); }\n        catch { return; }\n        if (msg is null) return;\n\n        switch (msg.Type)\n        {\n            case \"chat\":\n                await room.BroadcastAsync(new ChatMessage\n                {\n                    Type = \"chat\", From = from, Text = msg.Text\n                });\n                break;\n\n            case \"private\" when msg.To is not null:\n                var pm = new ChatMessage\n                {\n                    Type = \"private\", From = from, To = msg.To, Text = msg.Text\n                };\n                bool sent = await room.SendToUserAsync(msg.To, pm);\n                \u002F\u002F Відправляємо копію відправнику\n                await room.SendToUserAsync(from, sent ? pm : new ChatMessage\n                {\n                    Type = \"system\",\n                    Text = $\"⚠️ {msg.To} не в мережі\"\n                });\n                break;\n        }\n    }\n}\n",[3389,7289,7290,7306,7318,7334,7346,7358,7362,7375,7379,7398,7402,7420,7424,7436,7440,7444,7470,7485,7489,7515,7519,7543,7566,7572,7576,7580,7625,7629,7634,7651,7655,7667,7686,7699,7703,7708,7728,7732,7743,7760,7765,7769,7797,7802,7806,7822,7847,7863,7868,7879,7898,7903,7907,7911,7915,7939,7947,7951,7975,7979,8012,8016,8036,8046,8050,8056,8062,8067,8105,8110,8136,8142,8174,8182,8188,8193,8222,8242,8247,8277,8282,8296,8324,8329,8334,8339,8366,8371,8384,8417,8429,8450,8455,8472,8477,8489,8507,8512,8545,8551,8559,8564,8591,8606,8611,8653,8659,8693,8699,8731,8736,8747,8771,8776,8783,8788,8793],{"__ignoreMap":3391},[3417,7291,7292,7294,7296,7298,7300,7302,7304],{"class":3419,"line":3420},[3417,7293,6330],{"class":4033},[3417,7295,6333],{"class":6122},[3417,7297,4253],{"class":4037},[3417,7299,6356],{"class":6122},[3417,7301,4253],{"class":4037},[3417,7303,6361],{"class":6122},[3417,7305,6130],{"class":4037},[3417,7307,7308,7310,7312,7314,7316],{"class":3419,"line":3426},[3417,7309,6330],{"class":4033},[3417,7311,6333],{"class":6122},[3417,7313,4253],{"class":4037},[3417,7315,4600],{"class":6122},[3417,7317,6130],{"class":4037},[3417,7319,7320,7322,7324,7326,7328,7330,7332],{"class":3419,"line":3432},[3417,7321,6330],{"class":4033},[3417,7323,6333],{"class":6122},[3417,7325,4253],{"class":4037},[3417,7327,4600],{"class":6122},[3417,7329,4253],{"class":4037},[3417,7331,6390],{"class":6122},[3417,7333,6130],{"class":4037},[3417,7335,7336,7338,7340,7342,7344],{"class":3419,"line":3438},[3417,7337,6330],{"class":4033},[3417,7339,6095],{"class":6122},[3417,7341,4253],{"class":4037},[3417,7343,6127],{"class":6122},[3417,7345,6130],{"class":4037},[3417,7347,7348,7350,7352,7354,7356],{"class":3419,"line":3445},[3417,7349,6330],{"class":4033},[3417,7351,6095],{"class":6122},[3417,7353,4253],{"class":4037},[3417,7355,6419],{"class":6122},[3417,7357,6130],{"class":4037},[3417,7359,7360],{"class":3419,"line":3451},[3417,7361,3442],{"emptyLinePlaceholder":3441},[3417,7363,7364,7366,7368,7370,7373],{"class":3419,"line":3457},[3417,7365,6119],{"class":4041},[3417,7367,6095],{"class":6122},[3417,7369,4253],{"class":4037},[3417,7371,7372],{"class":6122},"Handlers",[3417,7374,6130],{"class":4037},[3417,7376,7377],{"class":3419,"line":3462},[3417,7378,3442],{"emptyLinePlaceholder":3441},[3417,7380,7381,7383,7385,7388,7390,7393,7396],{"class":3419,"line":3468},[3417,7382,6139],{"class":4041},[3417,7384,6432],{"class":4041},[3417,7386,7387],{"class":6122}," WebSocketHandler",[3417,7389,6537],{"class":4037},[3417,7391,7392],{"class":6122},"ChatRoom",[3417,7394,7395],{"class":6164}," room",[3417,7397,6552],{"class":4037},[3417,7399,7400],{"class":3419,"line":3474},[3417,7401,6150],{"class":4037},[3417,7403,7404,7406,7408,7410,7412,7414,7416,7418],{"class":3419,"line":3480},[3417,7405,6444],{"class":4041},[3417,7407,6480],{"class":4041},[3417,7409,6447],{"class":4041},[3417,7411,6485],{"class":6122},[3417,7413,6488],{"class":6164},[3417,7415,6467],{"class":4037},[3417,7417,6470],{"class":4041},[3417,7419,6495],{"class":4037},[3417,7421,7422],{"class":3419,"line":3486},[3417,7423,6500],{"class":4037},[3417,7425,7426,7428,7430,7432,7434],{"class":3419,"line":3492},[3417,7427,6505],{"class":6164},[3417,7429,6467],{"class":4037},[3417,7431,6510],{"class":6164},[3417,7433,4253],{"class":4037},[3417,7435,6515],{"class":6164},[3417,7437,7438],{"class":3419,"line":3498},[3417,7439,6520],{"class":4037},[3417,7441,7442],{"class":3419,"line":3504},[3417,7443,3442],{"emptyLinePlaceholder":3441},[3417,7445,7446,7448,7450,7452,7455,7457,7459,7462,7464,7466,7468],{"class":3419,"line":3509},[3417,7447,6155],{"class":4041},[3417,7449,6672],{"class":4041},[3417,7451,6675],{"class":6122},[3417,7453,7454],{"class":6043}," HandleAsync",[3417,7456,6537],{"class":4037},[3417,7458,3649],{"class":6122},[3417,7460,7461],{"class":6164}," ws",[3417,7463,4246],{"class":4037},[3417,7465,6261],{"class":4041},[3417,7467,6542],{"class":6164},[3417,7469,7216],{"class":4037},[3417,7471,7472,7475,7478,7480,7483],{"class":3419,"line":3515},[3417,7473,7474],{"class":6122},"        CancellationToken",[3417,7476,7477],{"class":6164}," ct",[3417,7479,6467],{"class":4037},[3417,7481,7482],{"class":4041},"default",[3417,7484,6552],{"class":4037},[3417,7486,7487],{"class":3419,"line":3521},[3417,7488,6500],{"class":4037},[3417,7490,7491,7493,7495,7498,7500,7503,7505,7507,7509,7512],{"class":3419,"line":3527},[3417,7492,6966],{"class":4033},[3417,7494,6969],{"class":4037},[3417,7496,7497],{"class":6164},"room",[3417,7499,4253],{"class":4037},[3417,7501,7502],{"class":6043},"TryAddClient",[3417,7504,6537],{"class":4037},[3417,7506,6569],{"class":6164},[3417,7508,4246],{"class":4037},[3417,7510,7511],{"class":6164},"ws",[3417,7513,7514],{"class":4037},"))\n",[3417,7516,7517],{"class":3419,"line":3533},[3417,7518,7187],{"class":4037},[3417,7520,7521,7523,7525,7527,7530,7533,7536,7538,7541],{"class":3419,"line":3538},[3417,7522,7193],{"class":4041},[3417,7524,7461],{"class":6164},[3417,7526,4253],{"class":4037},[3417,7528,7529],{"class":6043},"CloseAsync",[3417,7531,7532],{"class":4037},"((",[3417,7534,7535],{"class":6122},"WebSocketCloseStatus",[3417,7537,3996],{"class":4037},[3417,7539,7540],{"class":4048},"4000",[3417,7542,7216],{"class":4037},[3417,7544,7545,7548,7551,7553,7556,7559,7561,7564],{"class":3419,"line":3543},[3417,7546,7547],{"class":4060},"                $\"Username '",[3417,7549,5400],{"class":7550},"sD7JJ",[3417,7552,6569],{"class":6164},[3417,7554,7555],{"class":7550},"}",[3417,7557,7558],{"class":4060},"' is already taken\"",[3417,7560,4246],{"class":4037},[3417,7562,7563],{"class":6164},"ct",[3417,7565,6577],{"class":4037},[3417,7567,7568,7570],{"class":3419,"line":3549},[3417,7569,7020],{"class":4033},[3417,7571,6130],{"class":4037},[3417,7573,7574],{"class":3419,"line":3555},[3417,7575,7245],{"class":4037},[3417,7577,7578],{"class":3419,"line":3560},[3417,7579,3442],{"emptyLinePlaceholder":3441},[3417,7581,7582,7585,7587,7590,7592,7595,7597,7599,7601,7604,7606,7608,7610,7612,7615,7618,7620,7623],{"class":3419,"line":3565},[3417,7583,7584],{"class":6164},"        Console",[3417,7586,4253],{"class":4037},[3417,7588,7589],{"class":6043},"WriteLine",[3417,7591,6537],{"class":4037},[3417,7593,7594],{"class":4060},"$\"[+] ",[3417,7596,5400],{"class":7550},[3417,7598,6569],{"class":6164},[3417,7600,7555],{"class":7550},[3417,7602,7603],{"class":4060}," підключився. Онлайн: ",[3417,7605,5400],{"class":7550},[3417,7607,7497],{"class":6164},[3417,7609,4253],{"class":7550},[3417,7611,6644],{"class":6043},[3417,7613,7614],{"class":7550},"().",[3417,7616,7617],{"class":6164},"Count",[3417,7619,7555],{"class":7550},[3417,7621,7622],{"class":4060},"\"",[3417,7624,6577],{"class":4037},[3417,7626,7627],{"class":3419,"line":3571},[3417,7628,3442],{"emptyLinePlaceholder":3441},[3417,7630,7631],{"class":3419,"line":3576},[3417,7632,7633],{"class":6183},"        \u002F\u002F Сповіщаємо всіх про нового учасника\n",[3417,7635,7636,7638,7640,7642,7645,7647,7649],{"class":3419,"line":3965},[3417,7637,6903],{"class":4041},[3417,7639,7395],{"class":6164},[3417,7641,4253],{"class":4037},[3417,7643,7644],{"class":6043},"BroadcastAsync",[3417,7646,6537],{"class":4037},[3417,7648,6470],{"class":4041},[3417,7650,6145],{"class":6122},[3417,7652,7653],{"class":3419,"line":3970},[3417,7654,7187],{"class":4037},[3417,7656,7657,7660,7662,7665],{"class":3419,"line":4410},[3417,7658,7659],{"class":6164},"            Type",[3417,7661,6467],{"class":4037},[3417,7663,7664],{"class":4060},"\"system\"",[3417,7666,7216],{"class":4037},[3417,7668,7669,7672,7674,7677,7679,7681,7683],{"class":3419,"line":4415},[3417,7670,7671],{"class":6164},"            Text",[3417,7673,6467],{"class":4037},[3417,7675,7676],{"class":4060},"$\"🟢 ",[3417,7678,5400],{"class":7550},[3417,7680,6569],{"class":6164},[3417,7682,7555],{"class":7550},[3417,7684,7685],{"class":4060}," приєднався до чату\"\n",[3417,7687,7688,7691,7693,7695,7697],{"class":3419,"line":4964},[3417,7689,7690],{"class":4037},"        }, ",[3417,7692,6695],{"class":6164},[3417,7694,5407],{"class":4037},[3417,7696,6569],{"class":6164},[3417,7698,6577],{"class":4037},[3417,7700,7701],{"class":3419,"line":4969},[3417,7702,3442],{"emptyLinePlaceholder":3441},[3417,7704,7705],{"class":3419,"line":5876},[3417,7706,7707],{"class":6183},"        \u002F\u002F Новому клієнту — список онлайн\n",[3417,7709,7710,7712,7714,7716,7718,7720,7722,7724,7726],{"class":3419,"line":5882},[3417,7711,6903],{"class":4041},[3417,7713,7395],{"class":6164},[3417,7715,4253],{"class":4037},[3417,7717,6943],{"class":6043},[3417,7719,6537],{"class":4037},[3417,7721,6569],{"class":6164},[3417,7723,4246],{"class":4037},[3417,7725,6470],{"class":4041},[3417,7727,6145],{"class":6122},[3417,7729,7730],{"class":3419,"line":5888},[3417,7731,7187],{"class":4037},[3417,7733,7734,7736,7738,7741],{"class":3419,"line":5893},[3417,7735,7659],{"class":6164},[3417,7737,6467],{"class":4037},[3417,7739,7740],{"class":4060},"\"users\"",[3417,7742,7216],{"class":4037},[3417,7744,7745,7748,7751,7753,7755,7757],{"class":3419,"line":5898},[3417,7746,7747],{"class":6164},"            Users",[3417,7749,7750],{"class":4037}," = [.. ",[3417,7752,7497],{"class":6164},[3417,7754,4253],{"class":4037},[3417,7756,6644],{"class":6043},[3417,7758,7759],{"class":4037},"()]\n",[3417,7761,7762],{"class":3419,"line":5903},[3417,7763,7764],{"class":4037},"        });\n",[3417,7766,7767],{"class":3419,"line":5908},[3417,7768,3442],{"emptyLinePlaceholder":3441},[3417,7770,7771,7774,7776,7779,7782,7784,7786,7788,7790,7792,7794],{"class":3419,"line":5914},[3417,7772,7773],{"class":4033},"        try",[3417,7775,6168],{"class":4037},[3417,7777,7778],{"class":4041},"await",[3417,7780,7781],{"class":6043}," ReceiveLoopAsync",[3417,7783,6537],{"class":4037},[3417,7785,7511],{"class":6164},[3417,7787,4246],{"class":4037},[3417,7789,6569],{"class":6164},[3417,7791,4246],{"class":4037},[3417,7793,7563],{"class":6164},[3417,7795,7796],{"class":4037},"); }\n",[3417,7798,7799],{"class":3419,"line":5920},[3417,7800,7801],{"class":4033},"        finally\n",[3417,7803,7804],{"class":3419,"line":5925},[3417,7805,7187],{"class":4037},[3417,7807,7808,7811,7813,7816,7818,7820],{"class":3419,"line":7017},[3417,7809,7810],{"class":6164},"            room",[3417,7812,4253],{"class":4037},[3417,7814,7815],{"class":6043},"RemoveClient",[3417,7817,6537],{"class":4037},[3417,7819,6569],{"class":6164},[3417,7821,6577],{"class":4037},[3417,7823,7824,7827,7829,7831,7833,7836,7838,7840,7842,7845],{"class":3419,"line":7028},[3417,7825,7826],{"class":6164},"            Console",[3417,7828,4253],{"class":4037},[3417,7830,7589],{"class":6043},[3417,7832,6537],{"class":4037},[3417,7834,7835],{"class":4060},"$\"[-] ",[3417,7837,5400],{"class":7550},[3417,7839,6569],{"class":6164},[3417,7841,7555],{"class":7550},[3417,7843,7844],{"class":4060}," відключився\"",[3417,7846,6577],{"class":4037},[3417,7848,7849,7851,7853,7855,7857,7859,7861],{"class":3419,"line":7033},[3417,7850,7193],{"class":4041},[3417,7852,7395],{"class":6164},[3417,7854,4253],{"class":4037},[3417,7856,7644],{"class":6043},[3417,7858,6537],{"class":4037},[3417,7860,6470],{"class":4041},[3417,7862,6145],{"class":6122},[3417,7864,7865],{"class":3419,"line":7056},[3417,7866,7867],{"class":4037},"            {\n",[3417,7869,7870,7873,7875,7877],{"class":3419,"line":7075},[3417,7871,7872],{"class":6164},"                Type",[3417,7874,6467],{"class":4037},[3417,7876,7664],{"class":4060},[3417,7878,7216],{"class":4037},[3417,7880,7881,7884,7886,7889,7891,7893,7895],{"class":3419,"line":7105},[3417,7882,7883],{"class":6164},"                Text",[3417,7885,6467],{"class":4037},[3417,7887,7888],{"class":4060},"$\"🔴 ",[3417,7890,5400],{"class":7550},[3417,7892,6569],{"class":6164},[3417,7894,7555],{"class":7550},[3417,7896,7897],{"class":4060}," покинув чат\"\n",[3417,7899,7900],{"class":3419,"line":7116},[3417,7901,7902],{"class":4037},"            });\n",[3417,7904,7905],{"class":3419,"line":7121},[3417,7906,7245],{"class":4037},[3417,7908,7909],{"class":3419,"line":7126},[3417,7910,5765],{"class":4037},[3417,7912,7913],{"class":3419,"line":7132},[3417,7914,3442],{"emptyLinePlaceholder":3441},[3417,7916,7917,7919,7921,7923,7925,7927,7929,7931,7933,7935,7937],{"class":3419,"line":7138},[3417,7918,6444],{"class":4041},[3417,7920,6672],{"class":4041},[3417,7922,6675],{"class":6122},[3417,7924,7781],{"class":6043},[3417,7926,6537],{"class":4037},[3417,7928,3649],{"class":6122},[3417,7930,7461],{"class":6164},[3417,7932,4246],{"class":4037},[3417,7934,6261],{"class":4041},[3417,7936,6542],{"class":6164},[3417,7938,7216],{"class":4037},[3417,7940,7941,7943,7945],{"class":3419,"line":7173},[3417,7942,7474],{"class":6122},[3417,7944,7477],{"class":6164},[3417,7946,6552],{"class":4037},[3417,7948,7949],{"class":3419,"line":7178},[3417,7950,6500],{"class":4037},[3417,7952,7953,7955,7957,7960,7962,7964,7967,7970,7973],{"class":3419,"line":7184},[3417,7954,6711],{"class":4041},[3417,7956,6714],{"class":4037},[3417,7958,7959],{"class":6164},"buffer",[3417,7961,6467],{"class":4037},[3417,7963,6470],{"class":4041},[3417,7965,7966],{"class":4041}," byte",[3417,7968,7969],{"class":4037},"[",[3417,7971,7972],{"class":4048},"4096",[3417,7974,6661],{"class":4037},[3417,7976,7977],{"class":3419,"line":7190},[3417,7978,3442],{"emptyLinePlaceholder":3441},[3417,7980,7981,7984,7986,7988,7990,7992,7994,7996,7998,8000,8003,8005,8007,8010],{"class":3419,"line":7219},[3417,7982,7983],{"class":4033},"        while",[3417,7985,7254],{"class":4037},[3417,7987,7511],{"class":6164},[3417,7989,4253],{"class":4037},[3417,7991,6851],{"class":6164},[3417,7993,6854],{"class":4037},[3417,7995,6857],{"class":6164},[3417,7997,4253],{"class":4037},[3417,7999,6862],{"class":6164},[3417,8001,8002],{"class":4037}," && !",[3417,8004,7563],{"class":6164},[3417,8006,4253],{"class":4037},[3417,8008,8009],{"class":6164},"IsCancellationRequested",[3417,8011,6552],{"class":4037},[3417,8013,8014],{"class":3419,"line":7242},[3417,8015,7187],{"class":4037},[3417,8017,8018,8021,8024,8027,8029,8031,8034],{"class":3419,"line":7248},[3417,8019,8020],{"class":4033},"            using",[3417,8022,8023],{"class":4041}," var",[3417,8025,8026],{"class":6164}," ms",[3417,8028,6467],{"class":4037},[3417,8030,6470],{"class":4041},[3417,8032,8033],{"class":6122}," MemoryStream",[3417,8035,6473],{"class":4037},[3417,8037,8038,8041,8044],{"class":3419,"line":7269},[3417,8039,8040],{"class":6122},"            WebSocketReceiveResult",[3417,8042,8043],{"class":6164}," result",[3417,8045,6130],{"class":4037},[3417,8047,8048],{"class":3419,"line":7274},[3417,8049,3442],{"emptyLinePlaceholder":3441},[3417,8051,8053],{"class":3419,"line":8052},65,[3417,8054,8055],{"class":6183},"            \u002F\u002F Збираємо фрагментоване повідомлення в єдиний MemoryStream\n",[3417,8057,8059],{"class":3419,"line":8058},66,[3417,8060,8061],{"class":4033},"            do\n",[3417,8063,8065],{"class":3419,"line":8064},67,[3417,8066,7867],{"class":4037},[3417,8068,8070,8073,8075,8077,8079,8081,8084,8086,8088,8090,8092,8094,8096,8098,8101,8103],{"class":3419,"line":8069},68,[3417,8071,8072],{"class":6164},"                result",[3417,8074,6467],{"class":4037},[3417,8076,7778],{"class":4041},[3417,8078,7461],{"class":6164},[3417,8080,4253],{"class":4037},[3417,8082,8083],{"class":6043},"ReceiveAsync",[3417,8085,6537],{"class":4037},[3417,8087,6470],{"class":4041},[3417,8089,7091],{"class":6122},[3417,8091,6258],{"class":4037},[3417,8093,6768],{"class":4041},[3417,8095,7098],{"class":4037},[3417,8097,7959],{"class":6164},[3417,8099,8100],{"class":4037},"), ",[3417,8102,7563],{"class":6164},[3417,8104,6577],{"class":4037},[3417,8106,8108],{"class":3419,"line":8107},69,[3417,8109,3442],{"emptyLinePlaceholder":3441},[3417,8111,8113,8116,8118,8121,8123,8126,8128,8130,8132,8134],{"class":3419,"line":8112},70,[3417,8114,8115],{"class":4033},"                if",[3417,8117,7254],{"class":4037},[3417,8119,8120],{"class":6164},"result",[3417,8122,4253],{"class":4037},[3417,8124,8125],{"class":6164},"MessageType",[3417,8127,6854],{"class":4037},[3417,8129,7209],{"class":6164},[3417,8131,4253],{"class":4037},[3417,8133,4648],{"class":6164},[3417,8135,6552],{"class":4037},[3417,8137,8139],{"class":3419,"line":8138},71,[3417,8140,8141],{"class":4037},"                {\n",[3417,8143,8145,8148,8150,8152,8154,8156,8158,8160,8163,8165,8168,8170,8172],{"class":3419,"line":8144},72,[3417,8146,8147],{"class":4041},"                    await",[3417,8149,7461],{"class":6164},[3417,8151,4253],{"class":4037},[3417,8153,7529],{"class":6043},[3417,8155,6537],{"class":4037},[3417,8157,7535],{"class":6164},[3417,8159,4253],{"class":4037},[3417,8161,8162],{"class":6164},"NormalClosure",[3417,8164,4246],{"class":4037},[3417,8166,8167],{"class":4060},"\"OK\"",[3417,8169,4246],{"class":4037},[3417,8171,7563],{"class":6164},[3417,8173,6577],{"class":4037},[3417,8175,8177,8180],{"class":3419,"line":8176},73,[3417,8178,8179],{"class":4033},"                    return",[3417,8181,6130],{"class":4037},[3417,8183,8185],{"class":3419,"line":8184},74,[3417,8186,8187],{"class":4037},"                }\n",[3417,8189,8191],{"class":3419,"line":8190},75,[3417,8192,3442],{"emptyLinePlaceholder":3441},[3417,8194,8196,8199,8201,8204,8206,8208,8210,8212,8214,8216,8218,8220],{"class":3419,"line":8195},76,[3417,8197,8198],{"class":6164},"                ms",[3417,8200,4253],{"class":4037},[3417,8202,8203],{"class":6043},"Write",[3417,8205,6537],{"class":4037},[3417,8207,7959],{"class":6164},[3417,8209,4246],{"class":4037},[3417,8211,4453],{"class":4048},[3417,8213,4246],{"class":4037},[3417,8215,8120],{"class":6164},[3417,8217,4253],{"class":4037},[3417,8219,7617],{"class":6164},[3417,8221,6577],{"class":4037},[3417,8223,8225,8228,8231,8233,8235,8237,8240],{"class":3419,"line":8224},77,[3417,8226,8227],{"class":4037},"            } ",[3417,8229,8230],{"class":4033},"while",[3417,8232,6969],{"class":4037},[3417,8234,8120],{"class":6164},[3417,8236,4253],{"class":4037},[3417,8238,8239],{"class":6164},"EndOfMessage",[3417,8241,6577],{"class":4037},[3417,8243,8245],{"class":3419,"line":8244},78,[3417,8246,3442],{"emptyLinePlaceholder":3441},[3417,8248,8250,8253,8255,8257,8259,8261,8263,8265,8267,8269,8272,8275],{"class":3419,"line":8249},79,[3417,8251,8252],{"class":4033},"            if",[3417,8254,7254],{"class":4037},[3417,8256,8120],{"class":6164},[3417,8258,4253],{"class":4037},[3417,8260,8125],{"class":6164},[3417,8262,6831],{"class":4037},[3417,8264,7209],{"class":6164},[3417,8266,4253],{"class":4037},[3417,8268,4600],{"class":6164},[3417,8270,8271],{"class":4037},") ",[3417,8273,8274],{"class":4033},"continue",[3417,8276,6130],{"class":4037},[3417,8278,8280],{"class":3419,"line":8279},80,[3417,8281,3442],{"emptyLinePlaceholder":3441},[3417,8283,8285,8287,8290,8292,8294],{"class":3419,"line":8284},81,[3417,8286,7193],{"class":4041},[3417,8288,8289],{"class":6043}," ProcessMessageAsync",[3417,8291,6537],{"class":4037},[3417,8293,6569],{"class":6164},[3417,8295,7216],{"class":4037},[3417,8297,8299,8302,8304,8306,8308,8311,8313,8316,8318,8321],{"class":3419,"line":8298},82,[3417,8300,8301],{"class":6164},"                Encoding",[3417,8303,4253],{"class":4037},[3417,8305,6727],{"class":6164},[3417,8307,4253],{"class":4037},[3417,8309,8310],{"class":6043},"GetString",[3417,8312,6537],{"class":4037},[3417,8314,8315],{"class":6164},"ms",[3417,8317,4253],{"class":4037},[3417,8319,8320],{"class":6043},"ToArray",[3417,8322,8323],{"class":4037},"()));\n",[3417,8325,8327],{"class":3419,"line":8326},83,[3417,8328,7245],{"class":4037},[3417,8330,8332],{"class":3419,"line":8331},84,[3417,8333,5765],{"class":4037},[3417,8335,8337],{"class":3419,"line":8336},85,[3417,8338,3442],{"emptyLinePlaceholder":3441},[3417,8340,8342,8344,8346,8348,8350,8352,8354,8357,8359,8361,8364],{"class":3419,"line":8341},86,[3417,8343,6444],{"class":4041},[3417,8345,6672],{"class":4041},[3417,8347,6675],{"class":6122},[3417,8349,8289],{"class":6043},[3417,8351,6537],{"class":4037},[3417,8353,6261],{"class":4041},[3417,8355,8356],{"class":6164}," from",[3417,8358,4246],{"class":4037},[3417,8360,6261],{"class":4041},[3417,8362,8363],{"class":6164}," json",[3417,8365,6552],{"class":4037},[3417,8367,8369],{"class":3419,"line":8368},87,[3417,8370,6500],{"class":4037},[3417,8372,8374,8377,8379,8382],{"class":3419,"line":8373},88,[3417,8375,8376],{"class":6122},"        ChatMessage",[3417,8378,6193],{"class":4037},[3417,8380,8381],{"class":6164},"msg",[3417,8383,6130],{"class":4037},[3417,8385,8387,8389,8391,8393,8395,8398,8400,8403,8405,8407,8409,8411,8413,8415],{"class":3419,"line":8386},89,[3417,8388,7773],{"class":4033},[3417,8390,6168],{"class":4037},[3417,8392,8381],{"class":6164},[3417,8394,6467],{"class":4037},[3417,8396,8397],{"class":6164},"JsonSerializer",[3417,8399,4253],{"class":4037},[3417,8401,8402],{"class":6043},"Deserialize",[3417,8404,6258],{"class":4037},[3417,8406,6683],{"class":6122},[3417,8408,7098],{"class":4037},[3417,8410,5393],{"class":6164},[3417,8412,4246],{"class":4037},[3417,8414,6755],{"class":6164},[3417,8416,7796],{"class":4037},[3417,8418,8420,8422,8424,8427],{"class":3419,"line":8419},90,[3417,8421,7251],{"class":4033},[3417,8423,6168],{"class":4037},[3417,8425,8426],{"class":4033},"return",[3417,8428,6207],{"class":4037},[3417,8430,8432,8434,8436,8438,8441,8444,8446,8448],{"class":3419,"line":8431},91,[3417,8433,6966],{"class":4033},[3417,8435,7254],{"class":4037},[3417,8437,8381],{"class":6164},[3417,8439,8440],{"class":4041}," is",[3417,8442,8443],{"class":4041}," null",[3417,8445,8271],{"class":4037},[3417,8447,8426],{"class":4033},[3417,8449,6130],{"class":4037},[3417,8451,8453],{"class":3419,"line":8452},92,[3417,8454,3442],{"emptyLinePlaceholder":3441},[3417,8456,8458,8461,8463,8465,8467,8470],{"class":3419,"line":8457},93,[3417,8459,8460],{"class":4033},"        switch",[3417,8462,7254],{"class":4037},[3417,8464,8381],{"class":6164},[3417,8466,4253],{"class":4037},[3417,8468,8469],{"class":6164},"Type",[3417,8471,6552],{"class":4037},[3417,8473,8475],{"class":3419,"line":8474},94,[3417,8476,7187],{"class":4037},[3417,8478,8480,8483,8486],{"class":3419,"line":8479},95,[3417,8481,8482],{"class":4033},"            case",[3417,8484,8485],{"class":4060}," \"chat\"",[3417,8487,8488],{"class":4037},":\n",[3417,8490,8492,8495,8497,8499,8501,8503,8505],{"class":3419,"line":8491},96,[3417,8493,8494],{"class":4041},"                await",[3417,8496,7395],{"class":6164},[3417,8498,4253],{"class":4037},[3417,8500,7644],{"class":6043},[3417,8502,6537],{"class":4037},[3417,8504,6470],{"class":4041},[3417,8506,6145],{"class":6122},[3417,8508,8510],{"class":3419,"line":8509},97,[3417,8511,8141],{"class":4037},[3417,8513,8515,8518,8520,8523,8525,8527,8529,8532,8534,8536,8538,8540,8542],{"class":3419,"line":8514},98,[3417,8516,8517],{"class":6164},"                    Type",[3417,8519,6467],{"class":4037},[3417,8521,8522],{"class":4060},"\"chat\"",[3417,8524,4246],{"class":4037},[3417,8526,6196],{"class":6164},[3417,8528,6467],{"class":4037},[3417,8530,8531],{"class":6164},"from",[3417,8533,4246],{"class":4037},[3417,8535,4600],{"class":6164},[3417,8537,6467],{"class":4037},[3417,8539,8381],{"class":6164},[3417,8541,4253],{"class":4037},[3417,8543,8544],{"class":6164},"Text\n",[3417,8546,8548],{"class":3419,"line":8547},99,[3417,8549,8550],{"class":4037},"                });\n",[3417,8552,8554,8557],{"class":3419,"line":8553},100,[3417,8555,8556],{"class":4033},"                break",[3417,8558,6130],{"class":4037},[3417,8560,8562],{"class":3419,"line":8561},101,[3417,8563,3442],{"emptyLinePlaceholder":3441},[3417,8565,8567,8569,8572,8575,8578,8580,8582,8584,8587,8589],{"class":3419,"line":8566},102,[3417,8568,8482],{"class":4033},[3417,8570,8571],{"class":4060}," \"private\"",[3417,8573,8574],{"class":4033}," when",[3417,8576,8577],{"class":6164}," msg",[3417,8579,4253],{"class":4037},[3417,8581,6218],{"class":6164},[3417,8583,8440],{"class":4041},[3417,8585,8586],{"class":4041}," not",[3417,8588,8443],{"class":4041},[3417,8590,8488],{"class":4037},[3417,8592,8594,8597,8600,8602,8604],{"class":3419,"line":8593},103,[3417,8595,8596],{"class":4041},"                var",[3417,8598,8599],{"class":6164}," pm",[3417,8601,6467],{"class":4037},[3417,8603,6470],{"class":4041},[3417,8605,6145],{"class":6122},[3417,8607,8609],{"class":3419,"line":8608},104,[3417,8610,8141],{"class":4037},[3417,8612,8614,8616,8618,8621,8623,8625,8627,8629,8631,8633,8635,8637,8639,8641,8643,8645,8647,8649,8651],{"class":3419,"line":8613},105,[3417,8615,8517],{"class":6164},[3417,8617,6467],{"class":4037},[3417,8619,8620],{"class":4060},"\"private\"",[3417,8622,4246],{"class":4037},[3417,8624,6196],{"class":6164},[3417,8626,6467],{"class":4037},[3417,8628,8531],{"class":6164},[3417,8630,4246],{"class":4037},[3417,8632,6218],{"class":6164},[3417,8634,6467],{"class":4037},[3417,8636,8381],{"class":6164},[3417,8638,4253],{"class":4037},[3417,8640,6218],{"class":6164},[3417,8642,4246],{"class":4037},[3417,8644,4600],{"class":6164},[3417,8646,6467],{"class":4037},[3417,8648,8381],{"class":6164},[3417,8650,4253],{"class":4037},[3417,8652,8544],{"class":6164},[3417,8654,8656],{"class":3419,"line":8655},106,[3417,8657,8658],{"class":4037},"                };\n",[3417,8660,8662,8665,8668,8670,8672,8674,8676,8678,8680,8682,8684,8686,8688,8691],{"class":3419,"line":8661},107,[3417,8663,8664],{"class":4041},"                bool",[3417,8666,8667],{"class":6164}," sent",[3417,8669,6467],{"class":4037},[3417,8671,7778],{"class":4041},[3417,8673,7395],{"class":6164},[3417,8675,4253],{"class":4037},[3417,8677,6943],{"class":6043},[3417,8679,6537],{"class":4037},[3417,8681,8381],{"class":6164},[3417,8683,4253],{"class":4037},[3417,8685,6218],{"class":6164},[3417,8687,4246],{"class":4037},[3417,8689,8690],{"class":6164},"pm",[3417,8692,6577],{"class":4037},[3417,8694,8696],{"class":3419,"line":8695},108,[3417,8697,8698],{"class":6183},"                \u002F\u002F Відправляємо копію відправнику\n",[3417,8700,8702,8704,8706,8708,8710,8712,8714,8716,8719,8722,8724,8727,8729],{"class":3419,"line":8701},109,[3417,8703,8494],{"class":4041},[3417,8705,7395],{"class":6164},[3417,8707,4253],{"class":4037},[3417,8709,6943],{"class":6043},[3417,8711,6537],{"class":4037},[3417,8713,8531],{"class":6164},[3417,8715,4246],{"class":4037},[3417,8717,8718],{"class":6164},"sent",[3417,8720,8721],{"class":4037}," ? ",[3417,8723,8690],{"class":6164},[3417,8725,8726],{"class":4037}," : ",[3417,8728,6470],{"class":4041},[3417,8730,6145],{"class":6122},[3417,8732,8734],{"class":3419,"line":8733},110,[3417,8735,8141],{"class":4037},[3417,8737,8739,8741,8743,8745],{"class":3419,"line":8738},111,[3417,8740,8517],{"class":6164},[3417,8742,6467],{"class":4037},[3417,8744,7664],{"class":4060},[3417,8746,7216],{"class":4037},[3417,8748,8750,8753,8755,8758,8760,8762,8764,8766,8768],{"class":3419,"line":8749},112,[3417,8751,8752],{"class":6164},"                    Text",[3417,8754,6467],{"class":4037},[3417,8756,8757],{"class":4060},"$\"⚠️ ",[3417,8759,5400],{"class":7550},[3417,8761,8381],{"class":6164},[3417,8763,4253],{"class":7550},[3417,8765,6218],{"class":6164},[3417,8767,7555],{"class":7550},[3417,8769,8770],{"class":4060}," не в мережі\"\n",[3417,8772,8774],{"class":3419,"line":8773},113,[3417,8775,8550],{"class":4037},[3417,8777,8779,8781],{"class":3419,"line":8778},114,[3417,8780,8556],{"class":4033},[3417,8782,6130],{"class":4037},[3417,8784,8786],{"class":3419,"line":8785},115,[3417,8787,7245],{"class":4037},[3417,8789,8791],{"class":3419,"line":8790},116,[3417,8792,5765],{"class":4037},[3417,8794,8796],{"class":3419,"line":8795},117,[3417,8797,3869],{"class":4037},[3317,8799,8800,6103,8803,3593],{},[3321,8801,8802],{},"Program.cs",[3389,8804,8805],{},"ChatServer\u002FProgram.cs",[3382,8807,8809],{"className":6109,"code":8808,"language":6111,"meta":6112,"style":3391},"using System.Net;\nusing System.Text;\nusing ChatServer.Handlers;\nusing ChatServer.Services;\n\n\u002F\u002F Ручна інстанціація залежностей (без DI-контейнера)\nvar chatRoom = new ChatRoom();\n\nusing var listener = new HttpListener();\nlistener.Prefixes.Add(\"http:\u002F\u002Flocalhost:5000\u002F\");\nlistener.Start();\n\nConsole.WriteLine(\"ChatServer запущено: http:\u002F\u002Flocalhost:5000\");\nConsole.WriteLine(\"WebSocket endpoint: ws:\u002F\u002Flocalhost:5000\u002Fws?user=\u003Cнікнейм>\");\nConsole.WriteLine(\"Натисніть Ctrl+C для зупинки.\");\n\nusing var cts = new CancellationTokenSource();\nConsole.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };\n\ntry\n{\n    while (!cts.Token.IsCancellationRequested)\n    {\n        HttpListenerContext ctx = await listener.GetContextAsync();\n        \u002F\u002F Кожен запит обробляється в окремій Task, щоб не блокувати цикл\n        _ = HandleRequestAsync(ctx, chatRoom, cts.Token);\n    }\n}\ncatch (HttpListenerException) when (cts.Token.IsCancellationRequested) { }\nfinally\n{\n    listener.Stop();\n    Console.WriteLine(\"Сервер зупинено.\");\n}\n\n\u002F\u002F ─── Обробка запитів ────────────────────────────────────────────────────────\nstatic async Task HandleRequestAsync(HttpListenerContext ctx, ChatRoom chatRoom,\n    CancellationToken ct)\n{\n    string path = ctx.Request.Url?.AbsolutePath ?? \"\u002F\";\n\n    \u002F\u002F WebSocket upgrade: GET \u002Fws?user=\u003Cusername>\n    if (ctx.Request.IsWebSocketRequest && path == \"\u002Fws\")\n    {\n        string? username = ctx.Request.QueryString[\"user\"];\n        if (string.IsNullOrWhiteSpace(username))\n        {\n            ctx.Response.StatusCode = 400;\n            byte[] body = Encoding.UTF8.GetBytes(\"?user= required\");\n            await ctx.Response.OutputStream.WriteAsync(body, ct);\n            ctx.Response.Close();\n            return;\n        }\n\n        \u002F\u002F AcceptWebSocketAsync → відповідає 101 Switching Protocols автоматично\n        var wsCtx = await ctx.AcceptWebSocketAsync(subProtocol: null);\n        var handler = new WebSocketHandler(chatRoom);\n        await handler.HandleAsync(wsCtx.WebSocket, username, ct);\n        return;\n    }\n\n    \u002F\u002F Статичні файли: GET \u002F або GET \u002Findex.html\n    if (!ctx.Request.IsWebSocketRequest &&\n        (path == \"\u002F\" || path == \"\u002Findex.html\") &&\n        ctx.Request.HttpMethod == \"GET\")\n    {\n        string filePath = Path.Combine(AppContext.BaseDirectory, \"wwwroot\", \"index.html\");\n        if (File.Exists(filePath))\n        {\n            ctx.Response.StatusCode = 200;\n            ctx.Response.ContentType = \"text\u002Fhtml; charset=utf-8\";\n            byte[] content = await File.ReadAllBytesAsync(filePath, ct);\n            ctx.Response.ContentLength64 = content.Length;\n            await ctx.Response.OutputStream.WriteAsync(content, ct);\n        }\n        else\n        {\n            ctx.Response.StatusCode = 404;\n        }\n        ctx.Response.Close();\n        return;\n    }\n\n    ctx.Response.StatusCode = 404;\n    ctx.Response.Close();\n}\n",[3389,8810,8811,8823,8835,8847,8859,8863,8868,8885,8889,8907,8929,8940,8944,8960,8975,8990,8994,9012,9058,9062,9067,9071,9091,9095,9116,9121,9151,9155,9159,9189,9194,9198,9210,9226,9230,9234,9239,9266,9275,9279,9315,9319,9324,9355,9359,9388,9407,9411,9433,9462,9492,9506,9512,9516,9520,9525,9554,9573,9603,9609,9613,9617,9622,9641,9665,9686,9690,9729,9750,9754,9773,9793,9824,9848,9876,9880,9885,9889,9908,9912,9926,9932,9936,9940,9959,9973],{"__ignoreMap":3391},[3417,8812,8813,8815,8817,8819,8821],{"class":3419,"line":3420},[3417,8814,6330],{"class":4033},[3417,8816,6333],{"class":6122},[3417,8818,4253],{"class":4037},[3417,8820,6356],{"class":6122},[3417,8822,6130],{"class":4037},[3417,8824,8825,8827,8829,8831,8833],{"class":3419,"line":3426},[3417,8826,6330],{"class":4033},[3417,8828,6333],{"class":6122},[3417,8830,4253],{"class":4037},[3417,8832,4600],{"class":6122},[3417,8834,6130],{"class":4037},[3417,8836,8837,8839,8841,8843,8845],{"class":3419,"line":3432},[3417,8838,6330],{"class":4033},[3417,8840,6095],{"class":6122},[3417,8842,4253],{"class":4037},[3417,8844,7372],{"class":6122},[3417,8846,6130],{"class":4037},[3417,8848,8849,8851,8853,8855,8857],{"class":3419,"line":3438},[3417,8850,6330],{"class":4033},[3417,8852,6095],{"class":6122},[3417,8854,4253],{"class":4037},[3417,8856,6419],{"class":6122},[3417,8858,6130],{"class":4037},[3417,8860,8861],{"class":3419,"line":3445},[3417,8862,3442],{"emptyLinePlaceholder":3441},[3417,8864,8865],{"class":3419,"line":3451},[3417,8866,8867],{"class":6183},"\u002F\u002F Ручна інстанціація залежностей (без DI-контейнера)\n",[3417,8869,8870,8873,8876,8878,8880,8883],{"class":3419,"line":3457},[3417,8871,8872],{"class":4041},"var",[3417,8874,8875],{"class":6164}," chatRoom",[3417,8877,6467],{"class":4037},[3417,8879,6470],{"class":4041},[3417,8881,8882],{"class":6122}," ChatRoom",[3417,8884,6473],{"class":4037},[3417,8886,8887],{"class":3419,"line":3462},[3417,8888,3442],{"emptyLinePlaceholder":3441},[3417,8890,8891,8893,8895,8898,8900,8902,8905],{"class":3419,"line":3468},[3417,8892,6330],{"class":4033},[3417,8894,8023],{"class":4041},[3417,8896,8897],{"class":6164}," listener",[3417,8899,6467],{"class":4037},[3417,8901,6470],{"class":4041},[3417,8903,8904],{"class":6122}," HttpListener",[3417,8906,6473],{"class":4037},[3417,8908,8909,8912,8914,8917,8919,8922,8924,8927],{"class":3419,"line":3474},[3417,8910,8911],{"class":6164},"listener",[3417,8913,4253],{"class":4037},[3417,8915,8916],{"class":6164},"Prefixes",[3417,8918,4253],{"class":4037},[3417,8920,8921],{"class":6043},"Add",[3417,8923,6537],{"class":4037},[3417,8925,8926],{"class":4060},"\"http:\u002F\u002Flocalhost:5000\u002F\"",[3417,8928,6577],{"class":4037},[3417,8930,8931,8933,8935,8938],{"class":3419,"line":3480},[3417,8932,8911],{"class":6164},[3417,8934,4253],{"class":4037},[3417,8936,8937],{"class":6043},"Start",[3417,8939,6473],{"class":4037},[3417,8941,8942],{"class":3419,"line":3486},[3417,8943,3442],{"emptyLinePlaceholder":3441},[3417,8945,8946,8949,8951,8953,8955,8958],{"class":3419,"line":3492},[3417,8947,8948],{"class":6164},"Console",[3417,8950,4253],{"class":4037},[3417,8952,7589],{"class":6043},[3417,8954,6537],{"class":4037},[3417,8956,8957],{"class":4060},"\"ChatServer запущено: http:\u002F\u002Flocalhost:5000\"",[3417,8959,6577],{"class":4037},[3417,8961,8962,8964,8966,8968,8970,8973],{"class":3419,"line":3498},[3417,8963,8948],{"class":6164},[3417,8965,4253],{"class":4037},[3417,8967,7589],{"class":6043},[3417,8969,6537],{"class":4037},[3417,8971,8972],{"class":4060},"\"WebSocket endpoint: ws:\u002F\u002Flocalhost:5000\u002Fws?user=\u003Cнікнейм>\"",[3417,8974,6577],{"class":4037},[3417,8976,8977,8979,8981,8983,8985,8988],{"class":3419,"line":3504},[3417,8978,8948],{"class":6164},[3417,8980,4253],{"class":4037},[3417,8982,7589],{"class":6043},[3417,8984,6537],{"class":4037},[3417,8986,8987],{"class":4060},"\"Натисніть Ctrl+C для зупинки.\"",[3417,8989,6577],{"class":4037},[3417,8991,8992],{"class":3419,"line":3509},[3417,8993,3442],{"emptyLinePlaceholder":3441},[3417,8995,8996,8998,9000,9003,9005,9007,9010],{"class":3419,"line":3515},[3417,8997,6330],{"class":4033},[3417,8999,8023],{"class":4041},[3417,9001,9002],{"class":6164}," cts",[3417,9004,6467],{"class":4037},[3417,9006,6470],{"class":4041},[3417,9008,9009],{"class":6122}," CancellationTokenSource",[3417,9011,6473],{"class":4037},[3417,9013,9014,9016,9018,9021,9024,9027,9029,9032,9035,9037,9039,9042,9044,9046,9048,9051,9053,9055],{"class":3419,"line":3521},[3417,9015,8948],{"class":6164},[3417,9017,4253],{"class":4037},[3417,9019,9020],{"class":6164},"CancelKeyPress",[3417,9022,9023],{"class":4037}," += (",[3417,9025,9026],{"class":6164},"_",[3417,9028,4246],{"class":4037},[3417,9030,9031],{"class":6164},"e",[3417,9033,9034],{"class":4037},") => { ",[3417,9036,9031],{"class":6164},[3417,9038,4253],{"class":4037},[3417,9040,9041],{"class":6164},"Cancel",[3417,9043,6467],{"class":4037},[3417,9045,7227],{"class":4041},[3417,9047,6174],{"class":4037},[3417,9049,9050],{"class":6164},"cts",[3417,9052,4253],{"class":4037},[3417,9054,9041],{"class":6043},[3417,9056,9057],{"class":4037},"(); };\n",[3417,9059,9060],{"class":3419,"line":3527},[3417,9061,3442],{"emptyLinePlaceholder":3441},[3417,9063,9064],{"class":3419,"line":3533},[3417,9065,9066],{"class":4033},"try\n",[3417,9068,9069],{"class":3419,"line":3538},[3417,9070,6150],{"class":4037},[3417,9072,9073,9076,9078,9080,9082,9085,9087,9089],{"class":3419,"line":3543},[3417,9074,9075],{"class":4033},"    while",[3417,9077,6969],{"class":4037},[3417,9079,9050],{"class":6164},[3417,9081,4253],{"class":4037},[3417,9083,9084],{"class":6164},"Token",[3417,9086,4253],{"class":4037},[3417,9088,8009],{"class":6164},[3417,9090,6552],{"class":4037},[3417,9092,9093],{"class":3419,"line":3549},[3417,9094,6500],{"class":4037},[3417,9096,9097,9100,9103,9105,9107,9109,9111,9114],{"class":3419,"line":3555},[3417,9098,9099],{"class":6122},"        HttpListenerContext",[3417,9101,9102],{"class":6164}," ctx",[3417,9104,6467],{"class":4037},[3417,9106,7778],{"class":4041},[3417,9108,8897],{"class":6164},[3417,9110,4253],{"class":4037},[3417,9112,9113],{"class":6043},"GetContextAsync",[3417,9115,6473],{"class":4037},[3417,9117,9118],{"class":3419,"line":3560},[3417,9119,9120],{"class":6183},"        \u002F\u002F Кожен запит обробляється в окремій Task, щоб не блокувати цикл\n",[3417,9122,9123,9126,9128,9131,9133,9136,9138,9141,9143,9145,9147,9149],{"class":3419,"line":3565},[3417,9124,9125],{"class":6164},"        _",[3417,9127,6467],{"class":4037},[3417,9129,9130],{"class":6043},"HandleRequestAsync",[3417,9132,6537],{"class":4037},[3417,9134,9135],{"class":6164},"ctx",[3417,9137,4246],{"class":4037},[3417,9139,9140],{"class":6164},"chatRoom",[3417,9142,4246],{"class":4037},[3417,9144,9050],{"class":6164},[3417,9146,4253],{"class":4037},[3417,9148,9084],{"class":6164},[3417,9150,6577],{"class":4037},[3417,9152,9153],{"class":3419,"line":3571},[3417,9154,5765],{"class":4037},[3417,9156,9157],{"class":3419,"line":3576},[3417,9158,3869],{"class":4037},[3417,9160,9161,9164,9166,9169,9171,9174,9176,9178,9180,9182,9184,9186],{"class":3419,"line":3965},[3417,9162,9163],{"class":4033},"catch",[3417,9165,7254],{"class":4037},[3417,9167,9168],{"class":6122},"HttpListenerException",[3417,9170,8271],{"class":4037},[3417,9172,9173],{"class":4033},"when",[3417,9175,7254],{"class":4037},[3417,9177,9050],{"class":6164},[3417,9179,4253],{"class":4037},[3417,9181,9084],{"class":6164},[3417,9183,4253],{"class":4037},[3417,9185,8009],{"class":6164},[3417,9187,9188],{"class":4037},") { }\n",[3417,9190,9191],{"class":3419,"line":3970},[3417,9192,9193],{"class":4033},"finally\n",[3417,9195,9196],{"class":3419,"line":4410},[3417,9197,6150],{"class":4037},[3417,9199,9200,9203,9205,9208],{"class":3419,"line":4415},[3417,9201,9202],{"class":6164},"    listener",[3417,9204,4253],{"class":4037},[3417,9206,9207],{"class":6043},"Stop",[3417,9209,6473],{"class":4037},[3417,9211,9212,9215,9217,9219,9221,9224],{"class":3419,"line":4964},[3417,9213,9214],{"class":6164},"    Console",[3417,9216,4253],{"class":4037},[3417,9218,7589],{"class":6043},[3417,9220,6537],{"class":4037},[3417,9222,9223],{"class":4060},"\"Сервер зупинено.\"",[3417,9225,6577],{"class":4037},[3417,9227,9228],{"class":3419,"line":4969},[3417,9229,3869],{"class":4037},[3417,9231,9232],{"class":3419,"line":5876},[3417,9233,3442],{"emptyLinePlaceholder":3441},[3417,9235,9236],{"class":3419,"line":5882},[3417,9237,9238],{"class":6183},"\u002F\u002F ─── Обробка запитів ────────────────────────────────────────────────────────\n",[3417,9240,9241,9244,9246,9248,9251,9253,9256,9258,9260,9262,9264],{"class":3419,"line":5888},[3417,9242,9243],{"class":4041},"static",[3417,9245,6672],{"class":4041},[3417,9247,6675],{"class":6122},[3417,9249,9250],{"class":6043}," HandleRequestAsync",[3417,9252,6537],{"class":4037},[3417,9254,9255],{"class":6122},"HttpListenerContext",[3417,9257,9102],{"class":6164},[3417,9259,4246],{"class":4037},[3417,9261,7392],{"class":6122},[3417,9263,8875],{"class":6164},[3417,9265,7216],{"class":4037},[3417,9267,9268,9271,9273],{"class":3419,"line":5893},[3417,9269,9270],{"class":6122},"    CancellationToken",[3417,9272,7477],{"class":6164},[3417,9274,6552],{"class":4037},[3417,9276,9277],{"class":3419,"line":5898},[3417,9278,6150],{"class":4037},[3417,9280,9281,9284,9287,9289,9291,9293,9296,9298,9301,9304,9307,9310,9313],{"class":3419,"line":5903},[3417,9282,9283],{"class":4041},"    string",[3417,9285,9286],{"class":6164}," path",[3417,9288,6467],{"class":4037},[3417,9290,9135],{"class":6164},[3417,9292,4253],{"class":4037},[3417,9294,9295],{"class":6164},"Request",[3417,9297,4253],{"class":4037},[3417,9299,9300],{"class":6164},"Url",[3417,9302,9303],{"class":4037},"?.",[3417,9305,9306],{"class":6164},"AbsolutePath",[3417,9308,9309],{"class":4037}," ?? ",[3417,9311,9312],{"class":4060},"\"\u002F\"",[3417,9314,6130],{"class":4037},[3417,9316,9317],{"class":3419,"line":5908},[3417,9318,3442],{"emptyLinePlaceholder":3441},[3417,9320,9321],{"class":3419,"line":5914},[3417,9322,9323],{"class":6183},"    \u002F\u002F WebSocket upgrade: GET \u002Fws?user=\u003Cusername>\n",[3417,9325,9326,9329,9331,9333,9335,9337,9339,9342,9345,9348,9350,9353],{"class":3419,"line":5920},[3417,9327,9328],{"class":4033},"    if",[3417,9330,7254],{"class":4037},[3417,9332,9135],{"class":6164},[3417,9334,4253],{"class":4037},[3417,9336,9295],{"class":6164},[3417,9338,4253],{"class":4037},[3417,9340,9341],{"class":6164},"IsWebSocketRequest",[3417,9343,9344],{"class":4037}," && ",[3417,9346,9347],{"class":6164},"path",[3417,9349,6854],{"class":4037},[3417,9351,9352],{"class":4060},"\"\u002Fws\"",[3417,9354,6552],{"class":4037},[3417,9356,9357],{"class":3419,"line":5925},[3417,9358,6500],{"class":4037},[3417,9360,9361,9364,9366,9368,9370,9372,9374,9376,9378,9381,9383,9386],{"class":3419,"line":7017},[3417,9362,9363],{"class":4041},"        string",[3417,9365,6193],{"class":4037},[3417,9367,6569],{"class":6164},[3417,9369,6467],{"class":4037},[3417,9371,9135],{"class":6164},[3417,9373,4253],{"class":4037},[3417,9375,9295],{"class":6164},[3417,9377,4253],{"class":4037},[3417,9379,9380],{"class":6164},"QueryString",[3417,9382,7969],{"class":4037},[3417,9384,9385],{"class":4060},"\"user\"",[3417,9387,6661],{"class":4037},[3417,9389,9390,9392,9394,9396,9398,9401,9403,9405],{"class":3419,"line":7028},[3417,9391,6966],{"class":4033},[3417,9393,7254],{"class":4037},[3417,9395,6261],{"class":4041},[3417,9397,4253],{"class":4037},[3417,9399,9400],{"class":6043},"IsNullOrWhiteSpace",[3417,9402,6537],{"class":4037},[3417,9404,6569],{"class":6164},[3417,9406,7514],{"class":4037},[3417,9408,9409],{"class":3419,"line":7033},[3417,9410,7187],{"class":4037},[3417,9412,9413,9416,9418,9421,9423,9426,9428,9431],{"class":3419,"line":7056},[3417,9414,9415],{"class":6164},"            ctx",[3417,9417,4253],{"class":4037},[3417,9419,9420],{"class":6164},"Response",[3417,9422,4253],{"class":4037},[3417,9424,9425],{"class":6164},"StatusCode",[3417,9427,6467],{"class":4037},[3417,9429,9430],{"class":4048},"400",[3417,9432,6130],{"class":4037},[3417,9434,9435,9438,9440,9443,9445,9447,9449,9451,9453,9455,9457,9460],{"class":3419,"line":7075},[3417,9436,9437],{"class":4041},"            byte",[3417,9439,6714],{"class":4037},[3417,9441,9442],{"class":6164},"body",[3417,9444,6467],{"class":4037},[3417,9446,6722],{"class":6164},[3417,9448,4253],{"class":4037},[3417,9450,6727],{"class":6164},[3417,9452,4253],{"class":4037},[3417,9454,6732],{"class":6043},[3417,9456,6537],{"class":4037},[3417,9458,9459],{"class":4060},"\"?user= required\"",[3417,9461,6577],{"class":4037},[3417,9463,9464,9466,9468,9470,9472,9474,9477,9479,9482,9484,9486,9488,9490],{"class":3419,"line":7105},[3417,9465,7193],{"class":4041},[3417,9467,9102],{"class":6164},[3417,9469,4253],{"class":4037},[3417,9471,9420],{"class":6164},[3417,9473,4253],{"class":4037},[3417,9475,9476],{"class":6164},"OutputStream",[3417,9478,4253],{"class":4037},[3417,9480,9481],{"class":6043},"WriteAsync",[3417,9483,6537],{"class":4037},[3417,9485,9442],{"class":6164},[3417,9487,4246],{"class":4037},[3417,9489,7563],{"class":6164},[3417,9491,6577],{"class":4037},[3417,9493,9494,9496,9498,9500,9502,9504],{"class":3419,"line":7116},[3417,9495,9415],{"class":6164},[3417,9497,4253],{"class":4037},[3417,9499,9420],{"class":6164},[3417,9501,4253],{"class":4037},[3417,9503,4648],{"class":6043},[3417,9505,6473],{"class":4037},[3417,9507,9508,9510],{"class":3419,"line":7121},[3417,9509,7020],{"class":4033},[3417,9511,6130],{"class":4037},[3417,9513,9514],{"class":3419,"line":7126},[3417,9515,7245],{"class":4037},[3417,9517,9518],{"class":3419,"line":7132},[3417,9519,3442],{"emptyLinePlaceholder":3441},[3417,9521,9522],{"class":3419,"line":7138},[3417,9523,9524],{"class":6183},"        \u002F\u002F AcceptWebSocketAsync → відповідає 101 Switching Protocols автоматично\n",[3417,9526,9527,9529,9532,9534,9536,9538,9540,9543,9545,9548,9550,9552],{"class":3419,"line":7173},[3417,9528,6797],{"class":4041},[3417,9530,9531],{"class":6164}," wsCtx",[3417,9533,6467],{"class":4037},[3417,9535,7778],{"class":4041},[3417,9537,9102],{"class":6164},[3417,9539,4253],{"class":4037},[3417,9541,9542],{"class":6043},"AcceptWebSocketAsync",[3417,9544,6537],{"class":4037},[3417,9546,9547],{"class":6164},"subProtocol",[3417,9549,5407],{"class":4037},[3417,9551,6700],{"class":4041},[3417,9553,6577],{"class":4037},[3417,9555,9556,9558,9561,9563,9565,9567,9569,9571],{"class":3419,"line":7178},[3417,9557,6797],{"class":4041},[3417,9559,9560],{"class":6164}," handler",[3417,9562,6467],{"class":4037},[3417,9564,6470],{"class":4041},[3417,9566,7387],{"class":6122},[3417,9568,6537],{"class":4037},[3417,9570,9140],{"class":6164},[3417,9572,6577],{"class":4037},[3417,9574,9575,9577,9579,9581,9584,9586,9589,9591,9593,9595,9597,9599,9601],{"class":3419,"line":7184},[3417,9576,6903],{"class":4041},[3417,9578,9560],{"class":6164},[3417,9580,4253],{"class":4037},[3417,9582,9583],{"class":6043},"HandleAsync",[3417,9585,6537],{"class":4037},[3417,9587,9588],{"class":6164},"wsCtx",[3417,9590,4253],{"class":4037},[3417,9592,3649],{"class":6164},[3417,9594,4246],{"class":4037},[3417,9596,6569],{"class":6164},[3417,9598,4246],{"class":4037},[3417,9600,7563],{"class":6164},[3417,9602,6577],{"class":4037},[3417,9604,9605,9607],{"class":3419,"line":7190},[3417,9606,7108],{"class":4033},[3417,9608,6130],{"class":4037},[3417,9610,9611],{"class":3419,"line":7219},[3417,9612,5765],{"class":4037},[3417,9614,9615],{"class":3419,"line":7242},[3417,9616,3442],{"emptyLinePlaceholder":3441},[3417,9618,9619],{"class":3419,"line":7248},[3417,9620,9621],{"class":6183},"    \u002F\u002F Статичні файли: GET \u002F або GET \u002Findex.html\n",[3417,9623,9624,9626,9628,9630,9632,9634,9636,9638],{"class":3419,"line":7269},[3417,9625,9328],{"class":4033},[3417,9627,6969],{"class":4037},[3417,9629,9135],{"class":6164},[3417,9631,4253],{"class":4037},[3417,9633,9295],{"class":6164},[3417,9635,4253],{"class":4037},[3417,9637,9341],{"class":6164},[3417,9639,9640],{"class":4037}," &&\n",[3417,9642,9643,9646,9648,9650,9652,9655,9657,9659,9662],{"class":3419,"line":7274},[3417,9644,9645],{"class":4037},"        (",[3417,9647,9347],{"class":6164},[3417,9649,6854],{"class":4037},[3417,9651,9312],{"class":4060},[3417,9653,9654],{"class":4037}," || ",[3417,9656,9347],{"class":6164},[3417,9658,6854],{"class":4037},[3417,9660,9661],{"class":4060},"\"\u002Findex.html\"",[3417,9663,9664],{"class":4037},") &&\n",[3417,9666,9667,9670,9672,9674,9676,9679,9681,9684],{"class":3419,"line":8052},[3417,9668,9669],{"class":6164},"        ctx",[3417,9671,4253],{"class":4037},[3417,9673,9295],{"class":6164},[3417,9675,4253],{"class":4037},[3417,9677,9678],{"class":6164},"HttpMethod",[3417,9680,6854],{"class":4037},[3417,9682,9683],{"class":4060},"\"GET\"",[3417,9685,6552],{"class":4037},[3417,9687,9688],{"class":3419,"line":8058},[3417,9689,6500],{"class":4037},[3417,9691,9692,9694,9697,9699,9702,9704,9707,9709,9712,9714,9717,9719,9722,9724,9727],{"class":3419,"line":8064},[3417,9693,9363],{"class":4041},[3417,9695,9696],{"class":6164}," filePath",[3417,9698,6467],{"class":4037},[3417,9700,9701],{"class":6164},"Path",[3417,9703,4253],{"class":4037},[3417,9705,9706],{"class":6043},"Combine",[3417,9708,6537],{"class":4037},[3417,9710,9711],{"class":6164},"AppContext",[3417,9713,4253],{"class":4037},[3417,9715,9716],{"class":6164},"BaseDirectory",[3417,9718,4246],{"class":4037},[3417,9720,9721],{"class":4060},"\"wwwroot\"",[3417,9723,4246],{"class":4037},[3417,9725,9726],{"class":4060},"\"index.html\"",[3417,9728,6577],{"class":4037},[3417,9730,9731,9733,9735,9738,9740,9743,9745,9748],{"class":3419,"line":8069},[3417,9732,6966],{"class":4033},[3417,9734,7254],{"class":4037},[3417,9736,9737],{"class":6164},"File",[3417,9739,4253],{"class":4037},[3417,9741,9742],{"class":6043},"Exists",[3417,9744,6537],{"class":4037},[3417,9746,9747],{"class":6164},"filePath",[3417,9749,7514],{"class":4037},[3417,9751,9752],{"class":3419,"line":8107},[3417,9753,7187],{"class":4037},[3417,9755,9756,9758,9760,9762,9764,9766,9768,9771],{"class":3419,"line":8112},[3417,9757,9415],{"class":6164},[3417,9759,4253],{"class":4037},[3417,9761,9420],{"class":6164},[3417,9763,4253],{"class":4037},[3417,9765,9425],{"class":6164},[3417,9767,6467],{"class":4037},[3417,9769,9770],{"class":4048},"200",[3417,9772,6130],{"class":4037},[3417,9774,9775,9777,9779,9781,9783,9786,9788,9791],{"class":3419,"line":8138},[3417,9776,9415],{"class":6164},[3417,9778,4253],{"class":4037},[3417,9780,9420],{"class":6164},[3417,9782,4253],{"class":4037},[3417,9784,9785],{"class":6164},"ContentType",[3417,9787,6467],{"class":4037},[3417,9789,9790],{"class":4060},"\"text\u002Fhtml; charset=utf-8\"",[3417,9792,6130],{"class":4037},[3417,9794,9795,9797,9799,9802,9804,9806,9809,9811,9814,9816,9818,9820,9822],{"class":3419,"line":8144},[3417,9796,9437],{"class":4041},[3417,9798,6714],{"class":4037},[3417,9800,9801],{"class":6164},"content",[3417,9803,6467],{"class":4037},[3417,9805,7778],{"class":4041},[3417,9807,9808],{"class":6164}," File",[3417,9810,4253],{"class":4037},[3417,9812,9813],{"class":6043},"ReadAllBytesAsync",[3417,9815,6537],{"class":4037},[3417,9817,9747],{"class":6164},[3417,9819,4246],{"class":4037},[3417,9821,7563],{"class":6164},[3417,9823,6577],{"class":4037},[3417,9825,9826,9828,9830,9832,9834,9837,9839,9841,9843,9846],{"class":3419,"line":8176},[3417,9827,9415],{"class":6164},[3417,9829,4253],{"class":4037},[3417,9831,9420],{"class":6164},[3417,9833,4253],{"class":4037},[3417,9835,9836],{"class":6164},"ContentLength64",[3417,9838,6467],{"class":4037},[3417,9840,9801],{"class":6164},[3417,9842,4253],{"class":4037},[3417,9844,9845],{"class":6164},"Length",[3417,9847,6130],{"class":4037},[3417,9849,9850,9852,9854,9856,9858,9860,9862,9864,9866,9868,9870,9872,9874],{"class":3419,"line":8184},[3417,9851,7193],{"class":4041},[3417,9853,9102],{"class":6164},[3417,9855,4253],{"class":4037},[3417,9857,9420],{"class":6164},[3417,9859,4253],{"class":4037},[3417,9861,9476],{"class":6164},[3417,9863,4253],{"class":4037},[3417,9865,9481],{"class":6043},[3417,9867,6537],{"class":4037},[3417,9869,9801],{"class":6164},[3417,9871,4246],{"class":4037},[3417,9873,7563],{"class":6164},[3417,9875,6577],{"class":4037},[3417,9877,9878],{"class":3419,"line":8190},[3417,9879,7245],{"class":4037},[3417,9881,9882],{"class":3419,"line":8195},[3417,9883,9884],{"class":4033},"        else\n",[3417,9886,9887],{"class":3419,"line":8224},[3417,9888,7187],{"class":4037},[3417,9890,9891,9893,9895,9897,9899,9901,9903,9906],{"class":3419,"line":8244},[3417,9892,9415],{"class":6164},[3417,9894,4253],{"class":4037},[3417,9896,9420],{"class":6164},[3417,9898,4253],{"class":4037},[3417,9900,9425],{"class":6164},[3417,9902,6467],{"class":4037},[3417,9904,9905],{"class":4048},"404",[3417,9907,6130],{"class":4037},[3417,9909,9910],{"class":3419,"line":8249},[3417,9911,7245],{"class":4037},[3417,9913,9914,9916,9918,9920,9922,9924],{"class":3419,"line":8279},[3417,9915,9669],{"class":6164},[3417,9917,4253],{"class":4037},[3417,9919,9420],{"class":6164},[3417,9921,4253],{"class":4037},[3417,9923,4648],{"class":6043},[3417,9925,6473],{"class":4037},[3417,9927,9928,9930],{"class":3419,"line":8284},[3417,9929,7108],{"class":4033},[3417,9931,6130],{"class":4037},[3417,9933,9934],{"class":3419,"line":8298},[3417,9935,5765],{"class":4037},[3417,9937,9938],{"class":3419,"line":8326},[3417,9939,3442],{"emptyLinePlaceholder":3441},[3417,9941,9942,9945,9947,9949,9951,9953,9955,9957],{"class":3419,"line":8331},[3417,9943,9944],{"class":6164},"    ctx",[3417,9946,4253],{"class":4037},[3417,9948,9420],{"class":6164},[3417,9950,4253],{"class":4037},[3417,9952,9425],{"class":6164},[3417,9954,6467],{"class":4037},[3417,9956,9905],{"class":4048},[3417,9958,6130],{"class":4037},[3417,9960,9961,9963,9965,9967,9969,9971],{"class":3419,"line":8336},[3417,9962,9944],{"class":6164},[3417,9964,4253],{"class":4037},[3417,9966,9420],{"class":6164},[3417,9968,4253],{"class":4037},[3417,9970,4648],{"class":6043},[3417,9972,6473],{"class":4037},[3417,9974,9975],{"class":3419,"line":8341},[3417,9976,3869],{"class":4037},[3364,9978],{},[3374,9980,9982],{"id":9981},"клієнтська-частина-chatclient","Клієнтська частина: ChatClient",[3317,9984,9985,9986,9989,9990,9993,9994,9997],{},"Консольний клієнт використовує ",[3389,9987,9988],{},"System.Net.WebSockets.ClientWebSocket"," — нативний WebSocket-клієнт .NET. Ключовий виклик: одночасно читати повідомлення від сервера ",[3321,9991,9992],{},"та"," чекати введення користувача з консолі. Це класичний сценарій для ",[3389,9995,9996],{},"Task.WhenAny"," із паралельними задачами.",[3317,9999,10000,6103,10002,3593],{},[3321,10001,8802],{},[3389,10003,10004],{},"ChatClient\u002FProgram.cs",[3382,10006,10008],{"className":6109,"code":10007,"language":6111,"meta":6112,"style":3391},"using System.Net.WebSockets;\nusing System.Text;\nusing System.Text.Json;\n\nConsole.Write(\"Ваш нікнейм: \");\nstring username = Console.ReadLine()?.Trim() ?? \"Anonymous\";\n\n\u002F\u002F ClientWebSocket — нативний WS-клієнт .NET\n\u002F\u002F Виконує HTTP Upgrade Handshake автоматично при ConnectAsync\nusing var ws = new ClientWebSocket();\n\n\u002F\u002F Додаємо заголовок для ідентифікації клієнта (опціонально)\nws.Options.SetRequestHeader(\"X-Client-App\", \"ConsoleChat\u002F1.0\");\n\nUri serverUri = new($\"ws:\u002F\u002Flocalhost:5000\u002Fws?user={Uri.EscapeDataString(username)}\");\n\ntry\n{\n    Console.WriteLine($\"Підключення до {serverUri}...\");\n    await ws.ConnectAsync(serverUri, CancellationToken.None);\n    Console.WriteLine(\"✅ Підключено! Введіть повідомлення (або \u002Fprivate \u003Cuser> \u003Ctext>):\");\n    Console.WriteLine(\"─────────────────────────────────────────\");\n}\ncatch (WebSocketException ex)\n{\n    Console.Error.WriteLine($\"❌ Не вдалось підключитись: {ex.Message}\");\n    return;\n}\n\nusing var cts = new CancellationTokenSource();\n\n\u002F\u002F Задача 1: отримання повідомлень від сервера (безперервно у фоні)\nTask receiveTask = ReceiveLoopAsync(ws, cts.Token);\n\n\u002F\u002F Задача 2: читання введення користувача з консолі (головний потік)\nTask inputTask = SendLoopAsync(ws, username, cts);\n\n\u002F\u002F Чекаємо завершення будь-якої з задач\n\u002F\u002F (або розрив з'єднання, або Ctrl+C від користувача)\nawait Task.WhenAny(receiveTask, inputTask);\n\n\u002F\u002F Коректне закриття: сигналізуємо скасування другій задачі\ncts.Cancel();\n\nif (ws.State == WebSocketState.Open)\n{\n    await ws.CloseAsync(WebSocketCloseStatus.NormalClosure,\n        \"Client closing\", CancellationToken.None);\n}\n\nConsole.WriteLine(\"\\nВідключено. До побачення!\");\n\n\u002F\u002F ─── Методи ────────────────────────────────────────────────────────────\n\nstatic async Task ReceiveLoopAsync(ClientWebSocket ws, CancellationToken ct)\n{\n    byte[] buffer = new byte[8192];\n    var jsonOptions = new JsonSerializerOptions\n    {\n        PropertyNameCaseInsensitive = true\n    };\n\n    try\n    {\n        while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested)\n        {\n            using var ms = new MemoryStream();\n            WebSocketReceiveResult result;\n\n            \u002F\u002F Читаємо, поки не отримаємо повне повідомлення\n            do\n            {\n                result = await ws.ReceiveAsync(new ArraySegment\u003Cbyte>(buffer), ct);\n                if (result.MessageType == WebSocketMessageType.Close) return;\n                ms.Write(buffer, 0, result.Count);\n            } while (!result.EndOfMessage);\n\n            string json = Encoding.UTF8.GetString(ms.ToArray());\n\n            \u002F\u002F Десеріалізуємо та відображаємо відповідно до типу\n            var msg = JsonSerializer.Deserialize\u003CJsonElement>(json, jsonOptions);\n            string type = msg.GetProperty(\"type\").GetString() ?? \"\";\n\n            \u002F\u002F Зберігаємо поточний курсор і перемальовуємо повідомлення\n            \u002F\u002F вище рядка введення (спрощена версія без ncurses)\n            Console.ForegroundColor = type switch\n            {\n                \"system\" => ConsoleColor.Yellow,\n                \"private\" => ConsoleColor.Magenta,\n                \"users\" => ConsoleColor.Cyan,\n                _ => ConsoleColor.White\n            };\n\n            string displayText = type switch\n            {\n                \"chat\" => $\"[{msg.TryGetProp(\"from\")}]: {msg.TryGetProp(\"text\")}\",\n                \"private\" => $\"[🔒 {msg.TryGetProp(\"from\")} → {msg.TryGetProp(\"to\")}]: {msg.TryGetProp(\"text\")}\",\n                \"system\" => $\"  {msg.TryGetProp(\"text\")}\",\n                \"users\" => $\"  👥 Онлайн: {string.Join(\", \", msg.GetProperty(\"users\").EnumerateArray().Select(u => u.GetString()))}\",\n                _ => json\n            };\n\n            Console.WriteLine(displayText);\n            Console.ResetColor();\n        }\n    }\n    catch (OperationCanceledException) { }\n    catch (WebSocketException ex)\n    {\n        Console.Error.WriteLine($\"\\n⚠️ З'єднання розірвано: {ex.Message}\");\n    }\n}\n\nstatic async Task SendLoopAsync(ClientWebSocket ws, string username,\n    CancellationTokenSource cts)\n{\n    var jsonOptions = new JsonSerializerOptions\n    {\n        PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n    };\n\n    while (!cts.Token.IsCancellationRequested && ws.State == WebSocketState.Open)\n    {\n        \u002F\u002F Console.ReadLine() блокує — не підходить для async.\n        \u002F\u002F У реальному застосунку — використовуйте спеціалізовану TUI-бібліотеку.\n        string? input = await Task.Run(() => Console.ReadLine(), cts.Token);\n        if (input is null) break;\n        input = input.Trim();\n        if (string.IsNullOrEmpty(input)) continue;\n\n        if (input == \"\u002Fquit\" || input == \"\u002Fexit\")\n        {\n            cts.Cancel();\n            break;\n        }\n\n        \u002F\u002F Формат приватного повідомлення: \u002Fprivate \u003Cusername> \u003Ctext>\n        object message;\n        if (input.StartsWith(\"\u002Fprivate \"))\n        {\n            string[] parts = input[9..].Split(' ', 2);\n            if (parts.Length \u003C 2)\n            {\n                Console.WriteLine(\"Використання: \u002Fprivate \u003Cuser> \u003Ctext>\");\n                continue;\n            }\n            message = new { type = \"private\", to = parts[0], text = parts[1] };\n        }\n        else\n        {\n            message = new { type = \"chat\", text = input };\n        }\n\n        string json = JsonSerializer.Serialize(message, jsonOptions);\n        byte[] bytes = Encoding.UTF8.GetBytes(json);\n\n        await ws.SendAsync(new ArraySegment\u003Cbyte>(bytes),\n            WebSocketMessageType.Text, endOfMessage: true, cts.Token);\n    }\n}\n\n\u002F\u002F Extension method для зручного читання полів JsonElement\nstatic class JsonElementExtensions\n{\n    public static string TryGetProp(this JsonElement el, string prop)\n        => el.TryGetProperty(prop, out var val) ? val.GetString() ?? \"\" : \"\";\n}\n",[3389,10009,10010,10026,10038,10054,10058,10073,10102,10106,10111,10116,10133,10137,10142,10168,10172,10209,10213,10217,10221,10246,10272,10287,10302,10306,10319,10323,10357,10364,10368,10372,10388,10392,10397,10424,10428,10433,10459,10463,10468,10473,10496,10500,10505,10515,10519,10542,10546,10566,10581,10585,10589,10610,10614,10619,10623,10647,10651,10673,10688,10692,10702,10706,10710,10715,10719,10749,10753,10769,10777,10781,10786,10790,10794,10828,10854,10880,10896,10900,10930,10934,10939,10970,11002,11006,11011,11016,11033,11037,11054,11070,11086,11100,11105,11109,11122,11126,11174,11235,11262,11328,11337,11341,11345,11360,11371,11375,11379,11391,11403,11407,11443,11447,11451,11455,11480,11489,11493,11505,11510,11523,11528,11533,11568,11573,11579,11585,11626,11646,11662,11687,11692,11717,11722,11734,11742,11747,11752,11758,11768,11789,11794,11829,11849,11854,11871,11879,11885,11932,11937,11942,11947,11975,11980,11985,12010,12037,12042,12070,12099,12104,12109,12114,12120,12130,12135,12167,12214],{"__ignoreMap":3391},[3417,10011,10012,10014,10016,10018,10020,10022,10024],{"class":3419,"line":3420},[3417,10013,6330],{"class":4033},[3417,10015,6333],{"class":6122},[3417,10017,4253],{"class":4037},[3417,10019,6356],{"class":6122},[3417,10021,4253],{"class":4037},[3417,10023,6361],{"class":6122},[3417,10025,6130],{"class":4037},[3417,10027,10028,10030,10032,10034,10036],{"class":3419,"line":3426},[3417,10029,6330],{"class":4033},[3417,10031,6333],{"class":6122},[3417,10033,4253],{"class":4037},[3417,10035,4600],{"class":6122},[3417,10037,6130],{"class":4037},[3417,10039,10040,10042,10044,10046,10048,10050,10052],{"class":3419,"line":3432},[3417,10041,6330],{"class":4033},[3417,10043,6333],{"class":6122},[3417,10045,4253],{"class":4037},[3417,10047,4600],{"class":6122},[3417,10049,4253],{"class":4037},[3417,10051,6390],{"class":6122},[3417,10053,6130],{"class":4037},[3417,10055,10056],{"class":3419,"line":3438},[3417,10057,3442],{"emptyLinePlaceholder":3441},[3417,10059,10060,10062,10064,10066,10068,10071],{"class":3419,"line":3445},[3417,10061,8948],{"class":6164},[3417,10063,4253],{"class":4037},[3417,10065,8203],{"class":6043},[3417,10067,6537],{"class":4037},[3417,10069,10070],{"class":4060},"\"Ваш нікнейм: \"",[3417,10072,6577],{"class":4037},[3417,10074,10075,10077,10079,10081,10083,10085,10088,10091,10094,10097,10100],{"class":3419,"line":3451},[3417,10076,6261],{"class":4041},[3417,10078,6542],{"class":6164},[3417,10080,6467],{"class":4037},[3417,10082,8948],{"class":6164},[3417,10084,4253],{"class":4037},[3417,10086,10087],{"class":6043},"ReadLine",[3417,10089,10090],{"class":4037},"()?.",[3417,10092,10093],{"class":6043},"Trim",[3417,10095,10096],{"class":4037},"() ?? ",[3417,10098,10099],{"class":4060},"\"Anonymous\"",[3417,10101,6130],{"class":4037},[3417,10103,10104],{"class":3419,"line":3457},[3417,10105,3442],{"emptyLinePlaceholder":3441},[3417,10107,10108],{"class":3419,"line":3462},[3417,10109,10110],{"class":6183},"\u002F\u002F ClientWebSocket — нативний WS-клієнт .NET\n",[3417,10112,10113],{"class":3419,"line":3468},[3417,10114,10115],{"class":6183},"\u002F\u002F Виконує HTTP Upgrade Handshake автоматично при ConnectAsync\n",[3417,10117,10118,10120,10122,10124,10126,10128,10131],{"class":3419,"line":3474},[3417,10119,6330],{"class":4033},[3417,10121,8023],{"class":4041},[3417,10123,7461],{"class":6164},[3417,10125,6467],{"class":4037},[3417,10127,6470],{"class":4041},[3417,10129,10130],{"class":6122}," ClientWebSocket",[3417,10132,6473],{"class":4037},[3417,10134,10135],{"class":3419,"line":3480},[3417,10136,3442],{"emptyLinePlaceholder":3441},[3417,10138,10139],{"class":3419,"line":3486},[3417,10140,10141],{"class":6183},"\u002F\u002F Додаємо заголовок для ідентифікації клієнта (опціонально)\n",[3417,10143,10144,10146,10148,10151,10153,10156,10158,10161,10163,10166],{"class":3419,"line":3492},[3417,10145,7511],{"class":6164},[3417,10147,4253],{"class":4037},[3417,10149,10150],{"class":6164},"Options",[3417,10152,4253],{"class":4037},[3417,10154,10155],{"class":6043},"SetRequestHeader",[3417,10157,6537],{"class":4037},[3417,10159,10160],{"class":4060},"\"X-Client-App\"",[3417,10162,4246],{"class":4037},[3417,10164,10165],{"class":4060},"\"ConsoleChat\u002F1.0\"",[3417,10167,6577],{"class":4037},[3417,10169,10170],{"class":3419,"line":3498},[3417,10171,3442],{"emptyLinePlaceholder":3441},[3417,10173,10174,10177,10180,10182,10184,10186,10189,10191,10193,10195,10198,10200,10202,10205,10207],{"class":3419,"line":3504},[3417,10175,10176],{"class":6122},"Uri",[3417,10178,10179],{"class":6164}," serverUri",[3417,10181,6467],{"class":4037},[3417,10183,6470],{"class":4041},[3417,10185,6537],{"class":4037},[3417,10187,10188],{"class":4060},"$\"ws:\u002F\u002Flocalhost:5000\u002Fws?user=",[3417,10190,5400],{"class":7550},[3417,10192,10176],{"class":6164},[3417,10194,4253],{"class":7550},[3417,10196,10197],{"class":6043},"EscapeDataString",[3417,10199,6537],{"class":7550},[3417,10201,6569],{"class":6164},[3417,10203,10204],{"class":7550},")}",[3417,10206,7622],{"class":4060},[3417,10208,6577],{"class":4037},[3417,10210,10211],{"class":3419,"line":3509},[3417,10212,3442],{"emptyLinePlaceholder":3441},[3417,10214,10215],{"class":3419,"line":3515},[3417,10216,9066],{"class":4033},[3417,10218,10219],{"class":3419,"line":3521},[3417,10220,6150],{"class":4037},[3417,10222,10223,10225,10227,10229,10231,10234,10236,10239,10241,10244],{"class":3419,"line":3527},[3417,10224,9214],{"class":6164},[3417,10226,4253],{"class":4037},[3417,10228,7589],{"class":6043},[3417,10230,6537],{"class":4037},[3417,10232,10233],{"class":4060},"$\"Підключення до ",[3417,10235,5400],{"class":7550},[3417,10237,10238],{"class":6164},"serverUri",[3417,10240,7555],{"class":7550},[3417,10242,10243],{"class":4060},"...\"",[3417,10245,6577],{"class":4037},[3417,10247,10248,10251,10253,10255,10258,10260,10262,10264,10266,10268,10270],{"class":3419,"line":3533},[3417,10249,10250],{"class":4041},"    await",[3417,10252,7461],{"class":6164},[3417,10254,4253],{"class":4037},[3417,10256,10257],{"class":6043},"ConnectAsync",[3417,10259,6537],{"class":4037},[3417,10261,10238],{"class":6164},[3417,10263,4246],{"class":4037},[3417,10265,7232],{"class":6164},[3417,10267,4253],{"class":4037},[3417,10269,7237],{"class":6164},[3417,10271,6577],{"class":4037},[3417,10273,10274,10276,10278,10280,10282,10285],{"class":3419,"line":3538},[3417,10275,9214],{"class":6164},[3417,10277,4253],{"class":4037},[3417,10279,7589],{"class":6043},[3417,10281,6537],{"class":4037},[3417,10283,10284],{"class":4060},"\"✅ Підключено! Введіть повідомлення (або \u002Fprivate \u003Cuser> \u003Ctext>):\"",[3417,10286,6577],{"class":4037},[3417,10288,10289,10291,10293,10295,10297,10300],{"class":3419,"line":3543},[3417,10290,9214],{"class":6164},[3417,10292,4253],{"class":4037},[3417,10294,7589],{"class":6043},[3417,10296,6537],{"class":4037},[3417,10298,10299],{"class":4060},"\"─────────────────────────────────────────\"",[3417,10301,6577],{"class":4037},[3417,10303,10304],{"class":3419,"line":3549},[3417,10305,3869],{"class":4037},[3417,10307,10308,10310,10312,10314,10317],{"class":3419,"line":3555},[3417,10309,9163],{"class":4033},[3417,10311,7254],{"class":4037},[3417,10313,7257],{"class":6122},[3417,10315,10316],{"class":6164}," ex",[3417,10318,6552],{"class":4037},[3417,10320,10321],{"class":3419,"line":3560},[3417,10322,6150],{"class":4037},[3417,10324,10325,10327,10329,10332,10334,10336,10338,10341,10343,10346,10348,10351,10353,10355],{"class":3419,"line":3565},[3417,10326,9214],{"class":6164},[3417,10328,4253],{"class":4037},[3417,10330,10331],{"class":6164},"Error",[3417,10333,4253],{"class":4037},[3417,10335,7589],{"class":6043},[3417,10337,6537],{"class":4037},[3417,10339,10340],{"class":4060},"$\"❌ Не вдалось підключитись: ",[3417,10342,5400],{"class":7550},[3417,10344,10345],{"class":6164},"ex",[3417,10347,4253],{"class":7550},[3417,10349,10350],{"class":6164},"Message",[3417,10352,7555],{"class":7550},[3417,10354,7622],{"class":4060},[3417,10356,6577],{"class":4037},[3417,10358,10359,10362],{"class":3419,"line":3571},[3417,10360,10361],{"class":4033},"    return",[3417,10363,6130],{"class":4037},[3417,10365,10366],{"class":3419,"line":3576},[3417,10367,3869],{"class":4037},[3417,10369,10370],{"class":3419,"line":3965},[3417,10371,3442],{"emptyLinePlaceholder":3441},[3417,10373,10374,10376,10378,10380,10382,10384,10386],{"class":3419,"line":3970},[3417,10375,6330],{"class":4033},[3417,10377,8023],{"class":4041},[3417,10379,9002],{"class":6164},[3417,10381,6467],{"class":4037},[3417,10383,6470],{"class":4041},[3417,10385,9009],{"class":6122},[3417,10387,6473],{"class":4037},[3417,10389,10390],{"class":3419,"line":4410},[3417,10391,3442],{"emptyLinePlaceholder":3441},[3417,10393,10394],{"class":3419,"line":4415},[3417,10395,10396],{"class":6183},"\u002F\u002F Задача 1: отримання повідомлень від сервера (безперервно у фоні)\n",[3417,10398,10399,10402,10405,10407,10410,10412,10414,10416,10418,10420,10422],{"class":3419,"line":4964},[3417,10400,10401],{"class":6122},"Task",[3417,10403,10404],{"class":6164}," receiveTask",[3417,10406,6467],{"class":4037},[3417,10408,10409],{"class":6043},"ReceiveLoopAsync",[3417,10411,6537],{"class":4037},[3417,10413,7511],{"class":6164},[3417,10415,4246],{"class":4037},[3417,10417,9050],{"class":6164},[3417,10419,4253],{"class":4037},[3417,10421,9084],{"class":6164},[3417,10423,6577],{"class":4037},[3417,10425,10426],{"class":3419,"line":4969},[3417,10427,3442],{"emptyLinePlaceholder":3441},[3417,10429,10430],{"class":3419,"line":5876},[3417,10431,10432],{"class":6183},"\u002F\u002F Задача 2: читання введення користувача з консолі (головний потік)\n",[3417,10434,10435,10437,10440,10442,10445,10447,10449,10451,10453,10455,10457],{"class":3419,"line":5882},[3417,10436,10401],{"class":6122},[3417,10438,10439],{"class":6164}," inputTask",[3417,10441,6467],{"class":4037},[3417,10443,10444],{"class":6043},"SendLoopAsync",[3417,10446,6537],{"class":4037},[3417,10448,7511],{"class":6164},[3417,10450,4246],{"class":4037},[3417,10452,6569],{"class":6164},[3417,10454,4246],{"class":4037},[3417,10456,9050],{"class":6164},[3417,10458,6577],{"class":4037},[3417,10460,10461],{"class":3419,"line":5888},[3417,10462,3442],{"emptyLinePlaceholder":3441},[3417,10464,10465],{"class":3419,"line":5893},[3417,10466,10467],{"class":6183},"\u002F\u002F Чекаємо завершення будь-якої з задач\n",[3417,10469,10470],{"class":3419,"line":5898},[3417,10471,10472],{"class":6183},"\u002F\u002F (або розрив з'єднання, або Ctrl+C від користувача)\n",[3417,10474,10475,10477,10479,10481,10484,10486,10489,10491,10494],{"class":3419,"line":5903},[3417,10476,7778],{"class":4041},[3417,10478,6675],{"class":6164},[3417,10480,4253],{"class":4037},[3417,10482,10483],{"class":6043},"WhenAny",[3417,10485,6537],{"class":4037},[3417,10487,10488],{"class":6164},"receiveTask",[3417,10490,4246],{"class":4037},[3417,10492,10493],{"class":6164},"inputTask",[3417,10495,6577],{"class":4037},[3417,10497,10498],{"class":3419,"line":5908},[3417,10499,3442],{"emptyLinePlaceholder":3441},[3417,10501,10502],{"class":3419,"line":5914},[3417,10503,10504],{"class":6183},"\u002F\u002F Коректне закриття: сигналізуємо скасування другій задачі\n",[3417,10506,10507,10509,10511,10513],{"class":3419,"line":5920},[3417,10508,9050],{"class":6164},[3417,10510,4253],{"class":4037},[3417,10512,9041],{"class":6043},[3417,10514,6473],{"class":4037},[3417,10516,10517],{"class":3419,"line":5925},[3417,10518,3442],{"emptyLinePlaceholder":3441},[3417,10520,10521,10524,10526,10528,10530,10532,10534,10536,10538,10540],{"class":3419,"line":7017},[3417,10522,10523],{"class":4033},"if",[3417,10525,7254],{"class":4037},[3417,10527,7511],{"class":6164},[3417,10529,4253],{"class":4037},[3417,10531,6851],{"class":6164},[3417,10533,6854],{"class":4037},[3417,10535,6857],{"class":6164},[3417,10537,4253],{"class":4037},[3417,10539,6862],{"class":6164},[3417,10541,6552],{"class":4037},[3417,10543,10544],{"class":3419,"line":7028},[3417,10545,6150],{"class":4037},[3417,10547,10548,10550,10552,10554,10556,10558,10560,10562,10564],{"class":3419,"line":7033},[3417,10549,10250],{"class":4041},[3417,10551,7461],{"class":6164},[3417,10553,4253],{"class":4037},[3417,10555,7529],{"class":6043},[3417,10557,6537],{"class":4037},[3417,10559,7535],{"class":6164},[3417,10561,4253],{"class":4037},[3417,10563,8162],{"class":6164},[3417,10565,7216],{"class":4037},[3417,10567,10568,10571,10573,10575,10577,10579],{"class":3419,"line":7056},[3417,10569,10570],{"class":4060},"        \"Client closing\"",[3417,10572,4246],{"class":4037},[3417,10574,7232],{"class":6164},[3417,10576,4253],{"class":4037},[3417,10578,7237],{"class":6164},[3417,10580,6577],{"class":4037},[3417,10582,10583],{"class":3419,"line":7075},[3417,10584,3869],{"class":4037},[3417,10586,10587],{"class":3419,"line":7105},[3417,10588,3442],{"emptyLinePlaceholder":3441},[3417,10590,10591,10593,10595,10597,10599,10601,10605,10608],{"class":3419,"line":7116},[3417,10592,8948],{"class":6164},[3417,10594,4253],{"class":4037},[3417,10596,7589],{"class":6043},[3417,10598,6537],{"class":4037},[3417,10600,7622],{"class":4060},[3417,10602,10604],{"class":10603},"sjcCO","\\n",[3417,10606,10607],{"class":4060},"Відключено. До побачення!\"",[3417,10609,6577],{"class":4037},[3417,10611,10612],{"class":3419,"line":7121},[3417,10613,3442],{"emptyLinePlaceholder":3441},[3417,10615,10616],{"class":3419,"line":7126},[3417,10617,10618],{"class":6183},"\u002F\u002F ─── Методи ────────────────────────────────────────────────────────────\n",[3417,10620,10621],{"class":3419,"line":7132},[3417,10622,3442],{"emptyLinePlaceholder":3441},[3417,10624,10625,10627,10629,10631,10633,10635,10637,10639,10641,10643,10645],{"class":3419,"line":7138},[3417,10626,9243],{"class":4041},[3417,10628,6672],{"class":4041},[3417,10630,6675],{"class":6122},[3417,10632,7781],{"class":6043},[3417,10634,6537],{"class":4037},[3417,10636,4799],{"class":6122},[3417,10638,7461],{"class":6164},[3417,10640,4246],{"class":4037},[3417,10642,7232],{"class":6122},[3417,10644,7477],{"class":6164},[3417,10646,6552],{"class":4037},[3417,10648,10649],{"class":3419,"line":7173},[3417,10650,6150],{"class":4037},[3417,10652,10653,10656,10658,10660,10662,10664,10666,10668,10671],{"class":3419,"line":7178},[3417,10654,10655],{"class":4041},"    byte",[3417,10657,6714],{"class":4037},[3417,10659,7959],{"class":6164},[3417,10661,6467],{"class":4037},[3417,10663,6470],{"class":4041},[3417,10665,7966],{"class":4041},[3417,10667,7969],{"class":4037},[3417,10669,10670],{"class":4048},"8192",[3417,10672,6661],{"class":4037},[3417,10674,10675,10678,10681,10683,10685],{"class":3419,"line":7184},[3417,10676,10677],{"class":4041},"    var",[3417,10679,10680],{"class":6164}," jsonOptions",[3417,10682,6467],{"class":4037},[3417,10684,6470],{"class":4041},[3417,10686,10687],{"class":6122}," JsonSerializerOptions\n",[3417,10689,10690],{"class":3419,"line":7190},[3417,10691,6500],{"class":4037},[3417,10693,10694,10697,10699],{"class":3419,"line":7219},[3417,10695,10696],{"class":6164},"        PropertyNameCaseInsensitive",[3417,10698,6467],{"class":4037},[3417,10700,10701],{"class":4041},"true\n",[3417,10703,10704],{"class":3419,"line":7242},[3417,10705,6520],{"class":4037},[3417,10707,10708],{"class":3419,"line":7248},[3417,10709,3442],{"emptyLinePlaceholder":3441},[3417,10711,10712],{"class":3419,"line":7269},[3417,10713,10714],{"class":4033},"    try\n",[3417,10716,10717],{"class":3419,"line":7274},[3417,10718,6500],{"class":4037},[3417,10720,10721,10723,10725,10727,10729,10731,10733,10735,10737,10739,10741,10743,10745,10747],{"class":3419,"line":8052},[3417,10722,7983],{"class":4033},[3417,10724,7254],{"class":4037},[3417,10726,7511],{"class":6164},[3417,10728,4253],{"class":4037},[3417,10730,6851],{"class":6164},[3417,10732,6854],{"class":4037},[3417,10734,6857],{"class":6164},[3417,10736,4253],{"class":4037},[3417,10738,6862],{"class":6164},[3417,10740,8002],{"class":4037},[3417,10742,7563],{"class":6164},[3417,10744,4253],{"class":4037},[3417,10746,8009],{"class":6164},[3417,10748,6552],{"class":4037},[3417,10750,10751],{"class":3419,"line":8058},[3417,10752,7187],{"class":4037},[3417,10754,10755,10757,10759,10761,10763,10765,10767],{"class":3419,"line":8064},[3417,10756,8020],{"class":4033},[3417,10758,8023],{"class":4041},[3417,10760,8026],{"class":6164},[3417,10762,6467],{"class":4037},[3417,10764,6470],{"class":4041},[3417,10766,8033],{"class":6122},[3417,10768,6473],{"class":4037},[3417,10770,10771,10773,10775],{"class":3419,"line":8069},[3417,10772,8040],{"class":6122},[3417,10774,8043],{"class":6164},[3417,10776,6130],{"class":4037},[3417,10778,10779],{"class":3419,"line":8107},[3417,10780,3442],{"emptyLinePlaceholder":3441},[3417,10782,10783],{"class":3419,"line":8112},[3417,10784,10785],{"class":6183},"            \u002F\u002F Читаємо, поки не отримаємо повне повідомлення\n",[3417,10787,10788],{"class":3419,"line":8138},[3417,10789,8061],{"class":4033},[3417,10791,10792],{"class":3419,"line":8144},[3417,10793,7867],{"class":4037},[3417,10795,10796,10798,10800,10802,10804,10806,10808,10810,10812,10814,10816,10818,10820,10822,10824,10826],{"class":3419,"line":8176},[3417,10797,8072],{"class":6164},[3417,10799,6467],{"class":4037},[3417,10801,7778],{"class":4041},[3417,10803,7461],{"class":6164},[3417,10805,4253],{"class":4037},[3417,10807,8083],{"class":6043},[3417,10809,6537],{"class":4037},[3417,10811,6470],{"class":4041},[3417,10813,7091],{"class":6122},[3417,10815,6258],{"class":4037},[3417,10817,6768],{"class":4041},[3417,10819,7098],{"class":4037},[3417,10821,7959],{"class":6164},[3417,10823,8100],{"class":4037},[3417,10825,7563],{"class":6164},[3417,10827,6577],{"class":4037},[3417,10829,10830,10832,10834,10836,10838,10840,10842,10844,10846,10848,10850,10852],{"class":3419,"line":8184},[3417,10831,8115],{"class":4033},[3417,10833,7254],{"class":4037},[3417,10835,8120],{"class":6164},[3417,10837,4253],{"class":4037},[3417,10839,8125],{"class":6164},[3417,10841,6854],{"class":4037},[3417,10843,7209],{"class":6164},[3417,10845,4253],{"class":4037},[3417,10847,4648],{"class":6164},[3417,10849,8271],{"class":4037},[3417,10851,8426],{"class":4033},[3417,10853,6130],{"class":4037},[3417,10855,10856,10858,10860,10862,10864,10866,10868,10870,10872,10874,10876,10878],{"class":3419,"line":8190},[3417,10857,8198],{"class":6164},[3417,10859,4253],{"class":4037},[3417,10861,8203],{"class":6043},[3417,10863,6537],{"class":4037},[3417,10865,7959],{"class":6164},[3417,10867,4246],{"class":4037},[3417,10869,4453],{"class":4048},[3417,10871,4246],{"class":4037},[3417,10873,8120],{"class":6164},[3417,10875,4253],{"class":4037},[3417,10877,7617],{"class":6164},[3417,10879,6577],{"class":4037},[3417,10881,10882,10884,10886,10888,10890,10892,10894],{"class":3419,"line":8195},[3417,10883,8227],{"class":4037},[3417,10885,8230],{"class":4033},[3417,10887,6969],{"class":4037},[3417,10889,8120],{"class":6164},[3417,10891,4253],{"class":4037},[3417,10893,8239],{"class":6164},[3417,10895,6577],{"class":4037},[3417,10897,10898],{"class":3419,"line":8224},[3417,10899,3442],{"emptyLinePlaceholder":3441},[3417,10901,10902,10905,10907,10909,10911,10913,10915,10917,10919,10921,10923,10925,10927],{"class":3419,"line":8244},[3417,10903,10904],{"class":4041},"            string",[3417,10906,8363],{"class":6164},[3417,10908,6467],{"class":4037},[3417,10910,6722],{"class":6164},[3417,10912,4253],{"class":4037},[3417,10914,6727],{"class":6164},[3417,10916,4253],{"class":4037},[3417,10918,8310],{"class":6043},[3417,10920,6537],{"class":4037},[3417,10922,8315],{"class":6164},[3417,10924,4253],{"class":4037},[3417,10926,8320],{"class":6043},[3417,10928,10929],{"class":4037},"());\n",[3417,10931,10932],{"class":3419,"line":8249},[3417,10933,3442],{"emptyLinePlaceholder":3441},[3417,10935,10936],{"class":3419,"line":8279},[3417,10937,10938],{"class":6183},"            \u002F\u002F Десеріалізуємо та відображаємо відповідно до типу\n",[3417,10940,10941,10944,10946,10948,10950,10952,10954,10956,10959,10961,10963,10965,10968],{"class":3419,"line":8284},[3417,10942,10943],{"class":4041},"            var",[3417,10945,8577],{"class":6164},[3417,10947,6467],{"class":4037},[3417,10949,8397],{"class":6164},[3417,10951,4253],{"class":4037},[3417,10953,8402],{"class":6043},[3417,10955,6258],{"class":4037},[3417,10957,10958],{"class":6122},"JsonElement",[3417,10960,7098],{"class":4037},[3417,10962,5393],{"class":6164},[3417,10964,4246],{"class":4037},[3417,10966,10967],{"class":6164},"jsonOptions",[3417,10969,6577],{"class":4037},[3417,10971,10972,10974,10977,10979,10981,10983,10986,10988,10990,10993,10995,10997,11000],{"class":3419,"line":8298},[3417,10973,10904],{"class":4041},[3417,10975,10976],{"class":6164}," type",[3417,10978,6467],{"class":4037},[3417,10980,8381],{"class":6164},[3417,10982,4253],{"class":4037},[3417,10984,10985],{"class":6043},"GetProperty",[3417,10987,6537],{"class":4037},[3417,10989,5404],{"class":4060},[3417,10991,10992],{"class":4037},").",[3417,10994,8310],{"class":6043},[3417,10996,10096],{"class":4037},[3417,10998,10999],{"class":4060},"\"\"",[3417,11001,6130],{"class":4037},[3417,11003,11004],{"class":3419,"line":8326},[3417,11005,3442],{"emptyLinePlaceholder":3441},[3417,11007,11008],{"class":3419,"line":8331},[3417,11009,11010],{"class":6183},"            \u002F\u002F Зберігаємо поточний курсор і перемальовуємо повідомлення\n",[3417,11012,11013],{"class":3419,"line":8336},[3417,11014,11015],{"class":6183},"            \u002F\u002F вище рядка введення (спрощена версія без ncurses)\n",[3417,11017,11018,11020,11022,11025,11027,11030],{"class":3419,"line":8341},[3417,11019,7826],{"class":6164},[3417,11021,4253],{"class":4037},[3417,11023,11024],{"class":6164},"ForegroundColor",[3417,11026,6467],{"class":4037},[3417,11028,11029],{"class":6164},"type",[3417,11031,11032],{"class":4033}," switch\n",[3417,11034,11035],{"class":3419,"line":8368},[3417,11036,7867],{"class":4037},[3417,11038,11039,11042,11044,11047,11049,11052],{"class":3419,"line":8373},[3417,11040,11041],{"class":4060},"                \"system\"",[3417,11043,6821],{"class":4037},[3417,11045,11046],{"class":6164},"ConsoleColor",[3417,11048,4253],{"class":4037},[3417,11050,11051],{"class":6164},"Yellow",[3417,11053,7216],{"class":4037},[3417,11055,11056,11059,11061,11063,11065,11068],{"class":3419,"line":8386},[3417,11057,11058],{"class":4060},"                \"private\"",[3417,11060,6821],{"class":4037},[3417,11062,11046],{"class":6164},[3417,11064,4253],{"class":4037},[3417,11066,11067],{"class":6164},"Magenta",[3417,11069,7216],{"class":4037},[3417,11071,11072,11075,11077,11079,11081,11084],{"class":3419,"line":8419},[3417,11073,11074],{"class":4060},"                \"users\"",[3417,11076,6821],{"class":4037},[3417,11078,11046],{"class":6164},[3417,11080,4253],{"class":4037},[3417,11082,11083],{"class":6164},"Cyan",[3417,11085,7216],{"class":4037},[3417,11087,11088,11091,11093,11095,11097],{"class":3419,"line":8431},[3417,11089,11090],{"class":4041},"                _",[3417,11092,6821],{"class":4037},[3417,11094,11046],{"class":6164},[3417,11096,4253],{"class":4037},[3417,11098,11099],{"class":6164},"White\n",[3417,11101,11102],{"class":3419,"line":8452},[3417,11103,11104],{"class":4037},"            };\n",[3417,11106,11107],{"class":3419,"line":8457},[3417,11108,3442],{"emptyLinePlaceholder":3441},[3417,11110,11111,11113,11116,11118,11120],{"class":3419,"line":8474},[3417,11112,10904],{"class":4041},[3417,11114,11115],{"class":6164}," displayText",[3417,11117,6467],{"class":4037},[3417,11119,11029],{"class":6164},[3417,11121,11032],{"class":4033},[3417,11123,11124],{"class":3419,"line":8479},[3417,11125,7867],{"class":4037},[3417,11127,11128,11131,11133,11136,11138,11140,11142,11145,11147,11150,11152,11155,11157,11159,11161,11163,11165,11168,11170,11172],{"class":3419,"line":8491},[3417,11129,11130],{"class":4060},"                \"chat\"",[3417,11132,6821],{"class":4037},[3417,11134,11135],{"class":4060},"$\"[",[3417,11137,5400],{"class":7550},[3417,11139,8381],{"class":6164},[3417,11141,4253],{"class":7550},[3417,11143,11144],{"class":6043},"TryGetProp",[3417,11146,6537],{"class":7550},[3417,11148,11149],{"class":4060},"\"from\"",[3417,11151,10204],{"class":7550},[3417,11153,11154],{"class":4060},"]: ",[3417,11156,5400],{"class":7550},[3417,11158,8381],{"class":6164},[3417,11160,4253],{"class":7550},[3417,11162,11144],{"class":6043},[3417,11164,6537],{"class":7550},[3417,11166,11167],{"class":4060},"\"text\"",[3417,11169,10204],{"class":7550},[3417,11171,7622],{"class":4060},[3417,11173,7216],{"class":4037},[3417,11175,11176,11178,11180,11183,11185,11187,11189,11191,11193,11195,11197,11200,11202,11204,11206,11208,11210,11213,11215,11217,11219,11221,11223,11225,11227,11229,11231,11233],{"class":3419,"line":8509},[3417,11177,11058],{"class":4060},[3417,11179,6821],{"class":4037},[3417,11181,11182],{"class":4060},"$\"[🔒 ",[3417,11184,5400],{"class":7550},[3417,11186,8381],{"class":6164},[3417,11188,4253],{"class":7550},[3417,11190,11144],{"class":6043},[3417,11192,6537],{"class":7550},[3417,11194,11149],{"class":4060},[3417,11196,10204],{"class":7550},[3417,11198,11199],{"class":4060}," → ",[3417,11201,5400],{"class":7550},[3417,11203,8381],{"class":6164},[3417,11205,4253],{"class":7550},[3417,11207,11144],{"class":6043},[3417,11209,6537],{"class":7550},[3417,11211,11212],{"class":4060},"\"to\"",[3417,11214,10204],{"class":7550},[3417,11216,11154],{"class":4060},[3417,11218,5400],{"class":7550},[3417,11220,8381],{"class":6164},[3417,11222,4253],{"class":7550},[3417,11224,11144],{"class":6043},[3417,11226,6537],{"class":7550},[3417,11228,11167],{"class":4060},[3417,11230,10204],{"class":7550},[3417,11232,7622],{"class":4060},[3417,11234,7216],{"class":4037},[3417,11236,11237,11239,11241,11244,11246,11248,11250,11252,11254,11256,11258,11260],{"class":3419,"line":8514},[3417,11238,11041],{"class":4060},[3417,11240,6821],{"class":4037},[3417,11242,11243],{"class":4060},"$\"  ",[3417,11245,5400],{"class":7550},[3417,11247,8381],{"class":6164},[3417,11249,4253],{"class":7550},[3417,11251,11144],{"class":6043},[3417,11253,6537],{"class":7550},[3417,11255,11167],{"class":4060},[3417,11257,10204],{"class":7550},[3417,11259,7622],{"class":4060},[3417,11261,7216],{"class":4037},[3417,11263,11264,11266,11268,11271,11273,11275,11277,11280,11282,11285,11287,11289,11291,11293,11295,11297,11299,11302,11304,11306,11308,11311,11314,11317,11319,11321,11324,11326],{"class":3419,"line":8547},[3417,11265,11074],{"class":4060},[3417,11267,6821],{"class":4037},[3417,11269,11270],{"class":4060},"$\"  👥 Онлайн: ",[3417,11272,5400],{"class":7550},[3417,11274,6261],{"class":4041},[3417,11276,4253],{"class":7550},[3417,11278,11279],{"class":6043},"Join",[3417,11281,6537],{"class":7550},[3417,11283,11284],{"class":4060},"\", \"",[3417,11286,4246],{"class":7550},[3417,11288,8381],{"class":6164},[3417,11290,4253],{"class":7550},[3417,11292,10985],{"class":6043},[3417,11294,6537],{"class":7550},[3417,11296,7740],{"class":4060},[3417,11298,10992],{"class":7550},[3417,11300,11301],{"class":6043},"EnumerateArray",[3417,11303,7614],{"class":7550},[3417,11305,6871],{"class":6043},[3417,11307,6537],{"class":7550},[3417,11309,11310],{"class":6164},"u",[3417,11312,11313],{"class":4037}," =>",[3417,11315,11316],{"class":6164}," u",[3417,11318,4253],{"class":7550},[3417,11320,8310],{"class":6043},[3417,11322,11323],{"class":7550},"()))}",[3417,11325,7622],{"class":4060},[3417,11327,7216],{"class":4037},[3417,11329,11330,11332,11334],{"class":3419,"line":8553},[3417,11331,11090],{"class":4041},[3417,11333,6821],{"class":4037},[3417,11335,11336],{"class":6164},"json\n",[3417,11338,11339],{"class":3419,"line":8561},[3417,11340,11104],{"class":4037},[3417,11342,11343],{"class":3419,"line":8566},[3417,11344,3442],{"emptyLinePlaceholder":3441},[3417,11346,11347,11349,11351,11353,11355,11358],{"class":3419,"line":8593},[3417,11348,7826],{"class":6164},[3417,11350,4253],{"class":4037},[3417,11352,7589],{"class":6043},[3417,11354,6537],{"class":4037},[3417,11356,11357],{"class":6164},"displayText",[3417,11359,6577],{"class":4037},[3417,11361,11362,11364,11366,11369],{"class":3419,"line":8608},[3417,11363,7826],{"class":6164},[3417,11365,4253],{"class":4037},[3417,11367,11368],{"class":6043},"ResetColor",[3417,11370,6473],{"class":4037},[3417,11372,11373],{"class":3419,"line":8613},[3417,11374,7245],{"class":4037},[3417,11376,11377],{"class":3419,"line":8655},[3417,11378,5765],{"class":4037},[3417,11380,11381,11384,11386,11389],{"class":3419,"line":8661},[3417,11382,11383],{"class":4033},"    catch",[3417,11385,7254],{"class":4037},[3417,11387,11388],{"class":6122},"OperationCanceledException",[3417,11390,9188],{"class":4037},[3417,11392,11393,11395,11397,11399,11401],{"class":3419,"line":8695},[3417,11394,11383],{"class":4033},[3417,11396,7254],{"class":4037},[3417,11398,7257],{"class":6122},[3417,11400,10316],{"class":6164},[3417,11402,6552],{"class":4037},[3417,11404,11405],{"class":3419,"line":8701},[3417,11406,6500],{"class":4037},[3417,11408,11409,11411,11413,11415,11417,11419,11421,11424,11426,11429,11431,11433,11435,11437,11439,11441],{"class":3419,"line":8733},[3417,11410,7584],{"class":6164},[3417,11412,4253],{"class":4037},[3417,11414,10331],{"class":6164},[3417,11416,4253],{"class":4037},[3417,11418,7589],{"class":6043},[3417,11420,6537],{"class":4037},[3417,11422,11423],{"class":4060},"$\"",[3417,11425,10604],{"class":10603},[3417,11427,11428],{"class":4060},"⚠️ З'єднання розірвано: ",[3417,11430,5400],{"class":7550},[3417,11432,10345],{"class":6164},[3417,11434,4253],{"class":7550},[3417,11436,10350],{"class":6164},[3417,11438,7555],{"class":7550},[3417,11440,7622],{"class":4060},[3417,11442,6577],{"class":4037},[3417,11444,11445],{"class":3419,"line":8738},[3417,11446,5765],{"class":4037},[3417,11448,11449],{"class":3419,"line":8749},[3417,11450,3869],{"class":4037},[3417,11452,11453],{"class":3419,"line":8773},[3417,11454,3442],{"emptyLinePlaceholder":3441},[3417,11456,11457,11459,11461,11463,11466,11468,11470,11472,11474,11476,11478],{"class":3419,"line":8778},[3417,11458,9243],{"class":4041},[3417,11460,6672],{"class":4041},[3417,11462,6675],{"class":6122},[3417,11464,11465],{"class":6043}," SendLoopAsync",[3417,11467,6537],{"class":4037},[3417,11469,4799],{"class":6122},[3417,11471,7461],{"class":6164},[3417,11473,4246],{"class":4037},[3417,11475,6261],{"class":4041},[3417,11477,6542],{"class":6164},[3417,11479,7216],{"class":4037},[3417,11481,11482,11485,11487],{"class":3419,"line":8785},[3417,11483,11484],{"class":6122},"    CancellationTokenSource",[3417,11486,9002],{"class":6164},[3417,11488,6552],{"class":4037},[3417,11490,11491],{"class":3419,"line":8790},[3417,11492,6150],{"class":4037},[3417,11494,11495,11497,11499,11501,11503],{"class":3419,"line":8795},[3417,11496,10677],{"class":4041},[3417,11498,10680],{"class":6164},[3417,11500,6467],{"class":4037},[3417,11502,6470],{"class":4041},[3417,11504,10687],{"class":6122},[3417,11506,11508],{"class":3419,"line":11507},118,[3417,11509,6500],{"class":4037},[3417,11511,11513,11515,11517,11519,11521],{"class":3419,"line":11512},119,[3417,11514,6505],{"class":6164},[3417,11516,6467],{"class":4037},[3417,11518,6510],{"class":6164},[3417,11520,4253],{"class":4037},[3417,11522,6515],{"class":6164},[3417,11524,11526],{"class":3419,"line":11525},120,[3417,11527,6520],{"class":4037},[3417,11529,11531],{"class":3419,"line":11530},121,[3417,11532,3442],{"emptyLinePlaceholder":3441},[3417,11534,11536,11538,11540,11542,11544,11546,11548,11550,11552,11554,11556,11558,11560,11562,11564,11566],{"class":3419,"line":11535},122,[3417,11537,9075],{"class":4033},[3417,11539,6969],{"class":4037},[3417,11541,9050],{"class":6164},[3417,11543,4253],{"class":4037},[3417,11545,9084],{"class":6164},[3417,11547,4253],{"class":4037},[3417,11549,8009],{"class":6164},[3417,11551,9344],{"class":4037},[3417,11553,7511],{"class":6164},[3417,11555,4253],{"class":4037},[3417,11557,6851],{"class":6164},[3417,11559,6854],{"class":4037},[3417,11561,6857],{"class":6164},[3417,11563,4253],{"class":4037},[3417,11565,6862],{"class":6164},[3417,11567,6552],{"class":4037},[3417,11569,11571],{"class":3419,"line":11570},123,[3417,11572,6500],{"class":4037},[3417,11574,11576],{"class":3419,"line":11575},124,[3417,11577,11578],{"class":6183},"        \u002F\u002F Console.ReadLine() блокує — не підходить для async.\n",[3417,11580,11582],{"class":3419,"line":11581},125,[3417,11583,11584],{"class":6183},"        \u002F\u002F У реальному застосунку — використовуйте спеціалізовану TUI-бібліотеку.\n",[3417,11586,11588,11590,11592,11595,11597,11599,11601,11603,11606,11609,11611,11613,11615,11618,11620,11622,11624],{"class":3419,"line":11587},126,[3417,11589,9363],{"class":4041},[3417,11591,6193],{"class":4037},[3417,11593,11594],{"class":6164},"input",[3417,11596,6467],{"class":4037},[3417,11598,7778],{"class":4041},[3417,11600,6675],{"class":6164},[3417,11602,4253],{"class":4037},[3417,11604,11605],{"class":6043},"Run",[3417,11607,11608],{"class":4037},"(() => ",[3417,11610,8948],{"class":6164},[3417,11612,4253],{"class":4037},[3417,11614,10087],{"class":6043},[3417,11616,11617],{"class":4037},"(), ",[3417,11619,9050],{"class":6164},[3417,11621,4253],{"class":4037},[3417,11623,9084],{"class":6164},[3417,11625,6577],{"class":4037},[3417,11627,11629,11631,11633,11635,11637,11639,11641,11644],{"class":3419,"line":11628},127,[3417,11630,6966],{"class":4033},[3417,11632,7254],{"class":4037},[3417,11634,11594],{"class":6164},[3417,11636,8440],{"class":4041},[3417,11638,8443],{"class":4041},[3417,11640,8271],{"class":4037},[3417,11642,11643],{"class":4033},"break",[3417,11645,6130],{"class":4037},[3417,11647,11649,11652,11654,11656,11658,11660],{"class":3419,"line":11648},128,[3417,11650,11651],{"class":6164},"        input",[3417,11653,6467],{"class":4037},[3417,11655,11594],{"class":6164},[3417,11657,4253],{"class":4037},[3417,11659,10093],{"class":6043},[3417,11661,6473],{"class":4037},[3417,11663,11665,11667,11669,11671,11673,11676,11678,11680,11683,11685],{"class":3419,"line":11664},129,[3417,11666,6966],{"class":4033},[3417,11668,7254],{"class":4037},[3417,11670,6261],{"class":4041},[3417,11672,4253],{"class":4037},[3417,11674,11675],{"class":6043},"IsNullOrEmpty",[3417,11677,6537],{"class":4037},[3417,11679,11594],{"class":6164},[3417,11681,11682],{"class":4037},")) ",[3417,11684,8274],{"class":4033},[3417,11686,6130],{"class":4037},[3417,11688,11690],{"class":3419,"line":11689},130,[3417,11691,3442],{"emptyLinePlaceholder":3441},[3417,11693,11695,11697,11699,11701,11703,11706,11708,11710,11712,11715],{"class":3419,"line":11694},131,[3417,11696,6966],{"class":4033},[3417,11698,7254],{"class":4037},[3417,11700,11594],{"class":6164},[3417,11702,6854],{"class":4037},[3417,11704,11705],{"class":4060},"\"\u002Fquit\"",[3417,11707,9654],{"class":4037},[3417,11709,11594],{"class":6164},[3417,11711,6854],{"class":4037},[3417,11713,11714],{"class":4060},"\"\u002Fexit\"",[3417,11716,6552],{"class":4037},[3417,11718,11720],{"class":3419,"line":11719},132,[3417,11721,7187],{"class":4037},[3417,11723,11725,11728,11730,11732],{"class":3419,"line":11724},133,[3417,11726,11727],{"class":6164},"            cts",[3417,11729,4253],{"class":4037},[3417,11731,9041],{"class":6043},[3417,11733,6473],{"class":4037},[3417,11735,11737,11740],{"class":3419,"line":11736},134,[3417,11738,11739],{"class":4033},"            break",[3417,11741,6130],{"class":4037},[3417,11743,11745],{"class":3419,"line":11744},135,[3417,11746,7245],{"class":4037},[3417,11748,11750],{"class":3419,"line":11749},136,[3417,11751,3442],{"emptyLinePlaceholder":3441},[3417,11753,11755],{"class":3419,"line":11754},137,[3417,11756,11757],{"class":6183},"        \u002F\u002F Формат приватного повідомлення: \u002Fprivate \u003Cusername> \u003Ctext>\n",[3417,11759,11761,11764,11766],{"class":3419,"line":11760},138,[3417,11762,11763],{"class":4041},"        object",[3417,11765,6686],{"class":6164},[3417,11767,6130],{"class":4037},[3417,11769,11771,11773,11775,11777,11779,11782,11784,11787],{"class":3419,"line":11770},139,[3417,11772,6966],{"class":4033},[3417,11774,7254],{"class":4037},[3417,11776,11594],{"class":6164},[3417,11778,4253],{"class":4037},[3417,11780,11781],{"class":6043},"StartsWith",[3417,11783,6537],{"class":4037},[3417,11785,11786],{"class":4060},"\"\u002Fprivate \"",[3417,11788,7514],{"class":4037},[3417,11790,11792],{"class":3419,"line":11791},140,[3417,11793,7187],{"class":4037},[3417,11795,11797,11799,11801,11804,11806,11808,11810,11812,11815,11818,11820,11823,11825,11827],{"class":3419,"line":11796},141,[3417,11798,10904],{"class":4041},[3417,11800,6714],{"class":4037},[3417,11802,11803],{"class":6164},"parts",[3417,11805,6467],{"class":4037},[3417,11807,11594],{"class":6164},[3417,11809,7969],{"class":4037},[3417,11811,4656],{"class":4048},[3417,11813,11814],{"class":4037},"..].",[3417,11816,11817],{"class":6043},"Split",[3417,11819,6537],{"class":4037},[3417,11821,11822],{"class":4060},"' '",[3417,11824,4246],{"class":4037},[3417,11826,4608],{"class":4048},[3417,11828,6577],{"class":4037},[3417,11830,11832,11834,11836,11838,11840,11842,11845,11847],{"class":3419,"line":11831},142,[3417,11833,8252],{"class":4033},[3417,11835,7254],{"class":4037},[3417,11837,11803],{"class":6164},[3417,11839,4253],{"class":4037},[3417,11841,9845],{"class":6164},[3417,11843,11844],{"class":4037}," \u003C ",[3417,11846,4608],{"class":4048},[3417,11848,6552],{"class":4037},[3417,11850,11852],{"class":3419,"line":11851},143,[3417,11853,7867],{"class":4037},[3417,11855,11857,11860,11862,11864,11866,11869],{"class":3419,"line":11856},144,[3417,11858,11859],{"class":6164},"                Console",[3417,11861,4253],{"class":4037},[3417,11863,7589],{"class":6043},[3417,11865,6537],{"class":4037},[3417,11867,11868],{"class":4060},"\"Використання: \u002Fprivate \u003Cuser> \u003Ctext>\"",[3417,11870,6577],{"class":4037},[3417,11872,11874,11877],{"class":3419,"line":11873},145,[3417,11875,11876],{"class":4033},"                continue",[3417,11878,6130],{"class":4037},[3417,11880,11882],{"class":3419,"line":11881},146,[3417,11883,11884],{"class":4037},"            }\n",[3417,11886,11888,11891,11893,11895,11897,11899,11901,11903,11905,11908,11910,11912,11914,11916,11919,11921,11923,11925,11927,11929],{"class":3419,"line":11887},147,[3417,11889,11890],{"class":6164},"            message",[3417,11892,6467],{"class":4037},[3417,11894,6470],{"class":4041},[3417,11896,6168],{"class":4037},[3417,11898,11029],{"class":6164},[3417,11900,6467],{"class":4037},[3417,11902,8620],{"class":4060},[3417,11904,4246],{"class":4037},[3417,11906,11907],{"class":6164},"to",[3417,11909,6467],{"class":4037},[3417,11911,11803],{"class":6164},[3417,11913,7969],{"class":4037},[3417,11915,4453],{"class":4048},[3417,11917,11918],{"class":4037},"], ",[3417,11920,3387],{"class":6164},[3417,11922,6467],{"class":4037},[3417,11924,11803],{"class":6164},[3417,11926,7969],{"class":4037},[3417,11928,4449],{"class":4048},[3417,11930,11931],{"class":4037},"] };\n",[3417,11933,11935],{"class":3419,"line":11934},148,[3417,11936,7245],{"class":4037},[3417,11938,11940],{"class":3419,"line":11939},149,[3417,11941,9884],{"class":4033},[3417,11943,11945],{"class":3419,"line":11944},150,[3417,11946,7187],{"class":4037},[3417,11948,11950,11952,11954,11956,11958,11960,11962,11964,11966,11968,11970,11972],{"class":3419,"line":11949},151,[3417,11951,11890],{"class":6164},[3417,11953,6467],{"class":4037},[3417,11955,6470],{"class":4041},[3417,11957,6168],{"class":4037},[3417,11959,11029],{"class":6164},[3417,11961,6467],{"class":4037},[3417,11963,8522],{"class":4060},[3417,11965,4246],{"class":4037},[3417,11967,3387],{"class":6164},[3417,11969,6467],{"class":4037},[3417,11971,11594],{"class":6164},[3417,11973,11974],{"class":4037}," };\n",[3417,11976,11978],{"class":3419,"line":11977},152,[3417,11979,7245],{"class":4037},[3417,11981,11983],{"class":3419,"line":11982},153,[3417,11984,3442],{"emptyLinePlaceholder":3441},[3417,11986,11988,11990,11992,11994,11996,11998,12000,12002,12004,12006,12008],{"class":3419,"line":11987},154,[3417,11989,9363],{"class":4041},[3417,11991,8363],{"class":6164},[3417,11993,6467],{"class":4037},[3417,11995,8397],{"class":6164},[3417,11997,4253],{"class":4037},[3417,11999,6745],{"class":6043},[3417,12001,6537],{"class":4037},[3417,12003,6750],{"class":6164},[3417,12005,4246],{"class":4037},[3417,12007,10967],{"class":6164},[3417,12009,6577],{"class":4037},[3417,12011,12013,12015,12017,12019,12021,12023,12025,12027,12029,12031,12033,12035],{"class":3419,"line":12012},155,[3417,12014,6711],{"class":4041},[3417,12016,6714],{"class":4037},[3417,12018,6717],{"class":6164},[3417,12020,6467],{"class":4037},[3417,12022,6722],{"class":6164},[3417,12024,4253],{"class":4037},[3417,12026,6727],{"class":6164},[3417,12028,4253],{"class":4037},[3417,12030,6732],{"class":6043},[3417,12032,6537],{"class":4037},[3417,12034,5393],{"class":6164},[3417,12036,6577],{"class":4037},[3417,12038,12040],{"class":3419,"line":12039},156,[3417,12041,3442],{"emptyLinePlaceholder":3441},[3417,12043,12045,12047,12049,12051,12053,12055,12057,12059,12061,12063,12065,12067],{"class":3419,"line":12044},157,[3417,12046,6903],{"class":4041},[3417,12048,7461],{"class":6164},[3417,12050,4253],{"class":4037},[3417,12052,7200],{"class":6043},[3417,12054,6537],{"class":4037},[3417,12056,6470],{"class":4041},[3417,12058,7091],{"class":6122},[3417,12060,6258],{"class":4037},[3417,12062,6768],{"class":4041},[3417,12064,7098],{"class":4037},[3417,12066,6717],{"class":6164},[3417,12068,12069],{"class":4037},"),\n",[3417,12071,12073,12076,12078,12080,12082,12085,12087,12089,12091,12093,12095,12097],{"class":3419,"line":12072},158,[3417,12074,12075],{"class":6164},"            WebSocketMessageType",[3417,12077,4253],{"class":4037},[3417,12079,4600],{"class":6164},[3417,12081,4246],{"class":4037},[3417,12083,12084],{"class":6164},"endOfMessage",[3417,12086,5407],{"class":4037},[3417,12088,7227],{"class":4041},[3417,12090,4246],{"class":4037},[3417,12092,9050],{"class":6164},[3417,12094,4253],{"class":4037},[3417,12096,9084],{"class":6164},[3417,12098,6577],{"class":4037},[3417,12100,12102],{"class":3419,"line":12101},159,[3417,12103,5765],{"class":4037},[3417,12105,12107],{"class":3419,"line":12106},160,[3417,12108,3869],{"class":4037},[3417,12110,12112],{"class":3419,"line":12111},161,[3417,12113,3442],{"emptyLinePlaceholder":3441},[3417,12115,12117],{"class":3419,"line":12116},162,[3417,12118,12119],{"class":6183},"\u002F\u002F Extension method для зручного читання полів JsonElement\n",[3417,12121,12123,12125,12127],{"class":3419,"line":12122},163,[3417,12124,9243],{"class":4041},[3417,12126,6432],{"class":4041},[3417,12128,12129],{"class":6122}," JsonElementExtensions\n",[3417,12131,12133],{"class":3419,"line":12132},164,[3417,12134,6150],{"class":4037},[3417,12136,12138,12140,12142,12144,12147,12149,12152,12155,12158,12160,12162,12165],{"class":3419,"line":12137},165,[3417,12139,6155],{"class":4041},[3417,12141,6480],{"class":4041},[3417,12143,6161],{"class":4041},[3417,12145,12146],{"class":6043}," TryGetProp",[3417,12148,6537],{"class":4037},[3417,12150,12151],{"class":4041},"this",[3417,12153,12154],{"class":6122}," JsonElement",[3417,12156,12157],{"class":6164}," el",[3417,12159,4246],{"class":4037},[3417,12161,6261],{"class":4041},[3417,12163,12164],{"class":6164}," prop",[3417,12166,6552],{"class":4037},[3417,12168,12170,12172,12175,12177,12180,12182,12185,12187,12189,12191,12194,12197,12200,12202,12204,12206,12208,12210,12212],{"class":3419,"line":12169},166,[3417,12171,6557],{"class":4037},[3417,12173,12174],{"class":6164},"el",[3417,12176,4253],{"class":4037},[3417,12178,12179],{"class":6043},"TryGetProperty",[3417,12181,6537],{"class":4037},[3417,12183,12184],{"class":6164},"prop",[3417,12186,4246],{"class":4037},[3417,12188,6619],{"class":4041},[3417,12190,8023],{"class":4041},[3417,12192,12193],{"class":6164}," val",[3417,12195,12196],{"class":4037},") ? ",[3417,12198,12199],{"class":6164},"val",[3417,12201,4253],{"class":4037},[3417,12203,8310],{"class":6043},[3417,12205,10096],{"class":4037},[3417,12207,10999],{"class":4060},[3417,12209,8726],{"class":4037},[3417,12211,10999],{"class":4060},[3417,12213,6130],{"class":4037},[3417,12215,12217],{"class":3419,"line":12216},167,[3417,12218,3869],{"class":4037},[3364,12220],{},[3374,12222,12224],{"id":12223},"запуск-та-демонстрація","Запуск та демонстрація",[12226,12227,12228,12233,12257,12261,12341,12345],"steps",{},[12229,12230,12232],"h4",{"id":12231},"запустіть-сервер","Запустіть сервер",[3382,12234,12236],{"className":6034,"code":12235,"language":6036,"meta":3391,"style":3391},"cd ChatServer\ndotnet run\n# Сервер слухає на http:\u002F\u002Flocalhost:5000\n",[3389,12237,12238,12245,12252],{"__ignoreMap":3391},[3417,12239,12240,12243],{"class":3419,"line":3420},[3417,12241,12242],{"class":6043},"cd",[3417,12244,6070],{"class":4060},[3417,12246,12247,12249],{"class":3419,"line":3426},[3417,12248,6044],{"class":6043},[3417,12250,12251],{"class":4060}," run\n",[3417,12253,12254],{"class":3419,"line":3432},[3417,12255,12256],{"class":6183},"# Сервер слухає на http:\u002F\u002Flocalhost:5000\n",[12229,12258,12260],{"id":12259},"запустіть-кілька-клієнтів-у-різних-терміналах","Запустіть кілька клієнтів (у різних терміналах)",[3382,12262,12264],{"className":6034,"code":12263,"language":6036,"meta":3391,"style":3391},"# Термінал 1:\ncd ChatClient && dotnet run\n# Введіть нікнейм: Alice\n\n# Термінал 2:\ncd ChatClient && dotnet run\n# Введіть нікнейм: Bob\n\n# Термінал 3:\ncd ChatClient && dotnet run\n# Введіть нікнейм: Carol\n",[3389,12265,12266,12271,12284,12289,12293,12298,12310,12315,12319,12324,12336],{"__ignoreMap":3391},[3417,12267,12268],{"class":3419,"line":3420},[3417,12269,12270],{"class":6183},"# Термінал 1:\n",[3417,12272,12273,12275,12278,12280,12282],{"class":3419,"line":3426},[3417,12274,12242],{"class":6043},[3417,12276,12277],{"class":4060}," ChatClient",[3417,12279,9344],{"class":4037},[3417,12281,6044],{"class":6043},[3417,12283,12251],{"class":4060},[3417,12285,12286],{"class":3419,"line":3432},[3417,12287,12288],{"class":6183},"# Введіть нікнейм: Alice\n",[3417,12290,12291],{"class":3419,"line":3438},[3417,12292,3442],{"emptyLinePlaceholder":3441},[3417,12294,12295],{"class":3419,"line":3445},[3417,12296,12297],{"class":6183},"# Термінал 2:\n",[3417,12299,12300,12302,12304,12306,12308],{"class":3419,"line":3451},[3417,12301,12242],{"class":6043},[3417,12303,12277],{"class":4060},[3417,12305,9344],{"class":4037},[3417,12307,6044],{"class":6043},[3417,12309,12251],{"class":4060},[3417,12311,12312],{"class":3419,"line":3457},[3417,12313,12314],{"class":6183},"# Введіть нікнейм: Bob\n",[3417,12316,12317],{"class":3419,"line":3462},[3417,12318,3442],{"emptyLinePlaceholder":3441},[3417,12320,12321],{"class":3419,"line":3468},[3417,12322,12323],{"class":6183},"# Термінал 3:\n",[3417,12325,12326,12328,12330,12332,12334],{"class":3419,"line":3474},[3417,12327,12242],{"class":6043},[3417,12329,12277],{"class":4060},[3417,12331,9344],{"class":4037},[3417,12333,6044],{"class":6043},[3417,12335,12251],{"class":4060},[3417,12337,12338],{"class":3419,"line":3480},[3417,12339,12340],{"class":6183},"# Введіть нікнейм: Carol\n",[12229,12342,12344],{"id":12343},"тестуйте-функціональність","Тестуйте функціональність",[3382,12346,12348],{"className":6034,"code":12347,"language":6036,"meta":3391,"style":3391},"# У терміналі Alice — публічне повідомлення:\n> Привіт усім!\n# Відображається у всіх клієнтів:\n[Alice]: Привіт усім!\n\n# Приватне повідомлення лише для Bob:\n> \u002Fprivate Bob Це тільки для тебе\n\n# У терміналі Bob:\n[🔒 Alice → Bob]: Це тільки для тебе\n\n# Від'єднання Alice:\n> \u002Fquit\n# У всіх клієнтів:\n  🔴 Alice покинула чат\n",[3389,12349,12350,12355,12360,12365,12370,12374,12379,12384,12388,12393,12398,12402,12407,12412,12417],{"__ignoreMap":3391},[3417,12351,12352],{"class":3419,"line":3420},[3417,12353,12354],{"class":6183},"# У терміналі Alice — публічне повідомлення:\n",[3417,12356,12357],{"class":3419,"line":3426},[3417,12358,12359],{"class":4037},"> Привіт усім!\n",[3417,12361,12362],{"class":3419,"line":3432},[3417,12363,12364],{"class":6183},"# Відображається у всіх клієнтів:\n",[3417,12366,12367],{"class":3419,"line":3438},[3417,12368,12369],{"class":4037},"[Alice]: Привіт усім!\n",[3417,12371,12372],{"class":3419,"line":3445},[3417,12373,3442],{"emptyLinePlaceholder":3441},[3417,12375,12376],{"class":3419,"line":3451},[3417,12377,12378],{"class":6183},"# Приватне повідомлення лише для Bob:\n",[3417,12380,12381],{"class":3419,"line":3457},[3417,12382,12383],{"class":4037},"> \u002Fprivate Bob Це тільки для тебе\n",[3417,12385,12386],{"class":3419,"line":3462},[3417,12387,3442],{"emptyLinePlaceholder":3441},[3417,12389,12390],{"class":3419,"line":3468},[3417,12391,12392],{"class":6183},"# У терміналі Bob:\n",[3417,12394,12395],{"class":3419,"line":3474},[3417,12396,12397],{"class":4037},"[🔒 Alice → Bob]: Це тільки для тебе\n",[3417,12399,12400],{"class":3419,"line":3480},[3417,12401,3442],{"emptyLinePlaceholder":3441},[3417,12403,12404],{"class":3419,"line":3486},[3417,12405,12406],{"class":6183},"# Від'єднання Alice:\n",[3417,12408,12409],{"class":3419,"line":3492},[3417,12410,12411],{"class":4037},"> \u002Fquit\n",[3417,12413,12414],{"class":3419,"line":3498},[3417,12415,12416],{"class":6183},"# У всіх клієнтів:\n",[3417,12418,12419,12422,12425,12428],{"class":3419,"line":3504},[3417,12420,12421],{"class":6043},"  🔴",[3417,12423,12424],{"class":4060}," Alice",[3417,12426,12427],{"class":4060}," покинула",[3417,12429,12430],{"class":4060}," чат\n",[3801,12432,12433,12440,12496,12503],{},[3317,12434,12435,12436,12439],{},"Для тестування WebSocket-з'єднань без клієнтського коду використовуйте ",[3321,12437,12438],{},"wscat"," — CLI-інструмент:",[3382,12441,12443],{"className":6034,"code":12442,"language":6036,"meta":3391,"style":3391},"npm install -g wscat\nwscat -c \"ws:\u002F\u002Flocalhost:5000\u002Fws?user=TestUser\"\n# Введіть JSON вручну:\n{\"type\":\"chat\",\"text\":\"Привіт з wscat!\"}\n",[3389,12444,12445,12459,12469,12474],{"__ignoreMap":3391},[3417,12446,12447,12450,12453,12456],{"class":3419,"line":3420},[3417,12448,12449],{"class":6043},"npm",[3417,12451,12452],{"class":4060}," install",[3417,12454,12455],{"class":4041}," -g",[3417,12457,12458],{"class":4060}," wscat\n",[3417,12460,12461,12463,12466],{"class":3419,"line":3426},[3417,12462,12438],{"class":6043},[3417,12464,12465],{"class":4041}," -c",[3417,12467,12468],{"class":4060}," \"ws:\u002F\u002Flocalhost:5000\u002Fws?user=TestUser\"\n",[3417,12470,12471],{"class":3419,"line":3432},[3417,12472,12473],{"class":6183},"# Введіть JSON вручну:\n",[3417,12475,12476,12478,12480,12482,12484,12487,12489,12491,12494],{"class":3419,"line":3438},[3417,12477,5400],{"class":4037},[3417,12479,5404],{"class":6043},[3417,12481,3593],{"class":6043},[3417,12483,8522],{"class":6043},[3417,12485,12486],{"class":6043},",",[3417,12488,11167],{"class":6043},[3417,12490,3593],{"class":6043},[3417,12492,12493],{"class":6043},"\"Привіт з wscat!\"",[3417,12495,3869],{"class":6043},[3317,12497,12498,12499,12502],{},"Або ",[3321,12500,12501],{},"websocat"," (Rust, standalone бінарний файл, не потребує Node.js):",[3382,12504,12506],{"className":6034,"code":12505,"language":6036,"meta":3391,"style":3391},"websocat \"ws:\u002F\u002Flocalhost:5000\u002Fws?user=TestUser\"\n",[3389,12507,12508],{"__ignoreMap":3391},[3417,12509,12510,12512],{"class":3419,"line":3420},[3417,12511,12501],{"class":6043},[3417,12513,12468],{"class":4060},[3364,12515],{},[3312,12517,12519],{"id":12518},"підсумок","Підсумок",[3317,12521,12522],{},"У цьому розділі ми вивчили WebSocket від фундаментальних принципів до повноцінного проєкту:",[4769,12524,12525,12531,12537,12543,12551,12557],{},[3984,12526,12527,12530],{},[3321,12528,12529],{},"Обмеженість HTTP"," для real-time сценаріїв і еволюція підходів: Short Polling → Long Polling → SSE → WebSocket",[3984,12532,12533,12536],{},[3321,12534,12535],{},"Протокол WebSocket (RFC 6455)",": Opening Handshake із SHA-1 верифікацією, бінарний формат фреймів, opcodes Text\u002FBinary\u002FPing\u002FPong\u002FClose, маскування клієнтських фреймів",[3984,12538,12539,12542],{},[3321,12540,12541],{},"Lifecycle з'єднання",": Ping\u002FPong keepalive, Closing Handshake із кодами закриття, аварійне закриття",[3984,12544,12545,12548,12549],{},[3321,12546,12547],{},"Безпека",": WSS (TLS), аутентифікація через cookies\u002Fquery param\u002Fперше повідомлення, захист від CSWSH через перевірку ",[3389,12550,4126],{},[3984,12552,12553,12556],{},[3321,12554,12555],{},"Масштабування",": проблема sticky sessions, Redis Pub\u002FSub як backplane, обмеження ОС",[3984,12558,12559,12562,12563,12566,12567,12566,12570,12572],{},[3321,12560,12561],{},"Практика",": повноцінний чат на ",[3389,12564,12565],{},"System.Net.WebSockets"," + ",[3389,12568,12569],{},"HttpListener",[3389,12571,4799],{}," без жодних сторонніх залежностей",[3353,12574,12575,12576,12579,12580,12582],{},"Для продакшн-застосунків розгляньте ",[3321,12577,12578],{},"ASP.NET Core SignalR"," — він надбудовується поверх нативних WebSocket (та SSE\u002FLong Polling як fallback), додає автоматичний reconnect, групи, стрімінг та typed hubs. ",[3389,12581,12565],{}," залишається цінним для розуміння протоколу та для сценаріїв із нестандартними вимогами до протоколу.",[3364,12584],{},[3312,12586,12588],{"id":12587},"доповнення-браузерний-клієнт-на-html-vanilla-js","Доповнення: Браузерний клієнт на HTML + Vanilla JS",[3317,12590,12591,12592,12595,12596,12598,12599,12602],{},"Попередній приклад використовував консольний .NET-клієнт. У цьому доповненні ми замінимо його на ",[3321,12593,12594],{},"повноцінну веб-сторінку"," — єдиний HTML-файл із вбудованим CSS і JavaScript, що використовує нативний браузерний ",[3389,12597,3649],{}," API. Сервер залишається незмінним — той самий ",[3389,12600,12601],{},"ChatServer",". Цей підхід є найпоширенішим у реальних застосунках: бекенд — HttpListener, фронтенд — браузер.",[3374,12604,12606],{"id":12605},"браузерний-websocket-api","Браузерний WebSocket API",[3317,12608,12609,12610,12612],{},"Браузер надає глобальний клас ",[3389,12611,3649],{}," без будь-яких залежностей:",[3382,12614,12618],{"className":12615,"code":12616,"language":12617,"meta":3391,"style":3391},"language-javascript shiki shiki-themes light-plus dark-plus dark-plus","const ws = new WebSocket('ws:\u002F\u002Flocalhost:5000\u002Fws?user=Alice');\n","javascript",[3389,12619,12620],{"__ignoreMap":3391},[3417,12621,12622,12625,12628,12630,12632,12634,12636,12639],{"class":3419,"line":3420},[3417,12623,12624],{"class":4041},"const",[3417,12626,7461],{"class":12627},"s-QsJ",[3417,12629,6467],{"class":4037},[3417,12631,6470],{"class":4041},[3417,12633,6987],{"class":6043},[3417,12635,6537],{"class":4037},[3417,12637,12638],{"class":4060},"'ws:\u002F\u002Flocalhost:5000\u002Fws?user=Alice'",[3417,12640,6577],{"class":4037},[4216,12642,12643,12670,12686,12709,12724,12741,12764,12768],{},[4219,12644,12647,12648,12650,12651,12653,12654,8100,12657,12659,12660,12663,12664,12667,12668,4253],{"name":12645,"type":12646},"ws.readyState","number (readonly)","Стан з'єднання. Значення: ",[3389,12649,4453],{}," — CONNECTING (handshake), ",[3389,12652,4449],{}," — OPEN (можна ",[3389,12655,12656],{},"send()",[3389,12658,4608],{}," — CLOSING (handshake закриття), ",[3389,12661,12662],{},"3"," — CLOSED. Перевіряйте ",[3389,12665,12666],{},"ws.readyState === WebSocket.OPEN"," перед ",[3389,12669,12656],{},[4219,12671,12674,12675,12677,12678,12681,12682,12685],{"name":12672,"type":12673},"ws.onopen","EventListener (Event)","Спрацьовує після ",[3389,12676,4207],{},". З цього моменту дозволено надсилати дані. ",[3389,12679,12680],{},"event"," — стандартний ",[3389,12683,12684],{},"Event"," без додаткових полів.",[4219,12687,12690,12691,12694,12695,12698,12699,4462,12702,12705,12706,10992],{"name":12688,"type":12689},"ws.onmessage","EventListener (MessageEvent)","Спрацьовує при отриманні ",[3321,12692,12693],{},"повного"," повідомлення. Браузер сам збирає фрагментовані фрейми. ",[3389,12696,12697],{},"event.data"," — рядок (Text frame), ",[3389,12700,12701],{},"Blob",[3389,12703,12704],{},"ArrayBuffer"," (Binary frame, залежно від ",[3389,12707,12708],{},"ws.binaryType",[4219,12710,12712,12713,12716,12717,12720,12721,4253],{"name":12711,"type":12673},"ws.onerror","Помилка з'єднання. Браузер ",[3321,12714,12715],{},"не розкриває"," деталі помилки з міркувань безпеки (запобігання CSWSH). Після ",[3389,12718,12719],{},"onerror"," завжди спрацьовує ",[3389,12722,12723],{},"onclose",[4219,12725,12728,12729,12732,12733,12736,12737,12740],{"name":12726,"type":12727},"ws.onclose","EventListener (CloseEvent)","Закриття з'єднання. ",[3389,12730,12731],{},"event.code"," — close code, ",[3389,12734,12735],{},"event.reason"," — причина, ",[3389,12738,12739],{},"event.wasClean"," — чи пройшов Closing Handshake коректно.",[4219,12742,12745,12746,12749,12750,5490,12753,5407,12755,12757,12758,12760,12761,12763],{"name":12743,"type":12744},"ws.send(data)","void","Надсилає Text або Binary frame. Якщо ",[3389,12747,12748],{},"readyState !== OPEN"," — кидає ",[3389,12751,12752],{},"InvalidStateError",[3389,12754,7168],{},[3389,12756,6261],{}," → Text frame; ",[3389,12759,12704],{}," \u002F ",[3389,12762,12701],{}," → Binary frame.",[4219,12765,12767],{"name":12766,"type":12646},"ws.bufferedAmount","Байти у буфері, що очікують відправки. Корисно для backpressure: якщо зростає — сповільніть відправлення.",[4219,12769,12771,12772,12775,12776,12779],{"name":12708,"type":12770},"'blob' | 'arraybuffer'","Тип для Binary frames. За замовчуванням ",[3389,12773,12774],{},"'blob'",". Встановіть ",[3389,12777,12778],{},"ws.binaryType = 'arraybuffer'"," для прямої роботи з бінарними даними.",[3353,12781,12782,12783,12786,12787,12790],{},"Браузерний API є ",[3321,12784,12785],{},"подієво-орієнтованим"," (event-driven): немає явного циклу читання — браузер сам викликає ",[3389,12788,12789],{},"onmessage",". Ping\u002FPong обробляються автоматично, без доступу з JavaScript.",[3364,12792],{},[3374,12794,12796],{"id":12795},"роздача-статичних-файлів","Роздача статичних файлів",[3317,12798,12799,12800,12802,12803,4462,12806,12809,12810,12813],{},"Роздача HTML-файлу вже вбудована у ",[3389,12801,8802],{}," — запит ",[3389,12804,12805],{},"GET \u002F",[3389,12807,12808],{},"GET \u002Findex.html"," зчитує і повертає ",[3389,12811,12812],{},"wwwroot\u002Findex.html",". Ніякого додаткового коду не потрібно.",[3317,12815,12816,12817,4253],{},"Помістіть HTML-файл у ",[3389,12818,12819],{},"ChatServer\u002Fwwwroot\u002Findex.html",[3364,12821],{},[3374,12823,12825,12826],{"id":12824},"повний-html-клієнт-wwwrootindexhtml","Повний HTML-клієнт: ",[3389,12827,12812],{},[3382,12829,12833],{"className":12830,"code":12831,"language":12832,"meta":6112,"style":3391},"language-html shiki shiki-themes light-plus dark-plus dark-plus","\u003C!DOCTYPE html>\n\u003Chtml lang=\"uk\">\n\u003Chead>\n  \u003Cmeta charset=\"UTF-8\" \u002F>\n  \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n  \u003Ctitle>WebSocket Chat\u003C\u002Ftitle>\n  \u003Cstyle>\n    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n    body {\n      font-family: 'Segoe UI', system-ui, sans-serif;\n      background: #1a1a2e;\n      color: #e0e0e0;\n      height: 100dvh;\n      display: flex;\n      flex-direction: column;\n    }\n\n    \u002F* ── Заголовок ──────────────────────────────── *\u002F\n    header {\n      background: #16213e;\n      padding: 12px 20px;\n      display: flex;\n      align-items: center;\n      gap: 12px;\n      border-bottom: 1px solid #0f3460;\n    }\n    header h1 { font-size: 1.1rem; font-weight: 600; color: #e94560; }\n    #status-badge {\n      font-size: 0.75rem;\n      padding: 3px 10px;\n      border-radius: 999px;\n      font-weight: 500;\n    }\n    .badge-connecting { background: #f59e0b22; color: #f59e0b; border: 1px solid #f59e0b55; }\n    .badge-open       { background: #10b98122; color: #10b981; border: 1px solid #10b98155; }\n    .badge-closed     { background: #ef444422; color: #ef4444; border: 1px solid #ef444455; }\n\n    \u002F* ── Основний layout ────────────────────────── *\u002F\n    main {\n      flex: 1;\n      display: grid;\n      grid-template-columns: 220px 1fr;\n      overflow: hidden;\n    }\n\n    \u002F* ── Бокова панель (онлайн-список) ─────────── *\u002F\n    aside {\n      background: #16213e;\n      border-right: 1px solid #0f3460;\n      padding: 16px;\n      overflow-y: auto;\n    }\n    aside h2 { font-size: 0.75rem; text-transform: uppercase;\n               letter-spacing: 0.1em; color: #64748b; margin-bottom: 10px; }\n    #users-list { list-style: none; }\n    #users-list li {\n      padding: 6px 10px;\n      border-radius: 8px;\n      font-size: 0.9rem;\n      cursor: pointer;\n      transition: background 0.15s;\n      display: flex;\n      align-items: center;\n      gap: 8px;\n    }\n    #users-list li:hover { background: #0f3460; }\n    #users-list li.me { color: #e94560; font-weight: 600; }\n    #users-list li::before { content: '●'; font-size: 0.55rem; color: #10b981; }\n\n    \u002F* ── Область повідомлень ────────────────────── *\u002F\n    .chat-area {\n      display: flex;\n      flex-direction: column;\n      overflow: hidden;\n    }\n\n    #messages {\n      flex: 1;\n      overflow-y: auto;\n      padding: 16px;\n      display: flex;\n      flex-direction: column;\n      gap: 8px;\n      scroll-behavior: smooth;\n    }\n\n    \u002F* Базовий стиль повідомлення *\u002F\n    .msg {\n      max-width: 70%;\n      padding: 8px 14px;\n      border-radius: 12px;\n      font-size: 0.9rem;\n      line-height: 1.5;\n      animation: fadeIn 0.2s ease;\n    }\n    @keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }\n\n    \u002F* Чужі повідомлення — ліворуч *\u002F\n    .msg-other {\n      align-self: flex-start;\n      background: #0f3460;\n      border-bottom-left-radius: 4px;\n    }\n    \u002F* Мої повідомлення — праворуч *\u002F\n    .msg-mine {\n      align-self: flex-end;\n      background: #e94560;\n      color: #fff;\n      border-bottom-right-radius: 4px;\n    }\n    \u002F* Приватні — фіолетові *\u002F\n    .msg-private {\n      align-self: flex-start;\n      background: #4c1d95;\n      border: 1px solid #7c3aed;\n      border-bottom-left-radius: 4px;\n    }\n    .msg-private.msg-mine {\n      align-self: flex-end;\n      border-bottom-right-radius: 4px;\n      border-bottom-left-radius: 12px;\n    }\n    \u002F* Системні — по центру *\u002F\n    .msg-system {\n      align-self: center;\n      background: transparent;\n      color: #64748b;\n      font-size: 0.8rem;\n      font-style: italic;\n    }\n\n    .msg-header {\n      font-size: 0.72rem;\n      font-weight: 600;\n      margin-bottom: 2px;\n      opacity: 0.8;\n    }\n    .msg-time {\n      font-size: 0.68rem;\n      opacity: 0.5;\n      margin-top: 3px;\n    }\n\n    \u002F* ── Поле введення ──────────────────────────── *\u002F\n    .input-area {\n      padding: 12px 16px;\n      background: #16213e;\n      border-top: 1px solid #0f3460;\n      display: flex;\n      gap: 10px;\n      align-items: flex-end;\n    }\n    .input-wrap {\n      flex: 1;\n      position: relative;\n    }\n    #private-target {\n      font-size: 0.75rem;\n      color: #7c3aed;\n      padding: 2px 8px;\n      margin-bottom: 4px;\n      display: none;\n      align-items: center;\n      gap: 6px;\n      background: #4c1d9520;\n      border-radius: 4px;\n    }\n    #private-target.visible { display: flex; }\n    #private-target button {\n      background: none; border: none; color: #64748b;\n      cursor: pointer; font-size: 0.8rem; padding: 0;\n    }\n    #msg-input {\n      width: 100%;\n      background: #0f3460;\n      border: 1px solid #1e4080;\n      color: #e0e0e0;\n      border-radius: 10px;\n      padding: 10px 14px;\n      font-size: 0.9rem;\n      resize: none;\n      min-height: 42px;\n      max-height: 120px;\n      outline: none;\n      transition: border-color 0.2s;\n      font-family: inherit;\n    }\n    #msg-input:focus { border-color: #e94560; }\n\n    #send-btn {\n      background: #e94560;\n      color: white;\n      border: none;\n      border-radius: 10px;\n      width: 42px;\n      height: 42px;\n      font-size: 1.2rem;\n      cursor: pointer;\n      transition: background 0.2s, transform 0.1s;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      flex-shrink: 0;\n    }\n    #send-btn:hover { background: #c73652; }\n    #send-btn:active { transform: scale(0.95); }\n    #send-btn:disabled { background: #374151; cursor: not-allowed; }\n\n    \u002F* ── Вікно входу ────────────────────────────── *\u002F\n    #login-overlay {\n      position: fixed; inset: 0;\n      background: #1a1a2eee;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 100;\n      backdrop-filter: blur(4px);\n    }\n    .login-card {\n      background: #16213e;\n      border: 1px solid #0f3460;\n      border-radius: 16px;\n      padding: 32px;\n      width: 320px;\n      text-align: center;\n    }\n    .login-card h2 { color: #e94560; margin-bottom: 8px; }\n    .login-card p { color: #64748b; font-size: 0.85rem; margin-bottom: 24px; }\n    .login-card input {\n      width: 100%;\n      background: #0f3460;\n      border: 1px solid #1e4080;\n      color: #e0e0e0;\n      border-radius: 10px;\n      padding: 10px 14px;\n      font-size: 1rem;\n      outline: none;\n      margin-bottom: 14px;\n      font-family: inherit;\n    }\n    .login-card input:focus { border-color: #e94560; }\n    .login-card button {\n      width: 100%;\n      background: #e94560;\n      color: white;\n      border: none;\n      border-radius: 10px;\n      padding: 11px;\n      font-size: 1rem;\n      cursor: pointer;\n      font-weight: 600;\n      transition: background 0.2s;\n    }\n    .login-card button:hover { background: #c73652; }\n    #login-error { color: #f87171; font-size: 0.8rem; margin-top: 8px; }\n  \u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n\n\u003C!-- ── Вікно входу ──────────────────────────────────── -->\n\u003Cdiv id=\"login-overlay\">\n  \u003Cdiv class=\"login-card\">\n    \u003Ch2>💬 WebSocket Chat\u003C\u002Fh2>\n    \u003Cp>Введіть нікнейм для входу в кімнату\u003C\u002Fp>\n    \u003Cinput id=\"username-input\" type=\"text\" placeholder=\"Ваш нікнейм...\"\n           maxlength=\"20\" autocomplete=\"off\" \u002F>\n    \u003Cbutton id=\"join-btn\">Увійти\u003C\u002Fbutton>\n    \u003Cdiv id=\"login-error\">\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\n\u003C!-- ── Головний інтерфейс ──────────────────────────── -->\n\u003Cheader>\n  \u003Ch1>💬 WebSocket Chat\u003C\u002Fh1>\n  \u003Cspan id=\"status-badge\" class=\"badge-connecting\">● Підключення...\u003C\u002Fspan>\n  \u003Cspan id=\"my-name\" style=\"margin-left:auto; color:#64748b; font-size:0.85rem\">\u003C\u002Fspan>\n\u003C\u002Fheader>\n\n\u003Cmain>\n  \u003Caside>\n    \u003Ch2>🟢 Онлайн\u003C\u002Fh2>\n    \u003Cul id=\"users-list\">\u003C\u002Ful>\n  \u003C\u002Faside>\n\n  \u003Cdiv class=\"chat-area\">\n    \u003Cdiv id=\"messages\">\u003C\u002Fdiv>\n    \u003Cdiv class=\"input-area\">\n      \u003Cdiv class=\"input-wrap\">\n        \u003Cdiv id=\"private-target\">\n          \u003Cspan id=\"private-label\">🔒 Приватно до: \u003Cstrong>\u003C\u002Fstrong>\u003C\u002Fspan>\n          \u003Cbutton id=\"cancel-private\" title=\"Скасувати\">✕\u003C\u002Fbutton>\n        \u003C\u002Fdiv>\n        \u003Ctextarea id=\"msg-input\" placeholder=\"Введіть повідомлення... (Enter — надіслати, Shift+Enter — новий рядок)\"\n                  rows=\"1\">\u003C\u002Ftextarea>\n      \u003C\u002Fdiv>\n      \u003Cbutton id=\"send-btn\" disabled title=\"Надіслати\">➤\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Fmain>\n\n\u003Cscript>\n  \u002F\u002F ── Стан застосунку ──────────────────────────────────\n  let ws = null;\n  let myUsername = '';\n  let privateTarget = null; \u002F\u002F username для приватного повідомлення або null\n\n  const $ = id => document.getElementById(id);\n\n  \u002F\u002F ── Елементи DOM ─────────────────────────────────────\n  const overlay     = $('login-overlay');\n  const usernameIn  = $('username-input');\n  const joinBtn     = $('join-btn');\n  const loginError  = $('login-error');\n  const statusBadge = $('status-badge');\n  const myNameEl    = $('my-name');\n  const messagesList = $('messages');\n  const usersList   = $('users-list');\n  const msgInput    = $('msg-input');\n  const sendBtn     = $('send-btn');\n  const privateDiv  = $('private-target');\n  const privateLabel = $('private-label').querySelector('strong');\n\n  \u002F\u002F ── Підключення до сервера ───────────────────────────\n  function connect(username) {\n    myUsername = username;\n    \u002F\u002F Визначаємо URL: якщо HTTPS — використовуємо wss:\u002F\u002F, якщо HTTP — ws:\u002F\u002F\n    const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\n    const url = `${protocol}\u002F\u002F${location.host}\u002Fws?user=${encodeURIComponent(username)}`;\n\n    ws = new WebSocket(url);\n\n    setStatus('connecting');\n\n    \u002F\u002F Підключення встановлено\n    ws.onopen = () => {\n      setStatus('open');\n      sendBtn.disabled = false;\n      overlay.style.display = 'none';\n      myNameEl.textContent = `Ви: ${myUsername}`;\n      addSystemMessage('✅ Підключено до чату');\n    };\n\n    \u002F\u002F Отримано повідомлення від сервера\n    ws.onmessage = (event) => {\n      \u002F\u002F event.data завжди рядок для Text frames\n      let msg;\n      try { msg = JSON.parse(event.data); }\n      catch { return; }\n      handleMessage(msg);\n    };\n\n    \u002F\u002F Помилка (деталі не доступні з міркувань безпеки)\n    ws.onerror = () => {\n      addSystemMessage('⚠️ Помилка з\\'єднання');\n    };\n\n    \u002F\u002F Закриття з'єднання\n    ws.onclose = (event) => {\n      setStatus('closed');\n      sendBtn.disabled = true;\n      const reason = event.reason || 'невідома причина';\n      \u002F\u002F Код 4000 — ім'я зайняте (визначено у сервері)\n      if (event.code === 4000) {\n        loginError.textContent = `❌ Нікнейм \"${username}\" вже зайнятий`;\n        overlay.style.display = 'flex';\n      } else {\n        addSystemMessage(`🔴 Відключено (код ${event.code}: ${reason})`);\n      }\n    };\n  }\n\n  \u002F\u002F ── Обробка вхідних повідомлень ──────────────────────\n  function handleMessage(msg) {\n    switch (msg.type) {\n      case 'chat':\n        addChatMessage(msg.from, msg.text, msg.timestamp, false);\n        break;\n\n      case 'private':\n        \u002F\u002F msg.from: хто надіслав, msg.to: кому\n        const isOutgoing = msg.from === myUsername;\n        addPrivateMessage(msg.from, msg.to, msg.text, msg.timestamp, isOutgoing);\n        break;\n\n      case 'system':\n        addSystemMessage(msg.text);\n        break;\n\n      case 'users':\n        renderUsersList(msg.users);\n        break;\n    }\n  }\n\n  \u002F\u002F ── Відображення повідомлень ─────────────────────────\n  function addChatMessage(from, text, timestamp, isMine) {\n    const isMineMsg = from === myUsername;\n    const el = document.createElement('div');\n    el.className = `msg ${isMineMsg ? 'msg-mine' : 'msg-other'}`;\n    el.innerHTML = `\n      ${!isMineMsg ? `\u003Cdiv class=\"msg-header\">${escapeHtml(from)}\u003C\u002Fdiv>` : ''}\n      \u003Cdiv>${escapeHtml(text).replace(\u002F\\n\u002Fg, '\u003Cbr>')}\u003C\u002Fdiv>\n      \u003Cdiv class=\"msg-time\">${formatTime(timestamp)}\u003C\u002Fdiv>\n    `;\n    appendMessage(el);\n  }\n\n  function addPrivateMessage(from, to, text, timestamp, isOutgoing) {\n    const el = document.createElement('div');\n    el.className = `msg msg-private ${isOutgoing ? 'msg-mine' : ''}`;\n    const label = isOutgoing\n      ? `🔒 → ${escapeHtml(to)}`\n      : `🔒 ${escapeHtml(from)}`;\n    el.innerHTML = `\n      \u003Cdiv class=\"msg-header\">${label}\u003C\u002Fdiv>\n      \u003Cdiv>${escapeHtml(text).replace(\u002F\\n\u002Fg, '\u003Cbr>')}\u003C\u002Fdiv>\n      \u003Cdiv class=\"msg-time\">${formatTime(timestamp)}\u003C\u002Fdiv>\n    `;\n    appendMessage(el);\n  }\n\n  function addSystemMessage(text) {\n    const el = document.createElement('div');\n    el.className = 'msg msg-system';\n    el.textContent = text;\n    appendMessage(el);\n  }\n\n  function appendMessage(el) {\n    messagesList.appendChild(el);\n    \u002F\u002F Автоскрол донизу\n    messagesList.scrollTop = messagesList.scrollHeight;\n  }\n\n  \u002F\u002F ── Список користувачів ──────────────────────────────\n  function renderUsersList(users) {\n    usersList.innerHTML = '';\n    users.forEach(user => {\n      const li = document.createElement('li');\n      li.textContent = user;\n      if (user === myUsername) li.classList.add('me');\n      li.title = user === myUsername ? 'Це ви' : `Написати приватно ${user}`;\n      if (user !== myUsername) {\n        \u002F\u002F Клік на користувача — переключаємо на приватний режим\n        li.addEventListener('click', () => setPrivateTarget(user));\n      }\n      usersList.appendChild(li);\n    });\n  }\n\n  \u002F\u002F ── Приватний режим ──────────────────────────────────\n  function setPrivateTarget(username) {\n    privateTarget = username;\n    privateLabel.textContent = username;\n    privateDiv.classList.add('visible');\n    msgInput.placeholder = `Приватне для ${username}...`;\n    msgInput.focus();\n  }\n\n  $('cancel-private').addEventListener('click', () => {\n    privateTarget = null;\n    privateDiv.classList.remove('visible');\n    msgInput.placeholder = 'Введіть повідомлення...';\n  });\n\n  \u002F\u002F ── Відправлення повідомлення ────────────────────────\n  function sendMessage() {\n    const text = msgInput.value.trim();\n    if (!text || !ws || ws.readyState !== WebSocket.OPEN) return;\n\n    const payload = privateTarget\n      ? { type: 'private', to: privateTarget, text }\n      : { type: 'chat', text };\n\n    \u002F\u002F ws.send() — передає рядок як Text WebSocket frame\n    ws.send(JSON.stringify(payload));\n    msgInput.value = '';\n    msgInput.style.height = 'auto';\n  }\n\n  \u002F\u002F ── Обробники подій UI ───────────────────────────────\n  joinBtn.addEventListener('click', () => {\n    const username = usernameIn.value.trim();\n    if (!username) { loginError.textContent = 'Введіть нікнейм'; return; }\n    if (username.length > 20) { loginError.textContent = 'Максимум 20 символів'; return; }\n    loginError.textContent = '';\n    connect(username);\n  });\n\n  usernameIn.addEventListener('keydown', e => {\n    if (e.key === 'Enter') joinBtn.click();\n  });\n\n  sendBtn.addEventListener('click', sendMessage);\n\n  msgInput.addEventListener('keydown', e => {\n    if (e.key === 'Enter' && !e.shiftKey) {\n      e.preventDefault(); \u002F\u002F Запобігаємо новому рядку\n      sendMessage();\n    }\n  });\n\n  \u002F\u002F Автозростання textarea\n  msgInput.addEventListener('input', () => {\n    msgInput.style.height = 'auto';\n    msgInput.style.height = Math.min(msgInput.scrollHeight, 120) + 'px';\n  });\n\n  \u002F\u002F Коректне закриття при закритті вкладки\n  window.addEventListener('beforeunload', () => {\n    if (ws && ws.readyState === WebSocket.OPEN) {\n      \u002F\u002F Надсилаємо Close Frame із кодом 1001 \"Going Away\"\n      ws.close(1001, 'Page closing');\n    }\n  });\n\n  \u002F\u002F ── Допоміжні функції ────────────────────────────────\n  function setStatus(state) {\n    const labels = {\n      connecting: ['● Підключення...', 'badge-connecting'],\n      open:       ['● Онлайн',         'badge-open'],\n      closed:     ['● Відключено',      'badge-closed'],\n    };\n    const [text, cls] = labels[state];\n    statusBadge.textContent = text;\n    statusBadge.className = cls;\n  }\n\n  function formatTime(iso) {\n    if (!iso) return '';\n    try {\n      return new Date(iso).toLocaleTimeString('uk-UA',\n        { hour: '2-digit', minute: '2-digit' });\n    } catch { return ''; }\n  }\n\n  function escapeHtml(str = '') {\n    return str\n      .replace(\u002F&\u002Fg, '&amp;')\n      .replace(\u002F\u003C\u002Fg, '&lt;')\n      .replace(\u002F>\u002Fg, '&gt;')\n      .replace(\u002F\"\u002Fg, '&quot;');\n  }\n\u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html",[3389,12834,12835,12851,12869,12878,12897,12921,12941,12950,13002,13006,13014,13036,13048,13060,13072,13084,13096,13100,13104,13109,13116,13127,13142,13152,13164,13175,13193,13197,13236,13243,13255,13269,13281,13293,13297,13337,13376,13415,13419,13424,13431,13442,13453,13468,13480,13484,13488,13493,13500,13510,13525,13536,13548,13552,13579,13610,13627,13636,13649,13660,13671,13683,13698,13708,13718,13728,13732,13750,13775,13810,13814,13819,13826,13836,13846,13856,13860,13864,13871,13881,13891,13901,13911,13921,13931,13943,13947,13951,13956,13963,13975,13988,13998,14008,14020,14036,14040,14085,14089,14094,14101,14113,14123,14134,14138,14143,14150,14161,14171,14182,14193,14197,14202,14209,14219,14230,14246,14256,14260,14267,14277,14287,14297,14301,14306,14313,14323,14334,14344,14355,14367,14371,14375,14382,14393,14403,14415,14427,14431,14438,14449,14460,14471,14475,14479,14484,14491,14504,14514,14529,14539,14549,14559,14563,14570,14580,14592,14596,14603,14613,14624,14637,14647,14657,14667,14677,14688,14698,14703,14720,14730,14757,14784,14789,14797,14810,14821,14837,14848,14859,14872,14883,14895,14908,14921,14933,14945,14957,14962,14979,14984,14992,15003,15015,15026,15037,15048,15059,15071,15082,15102,15113,15124,15136,15148,15153,15170,15192,15219,15224,15230,15238,15259,15271,15282,15293,15304,15317,15334,15339,15347,15358,15373,15384,15396,15408,15420,15425,15450,15486,15496,15507,15518,15533,15544,15555,15568,15580,15591,15603,15614,15619,15637,15646,15657,15668,15679,15690,15701,15713,15724,15735,15746,15759,15764,15782,15816,15826,15835,15844,15849,15855,15873,15889,15908,15926,15954,15975,16001,16022,16031,16040,16045,16051,16061,16078,16110,16138,16147,16152,16162,16172,16190,16210,16219,16224,16240,16260,16276,16293,16310,16344,16377,16387,16409,16426,16436,16471,16481,16490,16499,16504,16514,16520,16535,16550,16567,16572,16602,16607,16613,16633,16653,16672,16691,16710,16730,16749,16769,16788,16807,16826,16855,16860,16866,16882,16894,16900,16939,16995,17000,17019,17024,17037,17042,17048,17068,17081,17098,17119,17146,17159,17164,17169,17175,17196,17202,17212,17242,17254,17266,17271,17276,17282,17299,17317,17322,17327,17333,17354,17366,17381,17407,17413,17434,17460,17481,17492,17526,17532,17537,17543,17548,17554,17568,17584,17595,17633,17641,17646,17656,17662,17685,17730,17737,17742,17752,17767,17774,17779,17789,17805,17812,17817,17822,17827,17833,17860,17878,17901,17938,17953,17991,18031,18052,18060,18072,18077,18082,18112,18133,18165,18178,18202,18227,18240,18255,18288,18307,18314,18325,18330,18335,18349,18370,18386,18402,18413,18418,18423,18437,18454,18460,18481,18486,18491,18497,18511,18527,18547,18569,18586,18620,18657,18673,18679,18709,18714,18730,18736,18741,18746,18752,18765,18777,18793,18814,18841,18853,18858,18863,18888,18899,18919,18935,18941,18946,18952,18963,18986,19028,19033,19046,19071,19088,19093,19099,19126,19141,19162,19167,19172,19178,19198,19219,19250,19289,19305,19317,19322,19327,19350,19381,19386,19391,19412,19417,19439,19470,19487,19495,19500,19505,19510,19516,19536,19555,19602,19607,19612,19618,19639,19666,19672,19694,19699,19704,19709,19715,19730,19742,19762,19782,19802,19807,19834,19850,19866,19871,19876,19891,19910,19918,19945,19967,19983,19988,19993,20012,20020,20042,20063,20084,20105,20110,20119,20128],{"__ignoreMap":3391},[3417,12836,12837,12841,12844,12848],{"class":3419,"line":3420},[3417,12838,12840],{"class":12839},"s0P7L","\u003C!",[3417,12842,12843],{"class":4054},"DOCTYPE",[3417,12845,12847],{"class":12846},"sa4r_"," html",[3417,12849,12850],{"class":12839},">\n",[3417,12852,12853,12855,12857,12860,12863,12867],{"class":3419,"line":3426},[3417,12854,6258],{"class":12839},[3417,12856,12832],{"class":4054},[3417,12858,12859],{"class":12846}," lang",[3417,12861,12862],{"class":4037},"=",[3417,12864,12866],{"class":12865},"su9tN","\"uk\"",[3417,12868,12850],{"class":12839},[3417,12870,12871,12873,12876],{"class":3419,"line":3432},[3417,12872,6258],{"class":12839},[3417,12874,12875],{"class":4054},"head",[3417,12877,12850],{"class":12839},[3417,12879,12880,12883,12886,12889,12891,12894],{"class":3419,"line":3438},[3417,12881,12882],{"class":12839},"  \u003C",[3417,12884,12885],{"class":4054},"meta",[3417,12887,12888],{"class":12846}," charset",[3417,12890,12862],{"class":4037},[3417,12892,12893],{"class":12865},"\"UTF-8\"",[3417,12895,12896],{"class":12839}," \u002F>\n",[3417,12898,12899,12901,12903,12906,12908,12911,12914,12916,12919],{"class":3419,"line":3445},[3417,12900,12882],{"class":12839},[3417,12902,12885],{"class":4054},[3417,12904,12905],{"class":12846}," name",[3417,12907,12862],{"class":4037},[3417,12909,12910],{"class":12865},"\"viewport\"",[3417,12912,12913],{"class":12846}," content",[3417,12915,12862],{"class":4037},[3417,12917,12918],{"class":12865},"\"width=device-width, initial-scale=1.0\"",[3417,12920,12896],{"class":12839},[3417,12922,12923,12925,12928,12931,12934,12937,12939],{"class":3419,"line":3451},[3417,12924,12882],{"class":12839},[3417,12926,12927],{"class":4054},"title",[3417,12929,12930],{"class":12839},">",[3417,12932,12933],{"class":4037},"WebSocket Chat",[3417,12935,12936],{"class":12839},"\u003C\u002F",[3417,12938,12927],{"class":4054},[3417,12940,12850],{"class":12839},[3417,12942,12943,12945,12948],{"class":3419,"line":3457},[3417,12944,12882],{"class":12839},[3417,12946,12947],{"class":4054},"style",[3417,12949,12850],{"class":12839},[3417,12951,12952,12955,12957,12960,12964,12966,12968,12971,12973,12976,12978,12982,12984,12987,12989,12991,12993,12996,12998,13000],{"class":3419,"line":3462},[3417,12953,12954],{"class":4054},"    *",[3417,12956,4246],{"class":7550},[3417,12958,12959],{"class":4054},"*",[3417,12961,12963],{"class":12962},"sqdDX","::before",[3417,12965,4246],{"class":7550},[3417,12967,12959],{"class":4054},[3417,12969,12970],{"class":12962},"::after",[3417,12972,6168],{"class":7550},[3417,12974,12975],{"class":12846},"box-sizing",[3417,12977,5407],{"class":7550},[3417,12979,12981],{"class":12980},"sDUd3","border-box",[3417,12983,6174],{"class":7550},[3417,12985,12986],{"class":12846},"margin",[3417,12988,5407],{"class":7550},[3417,12990,4453],{"class":4048},[3417,12992,6174],{"class":7550},[3417,12994,12995],{"class":12846},"padding",[3417,12997,5407],{"class":7550},[3417,12999,4453],{"class":4048},[3417,13001,6207],{"class":7550},[3417,13003,13004],{"class":3419,"line":3468},[3417,13005,3442],{"emptyLinePlaceholder":3441},[3417,13007,13008,13011],{"class":3419,"line":3474},[3417,13009,13010],{"class":12962},"    body",[3417,13012,13013],{"class":7550}," {\n",[3417,13015,13016,13019,13021,13024,13026,13029,13031,13034],{"class":3419,"line":3480},[3417,13017,13018],{"class":12846},"      font-family",[3417,13020,5407],{"class":7550},[3417,13022,13023],{"class":4060},"'Segoe UI'",[3417,13025,4246],{"class":7550},[3417,13027,13028],{"class":12980},"system-ui",[3417,13030,4246],{"class":7550},[3417,13032,13033],{"class":12980},"sans-serif",[3417,13035,6130],{"class":7550},[3417,13037,13038,13041,13043,13046],{"class":3419,"line":3486},[3417,13039,13040],{"class":12846},"      background",[3417,13042,5407],{"class":7550},[3417,13044,13045],{"class":12980},"#1a1a2e",[3417,13047,6130],{"class":7550},[3417,13049,13050,13053,13055,13058],{"class":3419,"line":3492},[3417,13051,13052],{"class":12846},"      color",[3417,13054,5407],{"class":7550},[3417,13056,13057],{"class":12980},"#e0e0e0",[3417,13059,6130],{"class":7550},[3417,13061,13062,13065,13067,13070],{"class":3419,"line":3498},[3417,13063,13064],{"class":12846},"      height",[3417,13066,5407],{"class":7550},[3417,13068,13069],{"class":4048},"100dvh",[3417,13071,6130],{"class":7550},[3417,13073,13074,13077,13079,13082],{"class":3419,"line":3504},[3417,13075,13076],{"class":12846},"      display",[3417,13078,5407],{"class":7550},[3417,13080,13081],{"class":12980},"flex",[3417,13083,6130],{"class":7550},[3417,13085,13086,13089,13091,13094],{"class":3419,"line":3509},[3417,13087,13088],{"class":12846},"      flex-direction",[3417,13090,5407],{"class":7550},[3417,13092,13093],{"class":12980},"column",[3417,13095,6130],{"class":7550},[3417,13097,13098],{"class":3419,"line":3515},[3417,13099,5765],{"class":7550},[3417,13101,13102],{"class":3419,"line":3521},[3417,13103,3442],{"emptyLinePlaceholder":3441},[3417,13105,13106],{"class":3419,"line":3527},[3417,13107,13108],{"class":6183},"    \u002F* ── Заголовок ──────────────────────────────── *\u002F\n",[3417,13110,13111,13114],{"class":3419,"line":3533},[3417,13112,13113],{"class":12962},"    header",[3417,13115,13013],{"class":7550},[3417,13117,13118,13120,13122,13125],{"class":3419,"line":3538},[3417,13119,13040],{"class":12846},[3417,13121,5407],{"class":7550},[3417,13123,13124],{"class":12980},"#16213e",[3417,13126,6130],{"class":7550},[3417,13128,13129,13132,13134,13137,13140],{"class":3419,"line":3543},[3417,13130,13131],{"class":12846},"      padding",[3417,13133,5407],{"class":7550},[3417,13135,13136],{"class":4048},"12px",[3417,13138,13139],{"class":4048}," 20px",[3417,13141,6130],{"class":7550},[3417,13143,13144,13146,13148,13150],{"class":3419,"line":3549},[3417,13145,13076],{"class":12846},[3417,13147,5407],{"class":7550},[3417,13149,13081],{"class":12980},[3417,13151,6130],{"class":7550},[3417,13153,13154,13157,13159,13162],{"class":3419,"line":3555},[3417,13155,13156],{"class":12846},"      align-items",[3417,13158,5407],{"class":7550},[3417,13160,13161],{"class":12980},"center",[3417,13163,6130],{"class":7550},[3417,13165,13166,13169,13171,13173],{"class":3419,"line":3560},[3417,13167,13168],{"class":12846},"      gap",[3417,13170,5407],{"class":7550},[3417,13172,13136],{"class":4048},[3417,13174,6130],{"class":7550},[3417,13176,13177,13180,13182,13185,13188,13191],{"class":3419,"line":3565},[3417,13178,13179],{"class":12846},"      border-bottom",[3417,13181,5407],{"class":7550},[3417,13183,13184],{"class":4048},"1px",[3417,13186,13187],{"class":12980}," solid",[3417,13189,13190],{"class":12980}," #0f3460",[3417,13192,6130],{"class":7550},[3417,13194,13195],{"class":3419,"line":3571},[3417,13196,5765],{"class":7550},[3417,13198,13199,13201,13204,13206,13209,13211,13214,13216,13219,13221,13224,13226,13229,13231,13234],{"class":3419,"line":3576},[3417,13200,13113],{"class":12962},[3417,13202,13203],{"class":12962}," h1",[3417,13205,6168],{"class":7550},[3417,13207,13208],{"class":12846},"font-size",[3417,13210,5407],{"class":7550},[3417,13212,13213],{"class":4048},"1.1rem",[3417,13215,6174],{"class":7550},[3417,13217,13218],{"class":12846},"font-weight",[3417,13220,5407],{"class":7550},[3417,13222,13223],{"class":4048},"600",[3417,13225,6174],{"class":7550},[3417,13227,13228],{"class":12846},"color",[3417,13230,5407],{"class":7550},[3417,13232,13233],{"class":12980},"#e94560",[3417,13235,6207],{"class":7550},[3417,13237,13238,13241],{"class":3419,"line":3965},[3417,13239,13240],{"class":12962},"    #status-badge",[3417,13242,13013],{"class":7550},[3417,13244,13245,13248,13250,13253],{"class":3419,"line":3970},[3417,13246,13247],{"class":12846},"      font-size",[3417,13249,5407],{"class":7550},[3417,13251,13252],{"class":4048},"0.75rem",[3417,13254,6130],{"class":7550},[3417,13256,13257,13259,13261,13264,13267],{"class":3419,"line":4410},[3417,13258,13131],{"class":12846},[3417,13260,5407],{"class":7550},[3417,13262,13263],{"class":4048},"3px",[3417,13265,13266],{"class":4048}," 10px",[3417,13268,6130],{"class":7550},[3417,13270,13271,13274,13276,13279],{"class":3419,"line":4415},[3417,13272,13273],{"class":12846},"      border-radius",[3417,13275,5407],{"class":7550},[3417,13277,13278],{"class":4048},"999px",[3417,13280,6130],{"class":7550},[3417,13282,13283,13286,13288,13291],{"class":3419,"line":4964},[3417,13284,13285],{"class":12846},"      font-weight",[3417,13287,5407],{"class":7550},[3417,13289,13290],{"class":4048},"500",[3417,13292,6130],{"class":7550},[3417,13294,13295],{"class":3419,"line":4969},[3417,13296,5765],{"class":7550},[3417,13298,13299,13302,13304,13307,13309,13312,13314,13316,13318,13321,13323,13326,13328,13330,13332,13335],{"class":3419,"line":5876},[3417,13300,13301],{"class":12962},"    .badge-connecting",[3417,13303,6168],{"class":7550},[3417,13305,13306],{"class":12846},"background",[3417,13308,5407],{"class":7550},[3417,13310,13311],{"class":12980},"#f59e0b22",[3417,13313,6174],{"class":7550},[3417,13315,13228],{"class":12846},[3417,13317,5407],{"class":7550},[3417,13319,13320],{"class":12980},"#f59e0b",[3417,13322,6174],{"class":7550},[3417,13324,13325],{"class":12846},"border",[3417,13327,5407],{"class":7550},[3417,13329,13184],{"class":4048},[3417,13331,13187],{"class":12980},[3417,13333,13334],{"class":12980}," #f59e0b55",[3417,13336,6207],{"class":7550},[3417,13338,13339,13342,13345,13347,13349,13352,13354,13356,13358,13361,13363,13365,13367,13369,13371,13374],{"class":3419,"line":5882},[3417,13340,13341],{"class":12962},"    .badge-open",[3417,13343,13344],{"class":7550},"       { ",[3417,13346,13306],{"class":12846},[3417,13348,5407],{"class":7550},[3417,13350,13351],{"class":12980},"#10b98122",[3417,13353,6174],{"class":7550},[3417,13355,13228],{"class":12846},[3417,13357,5407],{"class":7550},[3417,13359,13360],{"class":12980},"#10b981",[3417,13362,6174],{"class":7550},[3417,13364,13325],{"class":12846},[3417,13366,5407],{"class":7550},[3417,13368,13184],{"class":4048},[3417,13370,13187],{"class":12980},[3417,13372,13373],{"class":12980}," #10b98155",[3417,13375,6207],{"class":7550},[3417,13377,13378,13381,13384,13386,13388,13391,13393,13395,13397,13400,13402,13404,13406,13408,13410,13413],{"class":3419,"line":5888},[3417,13379,13380],{"class":12962},"    .badge-closed",[3417,13382,13383],{"class":7550},"     { ",[3417,13385,13306],{"class":12846},[3417,13387,5407],{"class":7550},[3417,13389,13390],{"class":12980},"#ef444422",[3417,13392,6174],{"class":7550},[3417,13394,13228],{"class":12846},[3417,13396,5407],{"class":7550},[3417,13398,13399],{"class":12980},"#ef4444",[3417,13401,6174],{"class":7550},[3417,13403,13325],{"class":12846},[3417,13405,5407],{"class":7550},[3417,13407,13184],{"class":4048},[3417,13409,13187],{"class":12980},[3417,13411,13412],{"class":12980}," #ef444455",[3417,13414,6207],{"class":7550},[3417,13416,13417],{"class":3419,"line":5893},[3417,13418,3442],{"emptyLinePlaceholder":3441},[3417,13420,13421],{"class":3419,"line":5898},[3417,13422,13423],{"class":6183},"    \u002F* ── Основний layout ────────────────────────── *\u002F\n",[3417,13425,13426,13429],{"class":3419,"line":5903},[3417,13427,13428],{"class":12962},"    main",[3417,13430,13013],{"class":7550},[3417,13432,13433,13436,13438,13440],{"class":3419,"line":5908},[3417,13434,13435],{"class":12846},"      flex",[3417,13437,5407],{"class":7550},[3417,13439,4449],{"class":4048},[3417,13441,6130],{"class":7550},[3417,13443,13444,13446,13448,13451],{"class":3419,"line":5914},[3417,13445,13076],{"class":12846},[3417,13447,5407],{"class":7550},[3417,13449,13450],{"class":12980},"grid",[3417,13452,6130],{"class":7550},[3417,13454,13455,13458,13460,13463,13466],{"class":3419,"line":5920},[3417,13456,13457],{"class":12846},"      grid-template-columns",[3417,13459,5407],{"class":7550},[3417,13461,13462],{"class":4048},"220px",[3417,13464,13465],{"class":4048}," 1fr",[3417,13467,6130],{"class":7550},[3417,13469,13470,13473,13475,13478],{"class":3419,"line":5925},[3417,13471,13472],{"class":12846},"      overflow",[3417,13474,5407],{"class":7550},[3417,13476,13477],{"class":12980},"hidden",[3417,13479,6130],{"class":7550},[3417,13481,13482],{"class":3419,"line":7017},[3417,13483,5765],{"class":7550},[3417,13485,13486],{"class":3419,"line":7028},[3417,13487,3442],{"emptyLinePlaceholder":3441},[3417,13489,13490],{"class":3419,"line":7033},[3417,13491,13492],{"class":6183},"    \u002F* ── Бокова панель (онлайн-список) ─────────── *\u002F\n",[3417,13494,13495,13498],{"class":3419,"line":7056},[3417,13496,13497],{"class":12962},"    aside",[3417,13499,13013],{"class":7550},[3417,13501,13502,13504,13506,13508],{"class":3419,"line":7075},[3417,13503,13040],{"class":12846},[3417,13505,5407],{"class":7550},[3417,13507,13124],{"class":12980},[3417,13509,6130],{"class":7550},[3417,13511,13512,13515,13517,13519,13521,13523],{"class":3419,"line":7105},[3417,13513,13514],{"class":12846},"      border-right",[3417,13516,5407],{"class":7550},[3417,13518,13184],{"class":4048},[3417,13520,13187],{"class":12980},[3417,13522,13190],{"class":12980},[3417,13524,6130],{"class":7550},[3417,13526,13527,13529,13531,13534],{"class":3419,"line":7116},[3417,13528,13131],{"class":12846},[3417,13530,5407],{"class":7550},[3417,13532,13533],{"class":4048},"16px",[3417,13535,6130],{"class":7550},[3417,13537,13538,13541,13543,13546],{"class":3419,"line":7121},[3417,13539,13540],{"class":12846},"      overflow-y",[3417,13542,5407],{"class":7550},[3417,13544,13545],{"class":12980},"auto",[3417,13547,6130],{"class":7550},[3417,13549,13550],{"class":3419,"line":7126},[3417,13551,5765],{"class":7550},[3417,13553,13554,13556,13559,13561,13563,13565,13567,13569,13572,13574,13577],{"class":3419,"line":7132},[3417,13555,13497],{"class":12962},[3417,13557,13558],{"class":12962}," h2",[3417,13560,6168],{"class":7550},[3417,13562,13208],{"class":12846},[3417,13564,5407],{"class":7550},[3417,13566,13252],{"class":4048},[3417,13568,6174],{"class":7550},[3417,13570,13571],{"class":12846},"text-transform",[3417,13573,5407],{"class":7550},[3417,13575,13576],{"class":12980},"uppercase",[3417,13578,6130],{"class":7550},[3417,13580,13581,13584,13586,13589,13591,13593,13595,13598,13600,13603,13605,13608],{"class":3419,"line":7138},[3417,13582,13583],{"class":12846},"               letter-spacing",[3417,13585,5407],{"class":7550},[3417,13587,13588],{"class":4048},"0.1em",[3417,13590,6174],{"class":7550},[3417,13592,13228],{"class":12846},[3417,13594,5407],{"class":7550},[3417,13596,13597],{"class":12980},"#64748b",[3417,13599,6174],{"class":7550},[3417,13601,13602],{"class":12846},"margin-bottom",[3417,13604,5407],{"class":7550},[3417,13606,13607],{"class":4048},"10px",[3417,13609,6207],{"class":7550},[3417,13611,13612,13615,13617,13620,13622,13625],{"class":3419,"line":7173},[3417,13613,13614],{"class":12962},"    #users-list",[3417,13616,6168],{"class":7550},[3417,13618,13619],{"class":12846},"list-style",[3417,13621,5407],{"class":7550},[3417,13623,13624],{"class":12980},"none",[3417,13626,6207],{"class":7550},[3417,13628,13629,13631,13634],{"class":3419,"line":7178},[3417,13630,13614],{"class":12962},[3417,13632,13633],{"class":12962}," li",[3417,13635,13013],{"class":7550},[3417,13637,13638,13640,13642,13645,13647],{"class":3419,"line":7184},[3417,13639,13131],{"class":12846},[3417,13641,5407],{"class":7550},[3417,13643,13644],{"class":4048},"6px",[3417,13646,13266],{"class":4048},[3417,13648,6130],{"class":7550},[3417,13650,13651,13653,13655,13658],{"class":3419,"line":7190},[3417,13652,13273],{"class":12846},[3417,13654,5407],{"class":7550},[3417,13656,13657],{"class":4048},"8px",[3417,13659,6130],{"class":7550},[3417,13661,13662,13664,13666,13669],{"class":3419,"line":7219},[3417,13663,13247],{"class":12846},[3417,13665,5407],{"class":7550},[3417,13667,13668],{"class":4048},"0.9rem",[3417,13670,6130],{"class":7550},[3417,13672,13673,13676,13678,13681],{"class":3419,"line":7242},[3417,13674,13675],{"class":12846},"      cursor",[3417,13677,5407],{"class":7550},[3417,13679,13680],{"class":12980},"pointer",[3417,13682,6130],{"class":7550},[3417,13684,13685,13688,13690,13693,13696],{"class":3419,"line":7248},[3417,13686,13687],{"class":12846},"      transition",[3417,13689,5407],{"class":7550},[3417,13691,13306],{"class":13692},"se1LK",[3417,13694,13695],{"class":4048}," 0.15s",[3417,13697,6130],{"class":7550},[3417,13699,13700,13702,13704,13706],{"class":3419,"line":7269},[3417,13701,13076],{"class":12846},[3417,13703,5407],{"class":7550},[3417,13705,13081],{"class":12980},[3417,13707,6130],{"class":7550},[3417,13709,13710,13712,13714,13716],{"class":3419,"line":7274},[3417,13711,13156],{"class":12846},[3417,13713,5407],{"class":7550},[3417,13715,13161],{"class":12980},[3417,13717,6130],{"class":7550},[3417,13719,13720,13722,13724,13726],{"class":3419,"line":8052},[3417,13721,13168],{"class":12846},[3417,13723,5407],{"class":7550},[3417,13725,13657],{"class":4048},[3417,13727,6130],{"class":7550},[3417,13729,13730],{"class":3419,"line":8058},[3417,13731,5765],{"class":7550},[3417,13733,13734,13736,13739,13741,13743,13745,13748],{"class":3419,"line":8064},[3417,13735,13614],{"class":12962},[3417,13737,13738],{"class":12962}," li:hover",[3417,13740,6168],{"class":7550},[3417,13742,13306],{"class":12846},[3417,13744,5407],{"class":7550},[3417,13746,13747],{"class":12980},"#0f3460",[3417,13749,6207],{"class":7550},[3417,13751,13752,13754,13757,13759,13761,13763,13765,13767,13769,13771,13773],{"class":3419,"line":8069},[3417,13753,13614],{"class":12962},[3417,13755,13756],{"class":12962}," li.me",[3417,13758,6168],{"class":7550},[3417,13760,13228],{"class":12846},[3417,13762,5407],{"class":7550},[3417,13764,13233],{"class":12980},[3417,13766,6174],{"class":7550},[3417,13768,13218],{"class":12846},[3417,13770,5407],{"class":7550},[3417,13772,13223],{"class":4048},[3417,13774,6207],{"class":7550},[3417,13776,13777,13779,13782,13784,13786,13788,13791,13793,13795,13797,13800,13802,13804,13806,13808],{"class":3419,"line":8107},[3417,13778,13614],{"class":12962},[3417,13780,13781],{"class":12962}," li::before",[3417,13783,6168],{"class":7550},[3417,13785,9801],{"class":12846},[3417,13787,5407],{"class":7550},[3417,13789,13790],{"class":4060},"'●'",[3417,13792,6174],{"class":7550},[3417,13794,13208],{"class":12846},[3417,13796,5407],{"class":7550},[3417,13798,13799],{"class":4048},"0.55rem",[3417,13801,6174],{"class":7550},[3417,13803,13228],{"class":12846},[3417,13805,5407],{"class":7550},[3417,13807,13360],{"class":12980},[3417,13809,6207],{"class":7550},[3417,13811,13812],{"class":3419,"line":8112},[3417,13813,3442],{"emptyLinePlaceholder":3441},[3417,13815,13816],{"class":3419,"line":8138},[3417,13817,13818],{"class":6183},"    \u002F* ── Область повідомлень ────────────────────── *\u002F\n",[3417,13820,13821,13824],{"class":3419,"line":8144},[3417,13822,13823],{"class":12962},"    .chat-area",[3417,13825,13013],{"class":7550},[3417,13827,13828,13830,13832,13834],{"class":3419,"line":8176},[3417,13829,13076],{"class":12846},[3417,13831,5407],{"class":7550},[3417,13833,13081],{"class":12980},[3417,13835,6130],{"class":7550},[3417,13837,13838,13840,13842,13844],{"class":3419,"line":8184},[3417,13839,13088],{"class":12846},[3417,13841,5407],{"class":7550},[3417,13843,13093],{"class":12980},[3417,13845,6130],{"class":7550},[3417,13847,13848,13850,13852,13854],{"class":3419,"line":8190},[3417,13849,13472],{"class":12846},[3417,13851,5407],{"class":7550},[3417,13853,13477],{"class":12980},[3417,13855,6130],{"class":7550},[3417,13857,13858],{"class":3419,"line":8195},[3417,13859,5765],{"class":7550},[3417,13861,13862],{"class":3419,"line":8224},[3417,13863,3442],{"emptyLinePlaceholder":3441},[3417,13865,13866,13869],{"class":3419,"line":8244},[3417,13867,13868],{"class":12962},"    #messages",[3417,13870,13013],{"class":7550},[3417,13872,13873,13875,13877,13879],{"class":3419,"line":8249},[3417,13874,13435],{"class":12846},[3417,13876,5407],{"class":7550},[3417,13878,4449],{"class":4048},[3417,13880,6130],{"class":7550},[3417,13882,13883,13885,13887,13889],{"class":3419,"line":8279},[3417,13884,13540],{"class":12846},[3417,13886,5407],{"class":7550},[3417,13888,13545],{"class":12980},[3417,13890,6130],{"class":7550},[3417,13892,13893,13895,13897,13899],{"class":3419,"line":8284},[3417,13894,13131],{"class":12846},[3417,13896,5407],{"class":7550},[3417,13898,13533],{"class":4048},[3417,13900,6130],{"class":7550},[3417,13902,13903,13905,13907,13909],{"class":3419,"line":8298},[3417,13904,13076],{"class":12846},[3417,13906,5407],{"class":7550},[3417,13908,13081],{"class":12980},[3417,13910,6130],{"class":7550},[3417,13912,13913,13915,13917,13919],{"class":3419,"line":8326},[3417,13914,13088],{"class":12846},[3417,13916,5407],{"class":7550},[3417,13918,13093],{"class":12980},[3417,13920,6130],{"class":7550},[3417,13922,13923,13925,13927,13929],{"class":3419,"line":8331},[3417,13924,13168],{"class":12846},[3417,13926,5407],{"class":7550},[3417,13928,13657],{"class":4048},[3417,13930,6130],{"class":7550},[3417,13932,13933,13936,13938,13941],{"class":3419,"line":8336},[3417,13934,13935],{"class":12846},"      scroll-behavior",[3417,13937,5407],{"class":7550},[3417,13939,13940],{"class":12980},"smooth",[3417,13942,6130],{"class":7550},[3417,13944,13945],{"class":3419,"line":8341},[3417,13946,5765],{"class":7550},[3417,13948,13949],{"class":3419,"line":8368},[3417,13950,3442],{"emptyLinePlaceholder":3441},[3417,13952,13953],{"class":3419,"line":8373},[3417,13954,13955],{"class":6183},"    \u002F* Базовий стиль повідомлення *\u002F\n",[3417,13957,13958,13961],{"class":3419,"line":8386},[3417,13959,13960],{"class":12962},"    .msg",[3417,13962,13013],{"class":7550},[3417,13964,13965,13968,13970,13973],{"class":3419,"line":8419},[3417,13966,13967],{"class":12846},"      max-width",[3417,13969,5407],{"class":7550},[3417,13971,13972],{"class":4048},"70%",[3417,13974,6130],{"class":7550},[3417,13976,13977,13979,13981,13983,13986],{"class":3419,"line":8431},[3417,13978,13131],{"class":12846},[3417,13980,5407],{"class":7550},[3417,13982,13657],{"class":4048},[3417,13984,13985],{"class":4048}," 14px",[3417,13987,6130],{"class":7550},[3417,13989,13990,13992,13994,13996],{"class":3419,"line":8452},[3417,13991,13273],{"class":12846},[3417,13993,5407],{"class":7550},[3417,13995,13136],{"class":4048},[3417,13997,6130],{"class":7550},[3417,13999,14000,14002,14004,14006],{"class":3419,"line":8457},[3417,14001,13247],{"class":12846},[3417,14003,5407],{"class":7550},[3417,14005,13668],{"class":4048},[3417,14007,6130],{"class":7550},[3417,14009,14010,14013,14015,14018],{"class":3419,"line":8474},[3417,14011,14012],{"class":12846},"      line-height",[3417,14014,5407],{"class":7550},[3417,14016,14017],{"class":4048},"1.5",[3417,14019,6130],{"class":7550},[3417,14021,14022,14025,14028,14031,14034],{"class":3419,"line":8479},[3417,14023,14024],{"class":12846},"      animation",[3417,14026,14027],{"class":7550},": fadeIn ",[3417,14029,14030],{"class":4048},"0.2s",[3417,14032,14033],{"class":12980}," ease",[3417,14035,6130],{"class":7550},[3417,14037,14038],{"class":3419,"line":8491},[3417,14039,5765],{"class":7550},[3417,14041,14042,14045,14048,14051,14054,14056,14058,14060,14063,14065,14068,14070,14073,14076,14078,14080,14082],{"class":3419,"line":8509},[3417,14043,14044],{"class":4033},"    @keyframes",[3417,14046,14047],{"class":12846}," fadeIn",[3417,14049,14050],{"class":7550}," { from { ",[3417,14052,14053],{"class":12846},"opacity",[3417,14055,5407],{"class":7550},[3417,14057,4453],{"class":4048},[3417,14059,6174],{"class":7550},[3417,14061,14062],{"class":12846},"transform",[3417,14064,5407],{"class":7550},[3417,14066,14067],{"class":6043},"translateY",[3417,14069,6537],{"class":7550},[3417,14071,14072],{"class":4048},"4px",[3417,14074,14075],{"class":7550},"); } to { ",[3417,14077,14053],{"class":12846},[3417,14079,5407],{"class":7550},[3417,14081,4449],{"class":4048},[3417,14083,14084],{"class":7550},"; } }\n",[3417,14086,14087],{"class":3419,"line":8514},[3417,14088,3442],{"emptyLinePlaceholder":3441},[3417,14090,14091],{"class":3419,"line":8547},[3417,14092,14093],{"class":6183},"    \u002F* Чужі повідомлення — ліворуч *\u002F\n",[3417,14095,14096,14099],{"class":3419,"line":8553},[3417,14097,14098],{"class":12962},"    .msg-other",[3417,14100,13013],{"class":7550},[3417,14102,14103,14106,14108,14111],{"class":3419,"line":8561},[3417,14104,14105],{"class":12846},"      align-self",[3417,14107,5407],{"class":7550},[3417,14109,14110],{"class":12980},"flex-start",[3417,14112,6130],{"class":7550},[3417,14114,14115,14117,14119,14121],{"class":3419,"line":8566},[3417,14116,13040],{"class":12846},[3417,14118,5407],{"class":7550},[3417,14120,13747],{"class":12980},[3417,14122,6130],{"class":7550},[3417,14124,14125,14128,14130,14132],{"class":3419,"line":8593},[3417,14126,14127],{"class":12846},"      border-bottom-left-radius",[3417,14129,5407],{"class":7550},[3417,14131,14072],{"class":4048},[3417,14133,6130],{"class":7550},[3417,14135,14136],{"class":3419,"line":8608},[3417,14137,5765],{"class":7550},[3417,14139,14140],{"class":3419,"line":8613},[3417,14141,14142],{"class":6183},"    \u002F* Мої повідомлення — праворуч *\u002F\n",[3417,14144,14145,14148],{"class":3419,"line":8655},[3417,14146,14147],{"class":12962},"    .msg-mine",[3417,14149,13013],{"class":7550},[3417,14151,14152,14154,14156,14159],{"class":3419,"line":8661},[3417,14153,14105],{"class":12846},[3417,14155,5407],{"class":7550},[3417,14157,14158],{"class":12980},"flex-end",[3417,14160,6130],{"class":7550},[3417,14162,14163,14165,14167,14169],{"class":3419,"line":8695},[3417,14164,13040],{"class":12846},[3417,14166,5407],{"class":7550},[3417,14168,13233],{"class":12980},[3417,14170,6130],{"class":7550},[3417,14172,14173,14175,14177,14180],{"class":3419,"line":8701},[3417,14174,13052],{"class":12846},[3417,14176,5407],{"class":7550},[3417,14178,14179],{"class":12980},"#fff",[3417,14181,6130],{"class":7550},[3417,14183,14184,14187,14189,14191],{"class":3419,"line":8733},[3417,14185,14186],{"class":12846},"      border-bottom-right-radius",[3417,14188,5407],{"class":7550},[3417,14190,14072],{"class":4048},[3417,14192,6130],{"class":7550},[3417,14194,14195],{"class":3419,"line":8738},[3417,14196,5765],{"class":7550},[3417,14198,14199],{"class":3419,"line":8749},[3417,14200,14201],{"class":6183},"    \u002F* Приватні — фіолетові *\u002F\n",[3417,14203,14204,14207],{"class":3419,"line":8773},[3417,14205,14206],{"class":12962},"    .msg-private",[3417,14208,13013],{"class":7550},[3417,14210,14211,14213,14215,14217],{"class":3419,"line":8778},[3417,14212,14105],{"class":12846},[3417,14214,5407],{"class":7550},[3417,14216,14110],{"class":12980},[3417,14218,6130],{"class":7550},[3417,14220,14221,14223,14225,14228],{"class":3419,"line":8785},[3417,14222,13040],{"class":12846},[3417,14224,5407],{"class":7550},[3417,14226,14227],{"class":12980},"#4c1d95",[3417,14229,6130],{"class":7550},[3417,14231,14232,14235,14237,14239,14241,14244],{"class":3419,"line":8790},[3417,14233,14234],{"class":12846},"      border",[3417,14236,5407],{"class":7550},[3417,14238,13184],{"class":4048},[3417,14240,13187],{"class":12980},[3417,14242,14243],{"class":12980}," #7c3aed",[3417,14245,6130],{"class":7550},[3417,14247,14248,14250,14252,14254],{"class":3419,"line":8795},[3417,14249,14127],{"class":12846},[3417,14251,5407],{"class":7550},[3417,14253,14072],{"class":4048},[3417,14255,6130],{"class":7550},[3417,14257,14258],{"class":3419,"line":11507},[3417,14259,5765],{"class":7550},[3417,14261,14262,14265],{"class":3419,"line":11512},[3417,14263,14264],{"class":12962},"    .msg-private.msg-mine",[3417,14266,13013],{"class":7550},[3417,14268,14269,14271,14273,14275],{"class":3419,"line":11525},[3417,14270,14105],{"class":12846},[3417,14272,5407],{"class":7550},[3417,14274,14158],{"class":12980},[3417,14276,6130],{"class":7550},[3417,14278,14279,14281,14283,14285],{"class":3419,"line":11530},[3417,14280,14186],{"class":12846},[3417,14282,5407],{"class":7550},[3417,14284,14072],{"class":4048},[3417,14286,6130],{"class":7550},[3417,14288,14289,14291,14293,14295],{"class":3419,"line":11535},[3417,14290,14127],{"class":12846},[3417,14292,5407],{"class":7550},[3417,14294,13136],{"class":4048},[3417,14296,6130],{"class":7550},[3417,14298,14299],{"class":3419,"line":11570},[3417,14300,5765],{"class":7550},[3417,14302,14303],{"class":3419,"line":11575},[3417,14304,14305],{"class":6183},"    \u002F* Системні — по центру *\u002F\n",[3417,14307,14308,14311],{"class":3419,"line":11581},[3417,14309,14310],{"class":12962},"    .msg-system",[3417,14312,13013],{"class":7550},[3417,14314,14315,14317,14319,14321],{"class":3419,"line":11587},[3417,14316,14105],{"class":12846},[3417,14318,5407],{"class":7550},[3417,14320,13161],{"class":12980},[3417,14322,6130],{"class":7550},[3417,14324,14325,14327,14329,14332],{"class":3419,"line":11628},[3417,14326,13040],{"class":12846},[3417,14328,5407],{"class":7550},[3417,14330,14331],{"class":12980},"transparent",[3417,14333,6130],{"class":7550},[3417,14335,14336,14338,14340,14342],{"class":3419,"line":11648},[3417,14337,13052],{"class":12846},[3417,14339,5407],{"class":7550},[3417,14341,13597],{"class":12980},[3417,14343,6130],{"class":7550},[3417,14345,14346,14348,14350,14353],{"class":3419,"line":11664},[3417,14347,13247],{"class":12846},[3417,14349,5407],{"class":7550},[3417,14351,14352],{"class":4048},"0.8rem",[3417,14354,6130],{"class":7550},[3417,14356,14357,14360,14362,14365],{"class":3419,"line":11689},[3417,14358,14359],{"class":12846},"      font-style",[3417,14361,5407],{"class":7550},[3417,14363,14364],{"class":12980},"italic",[3417,14366,6130],{"class":7550},[3417,14368,14369],{"class":3419,"line":11694},[3417,14370,5765],{"class":7550},[3417,14372,14373],{"class":3419,"line":11719},[3417,14374,3442],{"emptyLinePlaceholder":3441},[3417,14376,14377,14380],{"class":3419,"line":11724},[3417,14378,14379],{"class":12962},"    .msg-header",[3417,14381,13013],{"class":7550},[3417,14383,14384,14386,14388,14391],{"class":3419,"line":11736},[3417,14385,13247],{"class":12846},[3417,14387,5407],{"class":7550},[3417,14389,14390],{"class":4048},"0.72rem",[3417,14392,6130],{"class":7550},[3417,14394,14395,14397,14399,14401],{"class":3419,"line":11744},[3417,14396,13285],{"class":12846},[3417,14398,5407],{"class":7550},[3417,14400,13223],{"class":4048},[3417,14402,6130],{"class":7550},[3417,14404,14405,14408,14410,14413],{"class":3419,"line":11749},[3417,14406,14407],{"class":12846},"      margin-bottom",[3417,14409,5407],{"class":7550},[3417,14411,14412],{"class":4048},"2px",[3417,14414,6130],{"class":7550},[3417,14416,14417,14420,14422,14425],{"class":3419,"line":11754},[3417,14418,14419],{"class":12846},"      opacity",[3417,14421,5407],{"class":7550},[3417,14423,14424],{"class":4048},"0.8",[3417,14426,6130],{"class":7550},[3417,14428,14429],{"class":3419,"line":11760},[3417,14430,5765],{"class":7550},[3417,14432,14433,14436],{"class":3419,"line":11770},[3417,14434,14435],{"class":12962},"    .msg-time",[3417,14437,13013],{"class":7550},[3417,14439,14440,14442,14444,14447],{"class":3419,"line":11791},[3417,14441,13247],{"class":12846},[3417,14443,5407],{"class":7550},[3417,14445,14446],{"class":4048},"0.68rem",[3417,14448,6130],{"class":7550},[3417,14450,14451,14453,14455,14458],{"class":3419,"line":11796},[3417,14452,14419],{"class":12846},[3417,14454,5407],{"class":7550},[3417,14456,14457],{"class":4048},"0.5",[3417,14459,6130],{"class":7550},[3417,14461,14462,14465,14467,14469],{"class":3419,"line":11831},[3417,14463,14464],{"class":12846},"      margin-top",[3417,14466,5407],{"class":7550},[3417,14468,13263],{"class":4048},[3417,14470,6130],{"class":7550},[3417,14472,14473],{"class":3419,"line":11851},[3417,14474,5765],{"class":7550},[3417,14476,14477],{"class":3419,"line":11856},[3417,14478,3442],{"emptyLinePlaceholder":3441},[3417,14480,14481],{"class":3419,"line":11873},[3417,14482,14483],{"class":6183},"    \u002F* ── Поле введення ──────────────────────────── *\u002F\n",[3417,14485,14486,14489],{"class":3419,"line":11881},[3417,14487,14488],{"class":12962},"    .input-area",[3417,14490,13013],{"class":7550},[3417,14492,14493,14495,14497,14499,14502],{"class":3419,"line":11887},[3417,14494,13131],{"class":12846},[3417,14496,5407],{"class":7550},[3417,14498,13136],{"class":4048},[3417,14500,14501],{"class":4048}," 16px",[3417,14503,6130],{"class":7550},[3417,14505,14506,14508,14510,14512],{"class":3419,"line":11934},[3417,14507,13040],{"class":12846},[3417,14509,5407],{"class":7550},[3417,14511,13124],{"class":12980},[3417,14513,6130],{"class":7550},[3417,14515,14516,14519,14521,14523,14525,14527],{"class":3419,"line":11939},[3417,14517,14518],{"class":12846},"      border-top",[3417,14520,5407],{"class":7550},[3417,14522,13184],{"class":4048},[3417,14524,13187],{"class":12980},[3417,14526,13190],{"class":12980},[3417,14528,6130],{"class":7550},[3417,14530,14531,14533,14535,14537],{"class":3419,"line":11944},[3417,14532,13076],{"class":12846},[3417,14534,5407],{"class":7550},[3417,14536,13081],{"class":12980},[3417,14538,6130],{"class":7550},[3417,14540,14541,14543,14545,14547],{"class":3419,"line":11949},[3417,14542,13168],{"class":12846},[3417,14544,5407],{"class":7550},[3417,14546,13607],{"class":4048},[3417,14548,6130],{"class":7550},[3417,14550,14551,14553,14555,14557],{"class":3419,"line":11977},[3417,14552,13156],{"class":12846},[3417,14554,5407],{"class":7550},[3417,14556,14158],{"class":12980},[3417,14558,6130],{"class":7550},[3417,14560,14561],{"class":3419,"line":11982},[3417,14562,5765],{"class":7550},[3417,14564,14565,14568],{"class":3419,"line":11987},[3417,14566,14567],{"class":12962},"    .input-wrap",[3417,14569,13013],{"class":7550},[3417,14571,14572,14574,14576,14578],{"class":3419,"line":12012},[3417,14573,13435],{"class":12846},[3417,14575,5407],{"class":7550},[3417,14577,4449],{"class":4048},[3417,14579,6130],{"class":7550},[3417,14581,14582,14585,14587,14590],{"class":3419,"line":12039},[3417,14583,14584],{"class":12846},"      position",[3417,14586,5407],{"class":7550},[3417,14588,14589],{"class":12980},"relative",[3417,14591,6130],{"class":7550},[3417,14593,14594],{"class":3419,"line":12044},[3417,14595,5765],{"class":7550},[3417,14597,14598,14601],{"class":3419,"line":12072},[3417,14599,14600],{"class":12962},"    #private-target",[3417,14602,13013],{"class":7550},[3417,14604,14605,14607,14609,14611],{"class":3419,"line":12101},[3417,14606,13247],{"class":12846},[3417,14608,5407],{"class":7550},[3417,14610,13252],{"class":4048},[3417,14612,6130],{"class":7550},[3417,14614,14615,14617,14619,14622],{"class":3419,"line":12106},[3417,14616,13052],{"class":12846},[3417,14618,5407],{"class":7550},[3417,14620,14621],{"class":12980},"#7c3aed",[3417,14623,6130],{"class":7550},[3417,14625,14626,14628,14630,14632,14635],{"class":3419,"line":12111},[3417,14627,13131],{"class":12846},[3417,14629,5407],{"class":7550},[3417,14631,14412],{"class":4048},[3417,14633,14634],{"class":4048}," 8px",[3417,14636,6130],{"class":7550},[3417,14638,14639,14641,14643,14645],{"class":3419,"line":12116},[3417,14640,14407],{"class":12846},[3417,14642,5407],{"class":7550},[3417,14644,14072],{"class":4048},[3417,14646,6130],{"class":7550},[3417,14648,14649,14651,14653,14655],{"class":3419,"line":12122},[3417,14650,13076],{"class":12846},[3417,14652,5407],{"class":7550},[3417,14654,13624],{"class":12980},[3417,14656,6130],{"class":7550},[3417,14658,14659,14661,14663,14665],{"class":3419,"line":12132},[3417,14660,13156],{"class":12846},[3417,14662,5407],{"class":7550},[3417,14664,13161],{"class":12980},[3417,14666,6130],{"class":7550},[3417,14668,14669,14671,14673,14675],{"class":3419,"line":12137},[3417,14670,13168],{"class":12846},[3417,14672,5407],{"class":7550},[3417,14674,13644],{"class":4048},[3417,14676,6130],{"class":7550},[3417,14678,14679,14681,14683,14686],{"class":3419,"line":12169},[3417,14680,13040],{"class":12846},[3417,14682,5407],{"class":7550},[3417,14684,14685],{"class":12980},"#4c1d9520",[3417,14687,6130],{"class":7550},[3417,14689,14690,14692,14694,14696],{"class":3419,"line":12216},[3417,14691,13273],{"class":12846},[3417,14693,5407],{"class":7550},[3417,14695,14072],{"class":4048},[3417,14697,6130],{"class":7550},[3417,14699,14701],{"class":3419,"line":14700},168,[3417,14702,5765],{"class":7550},[3417,14704,14706,14709,14711,14714,14716,14718],{"class":3419,"line":14705},169,[3417,14707,14708],{"class":12962},"    #private-target.visible",[3417,14710,6168],{"class":7550},[3417,14712,14713],{"class":12846},"display",[3417,14715,5407],{"class":7550},[3417,14717,13081],{"class":12980},[3417,14719,6207],{"class":7550},[3417,14721,14723,14725,14728],{"class":3419,"line":14722},170,[3417,14724,14600],{"class":12962},[3417,14726,14727],{"class":12962}," button",[3417,14729,13013],{"class":7550},[3417,14731,14733,14735,14737,14739,14741,14743,14745,14747,14749,14751,14753,14755],{"class":3419,"line":14732},171,[3417,14734,13040],{"class":12846},[3417,14736,5407],{"class":7550},[3417,14738,13624],{"class":12980},[3417,14740,6174],{"class":7550},[3417,14742,13325],{"class":12846},[3417,14744,5407],{"class":7550},[3417,14746,13624],{"class":12980},[3417,14748,6174],{"class":7550},[3417,14750,13228],{"class":12846},[3417,14752,5407],{"class":7550},[3417,14754,13597],{"class":12980},[3417,14756,6130],{"class":7550},[3417,14758,14760,14762,14764,14766,14768,14770,14772,14774,14776,14778,14780,14782],{"class":3419,"line":14759},172,[3417,14761,13675],{"class":12846},[3417,14763,5407],{"class":7550},[3417,14765,13680],{"class":12980},[3417,14767,6174],{"class":7550},[3417,14769,13208],{"class":12846},[3417,14771,5407],{"class":7550},[3417,14773,14352],{"class":4048},[3417,14775,6174],{"class":7550},[3417,14777,12995],{"class":12846},[3417,14779,5407],{"class":7550},[3417,14781,4453],{"class":4048},[3417,14783,6130],{"class":7550},[3417,14785,14787],{"class":3419,"line":14786},173,[3417,14788,5765],{"class":7550},[3417,14790,14792,14795],{"class":3419,"line":14791},174,[3417,14793,14794],{"class":12962},"    #msg-input",[3417,14796,13013],{"class":7550},[3417,14798,14800,14803,14805,14808],{"class":3419,"line":14799},175,[3417,14801,14802],{"class":12846},"      width",[3417,14804,5407],{"class":7550},[3417,14806,14807],{"class":4048},"100%",[3417,14809,6130],{"class":7550},[3417,14811,14813,14815,14817,14819],{"class":3419,"line":14812},176,[3417,14814,13040],{"class":12846},[3417,14816,5407],{"class":7550},[3417,14818,13747],{"class":12980},[3417,14820,6130],{"class":7550},[3417,14822,14824,14826,14828,14830,14832,14835],{"class":3419,"line":14823},177,[3417,14825,14234],{"class":12846},[3417,14827,5407],{"class":7550},[3417,14829,13184],{"class":4048},[3417,14831,13187],{"class":12980},[3417,14833,14834],{"class":12980}," #1e4080",[3417,14836,6130],{"class":7550},[3417,14838,14840,14842,14844,14846],{"class":3419,"line":14839},178,[3417,14841,13052],{"class":12846},[3417,14843,5407],{"class":7550},[3417,14845,13057],{"class":12980},[3417,14847,6130],{"class":7550},[3417,14849,14851,14853,14855,14857],{"class":3419,"line":14850},179,[3417,14852,13273],{"class":12846},[3417,14854,5407],{"class":7550},[3417,14856,13607],{"class":4048},[3417,14858,6130],{"class":7550},[3417,14860,14862,14864,14866,14868,14870],{"class":3419,"line":14861},180,[3417,14863,13131],{"class":12846},[3417,14865,5407],{"class":7550},[3417,14867,13607],{"class":4048},[3417,14869,13985],{"class":4048},[3417,14871,6130],{"class":7550},[3417,14873,14875,14877,14879,14881],{"class":3419,"line":14874},181,[3417,14876,13247],{"class":12846},[3417,14878,5407],{"class":7550},[3417,14880,13668],{"class":4048},[3417,14882,6130],{"class":7550},[3417,14884,14886,14889,14891,14893],{"class":3419,"line":14885},182,[3417,14887,14888],{"class":12846},"      resize",[3417,14890,5407],{"class":7550},[3417,14892,13624],{"class":12980},[3417,14894,6130],{"class":7550},[3417,14896,14898,14901,14903,14906],{"class":3419,"line":14897},183,[3417,14899,14900],{"class":12846},"      min-height",[3417,14902,5407],{"class":7550},[3417,14904,14905],{"class":4048},"42px",[3417,14907,6130],{"class":7550},[3417,14909,14911,14914,14916,14919],{"class":3419,"line":14910},184,[3417,14912,14913],{"class":12846},"      max-height",[3417,14915,5407],{"class":7550},[3417,14917,14918],{"class":4048},"120px",[3417,14920,6130],{"class":7550},[3417,14922,14924,14927,14929,14931],{"class":3419,"line":14923},185,[3417,14925,14926],{"class":12846},"      outline",[3417,14928,5407],{"class":7550},[3417,14930,13624],{"class":12980},[3417,14932,6130],{"class":7550},[3417,14934,14936,14938,14941,14943],{"class":3419,"line":14935},186,[3417,14937,13687],{"class":12846},[3417,14939,14940],{"class":7550},": border-color ",[3417,14942,14030],{"class":4048},[3417,14944,6130],{"class":7550},[3417,14946,14948,14950,14952,14955],{"class":3419,"line":14947},187,[3417,14949,13018],{"class":12846},[3417,14951,5407],{"class":7550},[3417,14953,14954],{"class":12980},"inherit",[3417,14956,6130],{"class":7550},[3417,14958,14960],{"class":3419,"line":14959},188,[3417,14961,5765],{"class":7550},[3417,14963,14965,14968,14970,14973,14975,14977],{"class":3419,"line":14964},189,[3417,14966,14967],{"class":12962},"    #msg-input:focus",[3417,14969,6168],{"class":7550},[3417,14971,14972],{"class":12846},"border-color",[3417,14974,5407],{"class":7550},[3417,14976,13233],{"class":12980},[3417,14978,6207],{"class":7550},[3417,14980,14982],{"class":3419,"line":14981},190,[3417,14983,3442],{"emptyLinePlaceholder":3441},[3417,14985,14987,14990],{"class":3419,"line":14986},191,[3417,14988,14989],{"class":12962},"    #send-btn",[3417,14991,13013],{"class":7550},[3417,14993,14995,14997,14999,15001],{"class":3419,"line":14994},192,[3417,14996,13040],{"class":12846},[3417,14998,5407],{"class":7550},[3417,15000,13233],{"class":12980},[3417,15002,6130],{"class":7550},[3417,15004,15006,15008,15010,15013],{"class":3419,"line":15005},193,[3417,15007,13052],{"class":12846},[3417,15009,5407],{"class":7550},[3417,15011,15012],{"class":12980},"white",[3417,15014,6130],{"class":7550},[3417,15016,15018,15020,15022,15024],{"class":3419,"line":15017},194,[3417,15019,14234],{"class":12846},[3417,15021,5407],{"class":7550},[3417,15023,13624],{"class":12980},[3417,15025,6130],{"class":7550},[3417,15027,15029,15031,15033,15035],{"class":3419,"line":15028},195,[3417,15030,13273],{"class":12846},[3417,15032,5407],{"class":7550},[3417,15034,13607],{"class":4048},[3417,15036,6130],{"class":7550},[3417,15038,15040,15042,15044,15046],{"class":3419,"line":15039},196,[3417,15041,14802],{"class":12846},[3417,15043,5407],{"class":7550},[3417,15045,14905],{"class":4048},[3417,15047,6130],{"class":7550},[3417,15049,15051,15053,15055,15057],{"class":3419,"line":15050},197,[3417,15052,13064],{"class":12846},[3417,15054,5407],{"class":7550},[3417,15056,14905],{"class":4048},[3417,15058,6130],{"class":7550},[3417,15060,15062,15064,15066,15069],{"class":3419,"line":15061},198,[3417,15063,13247],{"class":12846},[3417,15065,5407],{"class":7550},[3417,15067,15068],{"class":4048},"1.2rem",[3417,15070,6130],{"class":7550},[3417,15072,15074,15076,15078,15080],{"class":3419,"line":15073},199,[3417,15075,13675],{"class":12846},[3417,15077,5407],{"class":7550},[3417,15079,13680],{"class":12980},[3417,15081,6130],{"class":7550},[3417,15083,15085,15087,15089,15091,15094,15097,15100],{"class":3419,"line":15084},200,[3417,15086,13687],{"class":12846},[3417,15088,5407],{"class":7550},[3417,15090,13306],{"class":13692},[3417,15092,15093],{"class":4048}," 0.2s",[3417,15095,15096],{"class":7550},", transform ",[3417,15098,15099],{"class":4048},"0.1s",[3417,15101,6130],{"class":7550},[3417,15103,15105,15107,15109,15111],{"class":3419,"line":15104},201,[3417,15106,13076],{"class":12846},[3417,15108,5407],{"class":7550},[3417,15110,13081],{"class":12980},[3417,15112,6130],{"class":7550},[3417,15114,15116,15118,15120,15122],{"class":3419,"line":15115},202,[3417,15117,13156],{"class":12846},[3417,15119,5407],{"class":7550},[3417,15121,13161],{"class":12980},[3417,15123,6130],{"class":7550},[3417,15125,15127,15130,15132,15134],{"class":3419,"line":15126},203,[3417,15128,15129],{"class":12846},"      justify-content",[3417,15131,5407],{"class":7550},[3417,15133,13161],{"class":12980},[3417,15135,6130],{"class":7550},[3417,15137,15139,15142,15144,15146],{"class":3419,"line":15138},204,[3417,15140,15141],{"class":12846},"      flex-shrink",[3417,15143,5407],{"class":7550},[3417,15145,4453],{"class":4048},[3417,15147,6130],{"class":7550},[3417,15149,15151],{"class":3419,"line":15150},205,[3417,15152,5765],{"class":7550},[3417,15154,15156,15159,15161,15163,15165,15168],{"class":3419,"line":15155},206,[3417,15157,15158],{"class":12962},"    #send-btn:hover",[3417,15160,6168],{"class":7550},[3417,15162,13306],{"class":12846},[3417,15164,5407],{"class":7550},[3417,15166,15167],{"class":12980},"#c73652",[3417,15169,6207],{"class":7550},[3417,15171,15173,15176,15178,15180,15182,15185,15187,15190],{"class":3419,"line":15172},207,[3417,15174,15175],{"class":12962},"    #send-btn:active",[3417,15177,6168],{"class":7550},[3417,15179,14062],{"class":12846},[3417,15181,5407],{"class":7550},[3417,15183,15184],{"class":6043},"scale",[3417,15186,6537],{"class":7550},[3417,15188,15189],{"class":4048},"0.95",[3417,15191,7796],{"class":7550},[3417,15193,15195,15198,15200,15202,15204,15207,15209,15212,15214,15217],{"class":3419,"line":15194},208,[3417,15196,15197],{"class":12962},"    #send-btn:disabled",[3417,15199,6168],{"class":7550},[3417,15201,13306],{"class":12846},[3417,15203,5407],{"class":7550},[3417,15205,15206],{"class":12980},"#374151",[3417,15208,6174],{"class":7550},[3417,15210,15211],{"class":12846},"cursor",[3417,15213,5407],{"class":7550},[3417,15215,15216],{"class":12980},"not-allowed",[3417,15218,6207],{"class":7550},[3417,15220,15222],{"class":3419,"line":15221},209,[3417,15223,3442],{"emptyLinePlaceholder":3441},[3417,15225,15227],{"class":3419,"line":15226},210,[3417,15228,15229],{"class":6183},"    \u002F* ── Вікно входу ────────────────────────────── *\u002F\n",[3417,15231,15233,15236],{"class":3419,"line":15232},211,[3417,15234,15235],{"class":12962},"    #login-overlay",[3417,15237,13013],{"class":7550},[3417,15239,15241,15243,15245,15248,15250,15253,15255,15257],{"class":3419,"line":15240},212,[3417,15242,14584],{"class":12846},[3417,15244,5407],{"class":7550},[3417,15246,15247],{"class":12980},"fixed",[3417,15249,6174],{"class":7550},[3417,15251,15252],{"class":12846},"inset",[3417,15254,5407],{"class":7550},[3417,15256,4453],{"class":4048},[3417,15258,6130],{"class":7550},[3417,15260,15262,15264,15266,15269],{"class":3419,"line":15261},213,[3417,15263,13040],{"class":12846},[3417,15265,5407],{"class":7550},[3417,15267,15268],{"class":12980},"#1a1a2eee",[3417,15270,6130],{"class":7550},[3417,15272,15274,15276,15278,15280],{"class":3419,"line":15273},214,[3417,15275,13076],{"class":12846},[3417,15277,5407],{"class":7550},[3417,15279,13081],{"class":12980},[3417,15281,6130],{"class":7550},[3417,15283,15285,15287,15289,15291],{"class":3419,"line":15284},215,[3417,15286,13156],{"class":12846},[3417,15288,5407],{"class":7550},[3417,15290,13161],{"class":12980},[3417,15292,6130],{"class":7550},[3417,15294,15296,15298,15300,15302],{"class":3419,"line":15295},216,[3417,15297,15129],{"class":12846},[3417,15299,5407],{"class":7550},[3417,15301,13161],{"class":12980},[3417,15303,6130],{"class":7550},[3417,15305,15307,15310,15312,15315],{"class":3419,"line":15306},217,[3417,15308,15309],{"class":12846},"      z-index",[3417,15311,5407],{"class":7550},[3417,15313,15314],{"class":4048},"100",[3417,15316,6130],{"class":7550},[3417,15318,15320,15323,15325,15328,15330,15332],{"class":3419,"line":15319},218,[3417,15321,15322],{"class":12846},"      backdrop-filter",[3417,15324,5407],{"class":7550},[3417,15326,15327],{"class":6043},"blur",[3417,15329,6537],{"class":7550},[3417,15331,14072],{"class":4048},[3417,15333,6577],{"class":7550},[3417,15335,15337],{"class":3419,"line":15336},219,[3417,15338,5765],{"class":7550},[3417,15340,15342,15345],{"class":3419,"line":15341},220,[3417,15343,15344],{"class":12962},"    .login-card",[3417,15346,13013],{"class":7550},[3417,15348,15350,15352,15354,15356],{"class":3419,"line":15349},221,[3417,15351,13040],{"class":12846},[3417,15353,5407],{"class":7550},[3417,15355,13124],{"class":12980},[3417,15357,6130],{"class":7550},[3417,15359,15361,15363,15365,15367,15369,15371],{"class":3419,"line":15360},222,[3417,15362,14234],{"class":12846},[3417,15364,5407],{"class":7550},[3417,15366,13184],{"class":4048},[3417,15368,13187],{"class":12980},[3417,15370,13190],{"class":12980},[3417,15372,6130],{"class":7550},[3417,15374,15376,15378,15380,15382],{"class":3419,"line":15375},223,[3417,15377,13273],{"class":12846},[3417,15379,5407],{"class":7550},[3417,15381,13533],{"class":4048},[3417,15383,6130],{"class":7550},[3417,15385,15387,15389,15391,15394],{"class":3419,"line":15386},224,[3417,15388,13131],{"class":12846},[3417,15390,5407],{"class":7550},[3417,15392,15393],{"class":4048},"32px",[3417,15395,6130],{"class":7550},[3417,15397,15399,15401,15403,15406],{"class":3419,"line":15398},225,[3417,15400,14802],{"class":12846},[3417,15402,5407],{"class":7550},[3417,15404,15405],{"class":4048},"320px",[3417,15407,6130],{"class":7550},[3417,15409,15411,15414,15416,15418],{"class":3419,"line":15410},226,[3417,15412,15413],{"class":12846},"      text-align",[3417,15415,5407],{"class":7550},[3417,15417,13161],{"class":12980},[3417,15419,6130],{"class":7550},[3417,15421,15423],{"class":3419,"line":15422},227,[3417,15424,5765],{"class":7550},[3417,15426,15428,15430,15432,15434,15436,15438,15440,15442,15444,15446,15448],{"class":3419,"line":15427},228,[3417,15429,15344],{"class":12962},[3417,15431,13558],{"class":12962},[3417,15433,6168],{"class":7550},[3417,15435,13228],{"class":12846},[3417,15437,5407],{"class":7550},[3417,15439,13233],{"class":12980},[3417,15441,6174],{"class":7550},[3417,15443,13602],{"class":12846},[3417,15445,5407],{"class":7550},[3417,15447,13657],{"class":4048},[3417,15449,6207],{"class":7550},[3417,15451,15453,15455,15458,15460,15462,15464,15466,15468,15470,15472,15475,15477,15479,15481,15484],{"class":3419,"line":15452},229,[3417,15454,15344],{"class":12962},[3417,15456,15457],{"class":12962}," p",[3417,15459,6168],{"class":7550},[3417,15461,13228],{"class":12846},[3417,15463,5407],{"class":7550},[3417,15465,13597],{"class":12980},[3417,15467,6174],{"class":7550},[3417,15469,13208],{"class":12846},[3417,15471,5407],{"class":7550},[3417,15473,15474],{"class":4048},"0.85rem",[3417,15476,6174],{"class":7550},[3417,15478,13602],{"class":12846},[3417,15480,5407],{"class":7550},[3417,15482,15483],{"class":4048},"24px",[3417,15485,6207],{"class":7550},[3417,15487,15489,15491,15494],{"class":3419,"line":15488},230,[3417,15490,15344],{"class":12962},[3417,15492,15493],{"class":12962}," input",[3417,15495,13013],{"class":7550},[3417,15497,15499,15501,15503,15505],{"class":3419,"line":15498},231,[3417,15500,14802],{"class":12846},[3417,15502,5407],{"class":7550},[3417,15504,14807],{"class":4048},[3417,15506,6130],{"class":7550},[3417,15508,15510,15512,15514,15516],{"class":3419,"line":15509},232,[3417,15511,13040],{"class":12846},[3417,15513,5407],{"class":7550},[3417,15515,13747],{"class":12980},[3417,15517,6130],{"class":7550},[3417,15519,15521,15523,15525,15527,15529,15531],{"class":3419,"line":15520},233,[3417,15522,14234],{"class":12846},[3417,15524,5407],{"class":7550},[3417,15526,13184],{"class":4048},[3417,15528,13187],{"class":12980},[3417,15530,14834],{"class":12980},[3417,15532,6130],{"class":7550},[3417,15534,15536,15538,15540,15542],{"class":3419,"line":15535},234,[3417,15537,13052],{"class":12846},[3417,15539,5407],{"class":7550},[3417,15541,13057],{"class":12980},[3417,15543,6130],{"class":7550},[3417,15545,15547,15549,15551,15553],{"class":3419,"line":15546},235,[3417,15548,13273],{"class":12846},[3417,15550,5407],{"class":7550},[3417,15552,13607],{"class":4048},[3417,15554,6130],{"class":7550},[3417,15556,15558,15560,15562,15564,15566],{"class":3419,"line":15557},236,[3417,15559,13131],{"class":12846},[3417,15561,5407],{"class":7550},[3417,15563,13607],{"class":4048},[3417,15565,13985],{"class":4048},[3417,15567,6130],{"class":7550},[3417,15569,15571,15573,15575,15578],{"class":3419,"line":15570},237,[3417,15572,13247],{"class":12846},[3417,15574,5407],{"class":7550},[3417,15576,15577],{"class":4048},"1rem",[3417,15579,6130],{"class":7550},[3417,15581,15583,15585,15587,15589],{"class":3419,"line":15582},238,[3417,15584,14926],{"class":12846},[3417,15586,5407],{"class":7550},[3417,15588,13624],{"class":12980},[3417,15590,6130],{"class":7550},[3417,15592,15594,15596,15598,15601],{"class":3419,"line":15593},239,[3417,15595,14407],{"class":12846},[3417,15597,5407],{"class":7550},[3417,15599,15600],{"class":4048},"14px",[3417,15602,6130],{"class":7550},[3417,15604,15606,15608,15610,15612],{"class":3419,"line":15605},240,[3417,15607,13018],{"class":12846},[3417,15609,5407],{"class":7550},[3417,15611,14954],{"class":12980},[3417,15613,6130],{"class":7550},[3417,15615,15617],{"class":3419,"line":15616},241,[3417,15618,5765],{"class":7550},[3417,15620,15622,15624,15627,15629,15631,15633,15635],{"class":3419,"line":15621},242,[3417,15623,15344],{"class":12962},[3417,15625,15626],{"class":12962}," input:focus",[3417,15628,6168],{"class":7550},[3417,15630,14972],{"class":12846},[3417,15632,5407],{"class":7550},[3417,15634,13233],{"class":12980},[3417,15636,6207],{"class":7550},[3417,15638,15640,15642,15644],{"class":3419,"line":15639},243,[3417,15641,15344],{"class":12962},[3417,15643,14727],{"class":12962},[3417,15645,13013],{"class":7550},[3417,15647,15649,15651,15653,15655],{"class":3419,"line":15648},244,[3417,15650,14802],{"class":12846},[3417,15652,5407],{"class":7550},[3417,15654,14807],{"class":4048},[3417,15656,6130],{"class":7550},[3417,15658,15660,15662,15664,15666],{"class":3419,"line":15659},245,[3417,15661,13040],{"class":12846},[3417,15663,5407],{"class":7550},[3417,15665,13233],{"class":12980},[3417,15667,6130],{"class":7550},[3417,15669,15671,15673,15675,15677],{"class":3419,"line":15670},246,[3417,15672,13052],{"class":12846},[3417,15674,5407],{"class":7550},[3417,15676,15012],{"class":12980},[3417,15678,6130],{"class":7550},[3417,15680,15682,15684,15686,15688],{"class":3419,"line":15681},247,[3417,15683,14234],{"class":12846},[3417,15685,5407],{"class":7550},[3417,15687,13624],{"class":12980},[3417,15689,6130],{"class":7550},[3417,15691,15693,15695,15697,15699],{"class":3419,"line":15692},248,[3417,15694,13273],{"class":12846},[3417,15696,5407],{"class":7550},[3417,15698,13607],{"class":4048},[3417,15700,6130],{"class":7550},[3417,15702,15704,15706,15708,15711],{"class":3419,"line":15703},249,[3417,15705,13131],{"class":12846},[3417,15707,5407],{"class":7550},[3417,15709,15710],{"class":4048},"11px",[3417,15712,6130],{"class":7550},[3417,15714,15716,15718,15720,15722],{"class":3419,"line":15715},250,[3417,15717,13247],{"class":12846},[3417,15719,5407],{"class":7550},[3417,15721,15577],{"class":4048},[3417,15723,6130],{"class":7550},[3417,15725,15727,15729,15731,15733],{"class":3419,"line":15726},251,[3417,15728,13675],{"class":12846},[3417,15730,5407],{"class":7550},[3417,15732,13680],{"class":12980},[3417,15734,6130],{"class":7550},[3417,15736,15738,15740,15742,15744],{"class":3419,"line":15737},252,[3417,15739,13285],{"class":12846},[3417,15741,5407],{"class":7550},[3417,15743,13223],{"class":4048},[3417,15745,6130],{"class":7550},[3417,15747,15749,15751,15753,15755,15757],{"class":3419,"line":15748},253,[3417,15750,13687],{"class":12846},[3417,15752,5407],{"class":7550},[3417,15754,13306],{"class":13692},[3417,15756,15093],{"class":4048},[3417,15758,6130],{"class":7550},[3417,15760,15762],{"class":3419,"line":15761},254,[3417,15763,5765],{"class":7550},[3417,15765,15767,15769,15772,15774,15776,15778,15780],{"class":3419,"line":15766},255,[3417,15768,15344],{"class":12962},[3417,15770,15771],{"class":12962}," button:hover",[3417,15773,6168],{"class":7550},[3417,15775,13306],{"class":12846},[3417,15777,5407],{"class":7550},[3417,15779,15167],{"class":12980},[3417,15781,6207],{"class":7550},[3417,15783,15785,15788,15790,15792,15794,15797,15799,15801,15803,15805,15807,15810,15812,15814],{"class":3419,"line":15784},256,[3417,15786,15787],{"class":12962},"    #login-error",[3417,15789,6168],{"class":7550},[3417,15791,13228],{"class":12846},[3417,15793,5407],{"class":7550},[3417,15795,15796],{"class":12980},"#f87171",[3417,15798,6174],{"class":7550},[3417,15800,13208],{"class":12846},[3417,15802,5407],{"class":7550},[3417,15804,14352],{"class":4048},[3417,15806,6174],{"class":7550},[3417,15808,15809],{"class":12846},"margin-top",[3417,15811,5407],{"class":7550},[3417,15813,13657],{"class":4048},[3417,15815,6207],{"class":7550},[3417,15817,15819,15822,15824],{"class":3419,"line":15818},257,[3417,15820,15821],{"class":12839},"  \u003C\u002F",[3417,15823,12947],{"class":4054},[3417,15825,12850],{"class":12839},[3417,15827,15829,15831,15833],{"class":3419,"line":15828},258,[3417,15830,12936],{"class":12839},[3417,15832,12875],{"class":4054},[3417,15834,12850],{"class":12839},[3417,15836,15838,15840,15842],{"class":3419,"line":15837},259,[3417,15839,6258],{"class":12839},[3417,15841,9442],{"class":4054},[3417,15843,12850],{"class":12839},[3417,15845,15847],{"class":3419,"line":15846},260,[3417,15848,3442],{"emptyLinePlaceholder":3441},[3417,15850,15852],{"class":3419,"line":15851},261,[3417,15853,15854],{"class":6183},"\u003C!-- ── Вікно входу ──────────────────────────────────── -->\n",[3417,15856,15858,15860,15863,15866,15868,15871],{"class":3419,"line":15857},262,[3417,15859,6258],{"class":12839},[3417,15861,15862],{"class":4054},"div",[3417,15864,15865],{"class":12846}," id",[3417,15867,12862],{"class":4037},[3417,15869,15870],{"class":12865},"\"login-overlay\"",[3417,15872,12850],{"class":12839},[3417,15874,15876,15878,15880,15882,15884,15887],{"class":3419,"line":15875},263,[3417,15877,12882],{"class":12839},[3417,15879,15862],{"class":4054},[3417,15881,6432],{"class":12846},[3417,15883,12862],{"class":4037},[3417,15885,15886],{"class":12865},"\"login-card\"",[3417,15888,12850],{"class":12839},[3417,15890,15892,15895,15897,15899,15902,15904,15906],{"class":3419,"line":15891},264,[3417,15893,15894],{"class":12839},"    \u003C",[3417,15896,3312],{"class":4054},[3417,15898,12930],{"class":12839},[3417,15900,15901],{"class":4037},"💬 WebSocket Chat",[3417,15903,12936],{"class":12839},[3417,15905,3312],{"class":4054},[3417,15907,12850],{"class":12839},[3417,15909,15911,15913,15915,15917,15920,15922,15924],{"class":3419,"line":15910},265,[3417,15912,15894],{"class":12839},[3417,15914,3317],{"class":4054},[3417,15916,12930],{"class":12839},[3417,15918,15919],{"class":4037},"Введіть нікнейм для входу в кімнату",[3417,15921,12936],{"class":12839},[3417,15923,3317],{"class":4054},[3417,15925,12850],{"class":12839},[3417,15927,15929,15931,15933,15935,15937,15940,15942,15944,15946,15949,15951],{"class":3419,"line":15928},266,[3417,15930,15894],{"class":12839},[3417,15932,11594],{"class":4054},[3417,15934,15865],{"class":12846},[3417,15936,12862],{"class":4037},[3417,15938,15939],{"class":12865},"\"username-input\"",[3417,15941,10976],{"class":12846},[3417,15943,12862],{"class":4037},[3417,15945,11167],{"class":12865},[3417,15947,15948],{"class":12846}," placeholder",[3417,15950,12862],{"class":4037},[3417,15952,15953],{"class":12865},"\"Ваш нікнейм...\"\n",[3417,15955,15957,15960,15962,15965,15968,15970,15973],{"class":3419,"line":15956},267,[3417,15958,15959],{"class":12846},"           maxlength",[3417,15961,12862],{"class":4037},[3417,15963,15964],{"class":12865},"\"20\"",[3417,15966,15967],{"class":12846}," autocomplete",[3417,15969,12862],{"class":4037},[3417,15971,15972],{"class":12865},"\"off\"",[3417,15974,12896],{"class":12839},[3417,15976,15978,15980,15983,15985,15987,15990,15992,15995,15997,15999],{"class":3419,"line":15977},268,[3417,15979,15894],{"class":12839},[3417,15981,15982],{"class":4054},"button",[3417,15984,15865],{"class":12846},[3417,15986,12862],{"class":4037},[3417,15988,15989],{"class":12865},"\"join-btn\"",[3417,15991,12930],{"class":12839},[3417,15993,15994],{"class":4037},"Увійти",[3417,15996,12936],{"class":12839},[3417,15998,15982],{"class":4054},[3417,16000,12850],{"class":12839},[3417,16002,16004,16006,16008,16010,16012,16015,16018,16020],{"class":3419,"line":16003},269,[3417,16005,15894],{"class":12839},[3417,16007,15862],{"class":4054},[3417,16009,15865],{"class":12846},[3417,16011,12862],{"class":4037},[3417,16013,16014],{"class":12865},"\"login-error\"",[3417,16016,16017],{"class":12839},">\u003C\u002F",[3417,16019,15862],{"class":4054},[3417,16021,12850],{"class":12839},[3417,16023,16025,16027,16029],{"class":3419,"line":16024},270,[3417,16026,15821],{"class":12839},[3417,16028,15862],{"class":4054},[3417,16030,12850],{"class":12839},[3417,16032,16034,16036,16038],{"class":3419,"line":16033},271,[3417,16035,12936],{"class":12839},[3417,16037,15862],{"class":4054},[3417,16039,12850],{"class":12839},[3417,16041,16043],{"class":3419,"line":16042},272,[3417,16044,3442],{"emptyLinePlaceholder":3441},[3417,16046,16048],{"class":3419,"line":16047},273,[3417,16049,16050],{"class":6183},"\u003C!-- ── Головний інтерфейс ──────────────────────────── -->\n",[3417,16052,16054,16056,16059],{"class":3419,"line":16053},274,[3417,16055,6258],{"class":12839},[3417,16057,16058],{"class":4054},"header",[3417,16060,12850],{"class":12839},[3417,16062,16064,16066,16068,16070,16072,16074,16076],{"class":3419,"line":16063},275,[3417,16065,12882],{"class":12839},[3417,16067,3308],{"class":4054},[3417,16069,12930],{"class":12839},[3417,16071,15901],{"class":4037},[3417,16073,12936],{"class":12839},[3417,16075,3308],{"class":4054},[3417,16077,12850],{"class":12839},[3417,16079,16081,16083,16085,16087,16089,16092,16094,16096,16099,16101,16104,16106,16108],{"class":3419,"line":16080},276,[3417,16082,12882],{"class":12839},[3417,16084,3417],{"class":4054},[3417,16086,15865],{"class":12846},[3417,16088,12862],{"class":4037},[3417,16090,16091],{"class":12865},"\"status-badge\"",[3417,16093,6432],{"class":12846},[3417,16095,12862],{"class":4037},[3417,16097,16098],{"class":12865},"\"badge-connecting\"",[3417,16100,12930],{"class":12839},[3417,16102,16103],{"class":4037},"● Підключення...",[3417,16105,12936],{"class":12839},[3417,16107,3417],{"class":4054},[3417,16109,12850],{"class":12839},[3417,16111,16113,16115,16117,16119,16121,16124,16127,16129,16132,16134,16136],{"class":3419,"line":16112},277,[3417,16114,12882],{"class":12839},[3417,16116,3417],{"class":4054},[3417,16118,15865],{"class":12846},[3417,16120,12862],{"class":4037},[3417,16122,16123],{"class":12865},"\"my-name\"",[3417,16125,16126],{"class":12846}," style",[3417,16128,12862],{"class":4037},[3417,16130,16131],{"class":12865},"\"margin-left:auto; color:#64748b; font-size:0.85rem\"",[3417,16133,16017],{"class":12839},[3417,16135,3417],{"class":4054},[3417,16137,12850],{"class":12839},[3417,16139,16141,16143,16145],{"class":3419,"line":16140},278,[3417,16142,12936],{"class":12839},[3417,16144,16058],{"class":4054},[3417,16146,12850],{"class":12839},[3417,16148,16150],{"class":3419,"line":16149},279,[3417,16151,3442],{"emptyLinePlaceholder":3441},[3417,16153,16155,16157,16160],{"class":3419,"line":16154},280,[3417,16156,6258],{"class":12839},[3417,16158,16159],{"class":4054},"main",[3417,16161,12850],{"class":12839},[3417,16163,16165,16167,16170],{"class":3419,"line":16164},281,[3417,16166,12882],{"class":12839},[3417,16168,16169],{"class":4054},"aside",[3417,16171,12850],{"class":12839},[3417,16173,16175,16177,16179,16181,16184,16186,16188],{"class":3419,"line":16174},282,[3417,16176,15894],{"class":12839},[3417,16178,3312],{"class":4054},[3417,16180,12930],{"class":12839},[3417,16182,16183],{"class":4037},"🟢 Онлайн",[3417,16185,12936],{"class":12839},[3417,16187,3312],{"class":4054},[3417,16189,12850],{"class":12839},[3417,16191,16193,16195,16197,16199,16201,16204,16206,16208],{"class":3419,"line":16192},283,[3417,16194,15894],{"class":12839},[3417,16196,4769],{"class":4054},[3417,16198,15865],{"class":12846},[3417,16200,12862],{"class":4037},[3417,16202,16203],{"class":12865},"\"users-list\"",[3417,16205,16017],{"class":12839},[3417,16207,4769],{"class":4054},[3417,16209,12850],{"class":12839},[3417,16211,16213,16215,16217],{"class":3419,"line":16212},284,[3417,16214,15821],{"class":12839},[3417,16216,16169],{"class":4054},[3417,16218,12850],{"class":12839},[3417,16220,16222],{"class":3419,"line":16221},285,[3417,16223,3442],{"emptyLinePlaceholder":3441},[3417,16225,16227,16229,16231,16233,16235,16238],{"class":3419,"line":16226},286,[3417,16228,12882],{"class":12839},[3417,16230,15862],{"class":4054},[3417,16232,6432],{"class":12846},[3417,16234,12862],{"class":4037},[3417,16236,16237],{"class":12865},"\"chat-area\"",[3417,16239,12850],{"class":12839},[3417,16241,16243,16245,16247,16249,16251,16254,16256,16258],{"class":3419,"line":16242},287,[3417,16244,15894],{"class":12839},[3417,16246,15862],{"class":4054},[3417,16248,15865],{"class":12846},[3417,16250,12862],{"class":4037},[3417,16252,16253],{"class":12865},"\"messages\"",[3417,16255,16017],{"class":12839},[3417,16257,15862],{"class":4054},[3417,16259,12850],{"class":12839},[3417,16261,16263,16265,16267,16269,16271,16274],{"class":3419,"line":16262},288,[3417,16264,15894],{"class":12839},[3417,16266,15862],{"class":4054},[3417,16268,6432],{"class":12846},[3417,16270,12862],{"class":4037},[3417,16272,16273],{"class":12865},"\"input-area\"",[3417,16275,12850],{"class":12839},[3417,16277,16279,16282,16284,16286,16288,16291],{"class":3419,"line":16278},289,[3417,16280,16281],{"class":12839},"      \u003C",[3417,16283,15862],{"class":4054},[3417,16285,6432],{"class":12846},[3417,16287,12862],{"class":4037},[3417,16289,16290],{"class":12865},"\"input-wrap\"",[3417,16292,12850],{"class":12839},[3417,16294,16296,16299,16301,16303,16305,16308],{"class":3419,"line":16295},290,[3417,16297,16298],{"class":12839},"        \u003C",[3417,16300,15862],{"class":4054},[3417,16302,15865],{"class":12846},[3417,16304,12862],{"class":4037},[3417,16306,16307],{"class":12865},"\"private-target\"",[3417,16309,12850],{"class":12839},[3417,16311,16313,16316,16318,16320,16322,16325,16327,16330,16332,16334,16336,16338,16340,16342],{"class":3419,"line":16312},291,[3417,16314,16315],{"class":12839},"          \u003C",[3417,16317,3417],{"class":4054},[3417,16319,15865],{"class":12846},[3417,16321,12862],{"class":4037},[3417,16323,16324],{"class":12865},"\"private-label\"",[3417,16326,12930],{"class":12839},[3417,16328,16329],{"class":4037},"🔒 Приватно до: ",[3417,16331,6258],{"class":12839},[3417,16333,3321],{"class":4054},[3417,16335,16017],{"class":12839},[3417,16337,3321],{"class":4054},[3417,16339,16017],{"class":12839},[3417,16341,3417],{"class":4054},[3417,16343,12850],{"class":12839},[3417,16345,16347,16349,16351,16353,16355,16358,16361,16363,16366,16368,16371,16373,16375],{"class":3419,"line":16346},292,[3417,16348,16315],{"class":12839},[3417,16350,15982],{"class":4054},[3417,16352,15865],{"class":12846},[3417,16354,12862],{"class":4037},[3417,16356,16357],{"class":12865},"\"cancel-private\"",[3417,16359,16360],{"class":12846}," title",[3417,16362,12862],{"class":4037},[3417,16364,16365],{"class":12865},"\"Скасувати\"",[3417,16367,12930],{"class":12839},[3417,16369,16370],{"class":4037},"✕",[3417,16372,12936],{"class":12839},[3417,16374,15982],{"class":4054},[3417,16376,12850],{"class":12839},[3417,16378,16380,16383,16385],{"class":3419,"line":16379},293,[3417,16381,16382],{"class":12839},"        \u003C\u002F",[3417,16384,15862],{"class":4054},[3417,16386,12850],{"class":12839},[3417,16388,16390,16392,16395,16397,16399,16402,16404,16406],{"class":3419,"line":16389},294,[3417,16391,16298],{"class":12839},[3417,16393,16394],{"class":4054},"textarea",[3417,16396,15865],{"class":12846},[3417,16398,12862],{"class":4037},[3417,16400,16401],{"class":12865},"\"msg-input\"",[3417,16403,15948],{"class":12846},[3417,16405,12862],{"class":4037},[3417,16407,16408],{"class":12865},"\"Введіть повідомлення... (Enter — надіслати, Shift+Enter — новий рядок)\"\n",[3417,16410,16412,16415,16417,16420,16422,16424],{"class":3419,"line":16411},295,[3417,16413,16414],{"class":12846},"                  rows",[3417,16416,12862],{"class":4037},[3417,16418,16419],{"class":12865},"\"1\"",[3417,16421,16017],{"class":12839},[3417,16423,16394],{"class":4054},[3417,16425,12850],{"class":12839},[3417,16427,16429,16432,16434],{"class":3419,"line":16428},296,[3417,16430,16431],{"class":12839},"      \u003C\u002F",[3417,16433,15862],{"class":4054},[3417,16435,12850],{"class":12839},[3417,16437,16439,16441,16443,16445,16447,16450,16453,16455,16457,16460,16462,16465,16467,16469],{"class":3419,"line":16438},297,[3417,16440,16281],{"class":12839},[3417,16442,15982],{"class":4054},[3417,16444,15865],{"class":12846},[3417,16446,12862],{"class":4037},[3417,16448,16449],{"class":12865},"\"send-btn\"",[3417,16451,16452],{"class":12846}," disabled",[3417,16454,16360],{"class":12846},[3417,16456,12862],{"class":4037},[3417,16458,16459],{"class":12865},"\"Надіслати\"",[3417,16461,12930],{"class":12839},[3417,16463,16464],{"class":4037},"➤",[3417,16466,12936],{"class":12839},[3417,16468,15982],{"class":4054},[3417,16470,12850],{"class":12839},[3417,16472,16474,16477,16479],{"class":3419,"line":16473},298,[3417,16475,16476],{"class":12839},"    \u003C\u002F",[3417,16478,15862],{"class":4054},[3417,16480,12850],{"class":12839},[3417,16482,16484,16486,16488],{"class":3419,"line":16483},299,[3417,16485,15821],{"class":12839},[3417,16487,15862],{"class":4054},[3417,16489,12850],{"class":12839},[3417,16491,16493,16495,16497],{"class":3419,"line":16492},300,[3417,16494,12936],{"class":12839},[3417,16496,16159],{"class":4054},[3417,16498,12850],{"class":12839},[3417,16500,16502],{"class":3419,"line":16501},301,[3417,16503,3442],{"emptyLinePlaceholder":3441},[3417,16505,16507,16509,16512],{"class":3419,"line":16506},302,[3417,16508,6258],{"class":12839},[3417,16510,16511],{"class":4054},"script",[3417,16513,12850],{"class":12839},[3417,16515,16517],{"class":3419,"line":16516},303,[3417,16518,16519],{"class":6183},"  \u002F\u002F ── Стан застосунку ──────────────────────────────────\n",[3417,16521,16523,16526,16528,16531,16533],{"class":3419,"line":16522},304,[3417,16524,16525],{"class":4041},"  let",[3417,16527,7461],{"class":6164},[3417,16529,16530],{"class":4037}," =",[3417,16532,8443],{"class":4041},[3417,16534,6130],{"class":7550},[3417,16536,16538,16540,16543,16545,16548],{"class":3419,"line":16537},305,[3417,16539,16525],{"class":4041},[3417,16541,16542],{"class":6164}," myUsername",[3417,16544,16530],{"class":4037},[3417,16546,16547],{"class":4060}," ''",[3417,16549,6130],{"class":7550},[3417,16551,16553,16555,16558,16560,16562,16564],{"class":3419,"line":16552},306,[3417,16554,16525],{"class":4041},[3417,16556,16557],{"class":6164}," privateTarget",[3417,16559,16530],{"class":4037},[3417,16561,8443],{"class":4041},[3417,16563,6174],{"class":7550},[3417,16565,16566],{"class":6183},"\u002F\u002F username для приватного повідомлення або null\n",[3417,16568,16570],{"class":3419,"line":16569},307,[3417,16571,3442],{"emptyLinePlaceholder":3441},[3417,16573,16575,16578,16581,16583,16585,16587,16590,16592,16595,16597,16600],{"class":3419,"line":16574},308,[3417,16576,16577],{"class":4041},"  const",[3417,16579,16580],{"class":6043}," $",[3417,16582,16530],{"class":4037},[3417,16584,15865],{"class":6164},[3417,16586,11313],{"class":4041},[3417,16588,16589],{"class":6164}," document",[3417,16591,4253],{"class":7550},[3417,16593,16594],{"class":6043},"getElementById",[3417,16596,6537],{"class":7550},[3417,16598,16599],{"class":6164},"id",[3417,16601,6577],{"class":7550},[3417,16603,16605],{"class":3419,"line":16604},309,[3417,16606,3442],{"emptyLinePlaceholder":3441},[3417,16608,16610],{"class":3419,"line":16609},310,[3417,16611,16612],{"class":6183},"  \u002F\u002F ── Елементи DOM ─────────────────────────────────────\n",[3417,16614,16616,16618,16621,16624,16626,16628,16631],{"class":3419,"line":16615},311,[3417,16617,16577],{"class":4041},[3417,16619,16620],{"class":12627}," overlay",[3417,16622,16623],{"class":4037},"     =",[3417,16625,16580],{"class":6043},[3417,16627,6537],{"class":7550},[3417,16629,16630],{"class":4060},"'login-overlay'",[3417,16632,6577],{"class":7550},[3417,16634,16636,16638,16641,16644,16646,16648,16651],{"class":3419,"line":16635},312,[3417,16637,16577],{"class":4041},[3417,16639,16640],{"class":12627}," usernameIn",[3417,16642,16643],{"class":4037},"  =",[3417,16645,16580],{"class":6043},[3417,16647,6537],{"class":7550},[3417,16649,16650],{"class":4060},"'username-input'",[3417,16652,6577],{"class":7550},[3417,16654,16656,16658,16661,16663,16665,16667,16670],{"class":3419,"line":16655},313,[3417,16657,16577],{"class":4041},[3417,16659,16660],{"class":12627}," joinBtn",[3417,16662,16623],{"class":4037},[3417,16664,16580],{"class":6043},[3417,16666,6537],{"class":7550},[3417,16668,16669],{"class":4060},"'join-btn'",[3417,16671,6577],{"class":7550},[3417,16673,16675,16677,16680,16682,16684,16686,16689],{"class":3419,"line":16674},314,[3417,16676,16577],{"class":4041},[3417,16678,16679],{"class":12627}," loginError",[3417,16681,16643],{"class":4037},[3417,16683,16580],{"class":6043},[3417,16685,6537],{"class":7550},[3417,16687,16688],{"class":4060},"'login-error'",[3417,16690,6577],{"class":7550},[3417,16692,16694,16696,16699,16701,16703,16705,16708],{"class":3419,"line":16693},315,[3417,16695,16577],{"class":4041},[3417,16697,16698],{"class":12627}," statusBadge",[3417,16700,16530],{"class":4037},[3417,16702,16580],{"class":6043},[3417,16704,6537],{"class":7550},[3417,16706,16707],{"class":4060},"'status-badge'",[3417,16709,6577],{"class":7550},[3417,16711,16713,16715,16718,16721,16723,16725,16728],{"class":3419,"line":16712},316,[3417,16714,16577],{"class":4041},[3417,16716,16717],{"class":12627}," myNameEl",[3417,16719,16720],{"class":4037},"    =",[3417,16722,16580],{"class":6043},[3417,16724,6537],{"class":7550},[3417,16726,16727],{"class":4060},"'my-name'",[3417,16729,6577],{"class":7550},[3417,16731,16733,16735,16738,16740,16742,16744,16747],{"class":3419,"line":16732},317,[3417,16734,16577],{"class":4041},[3417,16736,16737],{"class":12627}," messagesList",[3417,16739,16530],{"class":4037},[3417,16741,16580],{"class":6043},[3417,16743,6537],{"class":7550},[3417,16745,16746],{"class":4060},"'messages'",[3417,16748,6577],{"class":7550},[3417,16750,16752,16754,16757,16760,16762,16764,16767],{"class":3419,"line":16751},318,[3417,16753,16577],{"class":4041},[3417,16755,16756],{"class":12627}," usersList",[3417,16758,16759],{"class":4037},"   =",[3417,16761,16580],{"class":6043},[3417,16763,6537],{"class":7550},[3417,16765,16766],{"class":4060},"'users-list'",[3417,16768,6577],{"class":7550},[3417,16770,16772,16774,16777,16779,16781,16783,16786],{"class":3419,"line":16771},319,[3417,16773,16577],{"class":4041},[3417,16775,16776],{"class":12627}," msgInput",[3417,16778,16720],{"class":4037},[3417,16780,16580],{"class":6043},[3417,16782,6537],{"class":7550},[3417,16784,16785],{"class":4060},"'msg-input'",[3417,16787,6577],{"class":7550},[3417,16789,16791,16793,16796,16798,16800,16802,16805],{"class":3419,"line":16790},320,[3417,16792,16577],{"class":4041},[3417,16794,16795],{"class":12627}," sendBtn",[3417,16797,16623],{"class":4037},[3417,16799,16580],{"class":6043},[3417,16801,6537],{"class":7550},[3417,16803,16804],{"class":4060},"'send-btn'",[3417,16806,6577],{"class":7550},[3417,16808,16810,16812,16815,16817,16819,16821,16824],{"class":3419,"line":16809},321,[3417,16811,16577],{"class":4041},[3417,16813,16814],{"class":12627}," privateDiv",[3417,16816,16643],{"class":4037},[3417,16818,16580],{"class":6043},[3417,16820,6537],{"class":7550},[3417,16822,16823],{"class":4060},"'private-target'",[3417,16825,6577],{"class":7550},[3417,16827,16829,16831,16834,16836,16838,16840,16843,16845,16848,16850,16853],{"class":3419,"line":16828},322,[3417,16830,16577],{"class":4041},[3417,16832,16833],{"class":12627}," privateLabel",[3417,16835,16530],{"class":4037},[3417,16837,16580],{"class":6043},[3417,16839,6537],{"class":7550},[3417,16841,16842],{"class":4060},"'private-label'",[3417,16844,10992],{"class":7550},[3417,16846,16847],{"class":6043},"querySelector",[3417,16849,6537],{"class":7550},[3417,16851,16852],{"class":4060},"'strong'",[3417,16854,6577],{"class":7550},[3417,16856,16858],{"class":3419,"line":16857},323,[3417,16859,3442],{"emptyLinePlaceholder":3441},[3417,16861,16863],{"class":3419,"line":16862},324,[3417,16864,16865],{"class":6183},"  \u002F\u002F ── Підключення до сервера ───────────────────────────\n",[3417,16867,16869,16872,16875,16877,16879],{"class":3419,"line":16868},325,[3417,16870,16871],{"class":4041},"  function",[3417,16873,16874],{"class":6043}," connect",[3417,16876,6537],{"class":7550},[3417,16878,6569],{"class":6164},[3417,16880,16881],{"class":7550},") {\n",[3417,16883,16885,16888,16890,16892],{"class":3419,"line":16884},326,[3417,16886,16887],{"class":6164},"    myUsername",[3417,16889,16530],{"class":4037},[3417,16891,6542],{"class":6164},[3417,16893,6130],{"class":7550},[3417,16895,16897],{"class":3419,"line":16896},327,[3417,16898,16899],{"class":6183},"    \u002F\u002F Визначаємо URL: якщо HTTPS — використовуємо wss:\u002F\u002F, якщо HTTP — ws:\u002F\u002F\n",[3417,16901,16903,16906,16909,16911,16914,16916,16919,16922,16925,16928,16931,16934,16937],{"class":3419,"line":16902},328,[3417,16904,16905],{"class":4041},"    const",[3417,16907,16908],{"class":12627}," protocol",[3417,16910,16530],{"class":4037},[3417,16912,16913],{"class":6164}," location",[3417,16915,4253],{"class":7550},[3417,16917,16918],{"class":6164},"protocol",[3417,16920,16921],{"class":4037}," ===",[3417,16923,16924],{"class":4060}," 'https:'",[3417,16926,16927],{"class":4037}," ?",[3417,16929,16930],{"class":4060}," 'wss:'",[3417,16932,16933],{"class":4037}," :",[3417,16935,16936],{"class":4060}," 'ws:'",[3417,16938,6130],{"class":7550},[3417,16940,16942,16944,16947,16949,16952,16955,16957,16959,16962,16964,16967,16969,16972,16974,16977,16979,16982,16984,16986,16988,16990,16993],{"class":3419,"line":16941},329,[3417,16943,16905],{"class":4041},[3417,16945,16946],{"class":12627}," url",[3417,16948,16530],{"class":4037},[3417,16950,16951],{"class":4060}," `",[3417,16953,16954],{"class":4041},"${",[3417,16956,16918],{"class":6164},[3417,16958,7555],{"class":4041},[3417,16960,16961],{"class":4060},"\u002F\u002F",[3417,16963,16954],{"class":4041},[3417,16965,16966],{"class":6164},"location",[3417,16968,4253],{"class":7550},[3417,16970,16971],{"class":6164},"host",[3417,16973,7555],{"class":4041},[3417,16975,16976],{"class":4060},"\u002Fws?user=",[3417,16978,16954],{"class":4041},[3417,16980,16981],{"class":6043},"encodeURIComponent",[3417,16983,6537],{"class":7550},[3417,16985,6569],{"class":6164},[3417,16987,3996],{"class":7550},[3417,16989,7555],{"class":4041},[3417,16991,16992],{"class":4060},"`",[3417,16994,6130],{"class":7550},[3417,16996,16998],{"class":3419,"line":16997},330,[3417,16999,3442],{"emptyLinePlaceholder":3441},[3417,17001,17003,17006,17008,17010,17012,17014,17017],{"class":3419,"line":17002},331,[3417,17004,17005],{"class":6164},"    ws",[3417,17007,16530],{"class":4037},[3417,17009,6047],{"class":4041},[3417,17011,6987],{"class":6043},[3417,17013,6537],{"class":7550},[3417,17015,17016],{"class":6164},"url",[3417,17018,6577],{"class":7550},[3417,17020,17022],{"class":3419,"line":17021},332,[3417,17023,3442],{"emptyLinePlaceholder":3441},[3417,17025,17027,17030,17032,17035],{"class":3419,"line":17026},333,[3417,17028,17029],{"class":6043},"    setStatus",[3417,17031,6537],{"class":7550},[3417,17033,17034],{"class":4060},"'connecting'",[3417,17036,6577],{"class":7550},[3417,17038,17040],{"class":3419,"line":17039},334,[3417,17041,3442],{"emptyLinePlaceholder":3441},[3417,17043,17045],{"class":3419,"line":17044},335,[3417,17046,17047],{"class":6183},"    \u002F\u002F Підключення встановлено\n",[3417,17049,17051,17053,17055,17058,17060,17063,17066],{"class":3419,"line":17050},336,[3417,17052,17005],{"class":6164},[3417,17054,4253],{"class":7550},[3417,17056,17057],{"class":6043},"onopen",[3417,17059,16530],{"class":4037},[3417,17061,17062],{"class":7550}," () ",[3417,17064,17065],{"class":4041},"=>",[3417,17067,13013],{"class":7550},[3417,17069,17071,17074,17076,17079],{"class":3419,"line":17070},337,[3417,17072,17073],{"class":6043},"      setStatus",[3417,17075,6537],{"class":7550},[3417,17077,17078],{"class":4060},"'open'",[3417,17080,6577],{"class":7550},[3417,17082,17084,17087,17089,17092,17094,17096],{"class":3419,"line":17083},338,[3417,17085,17086],{"class":6164},"      sendBtn",[3417,17088,4253],{"class":7550},[3417,17090,17091],{"class":6164},"disabled",[3417,17093,16530],{"class":4037},[3417,17095,7023],{"class":4041},[3417,17097,6130],{"class":7550},[3417,17099,17101,17104,17106,17108,17110,17112,17114,17117],{"class":3419,"line":17100},339,[3417,17102,17103],{"class":6164},"      overlay",[3417,17105,4253],{"class":7550},[3417,17107,12947],{"class":6164},[3417,17109,4253],{"class":7550},[3417,17111,14713],{"class":6164},[3417,17113,16530],{"class":4037},[3417,17115,17116],{"class":4060}," 'none'",[3417,17118,6130],{"class":7550},[3417,17120,17122,17125,17127,17130,17132,17135,17137,17140,17142,17144],{"class":3419,"line":17121},340,[3417,17123,17124],{"class":6164},"      myNameEl",[3417,17126,4253],{"class":7550},[3417,17128,17129],{"class":6164},"textContent",[3417,17131,16530],{"class":4037},[3417,17133,17134],{"class":4060}," `Ви: ",[3417,17136,16954],{"class":4041},[3417,17138,17139],{"class":6164},"myUsername",[3417,17141,7555],{"class":4041},[3417,17143,16992],{"class":4060},[3417,17145,6130],{"class":7550},[3417,17147,17149,17152,17154,17157],{"class":3419,"line":17148},341,[3417,17150,17151],{"class":6043},"      addSystemMessage",[3417,17153,6537],{"class":7550},[3417,17155,17156],{"class":4060},"'✅ Підключено до чату'",[3417,17158,6577],{"class":7550},[3417,17160,17162],{"class":3419,"line":17161},342,[3417,17163,6520],{"class":7550},[3417,17165,17167],{"class":3419,"line":17166},343,[3417,17168,3442],{"emptyLinePlaceholder":3441},[3417,17170,17172],{"class":3419,"line":17171},344,[3417,17173,17174],{"class":6183},"    \u002F\u002F Отримано повідомлення від сервера\n",[3417,17176,17178,17180,17182,17184,17186,17188,17190,17192,17194],{"class":3419,"line":17177},345,[3417,17179,17005],{"class":6164},[3417,17181,4253],{"class":7550},[3417,17183,12789],{"class":6043},[3417,17185,16530],{"class":4037},[3417,17187,7254],{"class":7550},[3417,17189,12680],{"class":6164},[3417,17191,8271],{"class":7550},[3417,17193,17065],{"class":4041},[3417,17195,13013],{"class":7550},[3417,17197,17199],{"class":3419,"line":17198},346,[3417,17200,17201],{"class":6183},"      \u002F\u002F event.data завжди рядок для Text frames\n",[3417,17203,17205,17208,17210],{"class":3419,"line":17204},347,[3417,17206,17207],{"class":4041},"      let",[3417,17209,8577],{"class":6164},[3417,17211,6130],{"class":7550},[3417,17213,17215,17218,17220,17222,17224,17227,17229,17232,17234,17236,17238,17240],{"class":3419,"line":17214},348,[3417,17216,17217],{"class":4033},"      try",[3417,17219,6168],{"class":7550},[3417,17221,8381],{"class":6164},[3417,17223,16530],{"class":4037},[3417,17225,17226],{"class":12627}," JSON",[3417,17228,4253],{"class":7550},[3417,17230,17231],{"class":6043},"parse",[3417,17233,6537],{"class":7550},[3417,17235,12680],{"class":6164},[3417,17237,4253],{"class":7550},[3417,17239,7168],{"class":6164},[3417,17241,7796],{"class":7550},[3417,17243,17245,17248,17250,17252],{"class":3419,"line":17244},349,[3417,17246,17247],{"class":4033},"      catch",[3417,17249,6168],{"class":7550},[3417,17251,8426],{"class":4033},[3417,17253,6207],{"class":7550},[3417,17255,17257,17260,17262,17264],{"class":3419,"line":17256},350,[3417,17258,17259],{"class":6043},"      handleMessage",[3417,17261,6537],{"class":7550},[3417,17263,8381],{"class":6164},[3417,17265,6577],{"class":7550},[3417,17267,17269],{"class":3419,"line":17268},351,[3417,17270,6520],{"class":7550},[3417,17272,17274],{"class":3419,"line":17273},352,[3417,17275,3442],{"emptyLinePlaceholder":3441},[3417,17277,17279],{"class":3419,"line":17278},353,[3417,17280,17281],{"class":6183},"    \u002F\u002F Помилка (деталі не доступні з міркувань безпеки)\n",[3417,17283,17285,17287,17289,17291,17293,17295,17297],{"class":3419,"line":17284},354,[3417,17286,17005],{"class":6164},[3417,17288,4253],{"class":7550},[3417,17290,12719],{"class":6043},[3417,17292,16530],{"class":4037},[3417,17294,17062],{"class":7550},[3417,17296,17065],{"class":4041},[3417,17298,13013],{"class":7550},[3417,17300,17302,17304,17306,17309,17312,17315],{"class":3419,"line":17301},355,[3417,17303,17151],{"class":6043},[3417,17305,6537],{"class":7550},[3417,17307,17308],{"class":4060},"'⚠️ Помилка з",[3417,17310,17311],{"class":10603},"\\'",[3417,17313,17314],{"class":4060},"єднання'",[3417,17316,6577],{"class":7550},[3417,17318,17320],{"class":3419,"line":17319},356,[3417,17321,6520],{"class":7550},[3417,17323,17325],{"class":3419,"line":17324},357,[3417,17326,3442],{"emptyLinePlaceholder":3441},[3417,17328,17330],{"class":3419,"line":17329},358,[3417,17331,17332],{"class":6183},"    \u002F\u002F Закриття з'єднання\n",[3417,17334,17336,17338,17340,17342,17344,17346,17348,17350,17352],{"class":3419,"line":17335},359,[3417,17337,17005],{"class":6164},[3417,17339,4253],{"class":7550},[3417,17341,12723],{"class":6043},[3417,17343,16530],{"class":4037},[3417,17345,7254],{"class":7550},[3417,17347,12680],{"class":6164},[3417,17349,8271],{"class":7550},[3417,17351,17065],{"class":4041},[3417,17353,13013],{"class":7550},[3417,17355,17357,17359,17361,17364],{"class":3419,"line":17356},360,[3417,17358,17073],{"class":6043},[3417,17360,6537],{"class":7550},[3417,17362,17363],{"class":4060},"'closed'",[3417,17365,6577],{"class":7550},[3417,17367,17369,17371,17373,17375,17377,17379],{"class":3419,"line":17368},361,[3417,17370,17086],{"class":6164},[3417,17372,4253],{"class":7550},[3417,17374,17091],{"class":6164},[3417,17376,16530],{"class":4037},[3417,17378,7111],{"class":4041},[3417,17380,6130],{"class":7550},[3417,17382,17384,17387,17390,17392,17395,17397,17399,17402,17405],{"class":3419,"line":17383},362,[3417,17385,17386],{"class":4041},"      const",[3417,17388,17389],{"class":12627}," reason",[3417,17391,16530],{"class":4037},[3417,17393,17394],{"class":6164}," event",[3417,17396,4253],{"class":7550},[3417,17398,4985],{"class":6164},[3417,17400,17401],{"class":4037}," ||",[3417,17403,17404],{"class":4060}," 'невідома причина'",[3417,17406,6130],{"class":7550},[3417,17408,17410],{"class":3419,"line":17409},363,[3417,17411,17412],{"class":6183},"      \u002F\u002F Код 4000 — ім'я зайняте (визначено у сервері)\n",[3417,17414,17416,17419,17421,17423,17425,17427,17429,17432],{"class":3419,"line":17415},364,[3417,17417,17418],{"class":4033},"      if",[3417,17420,7254],{"class":7550},[3417,17422,12680],{"class":6164},[3417,17424,4253],{"class":7550},[3417,17426,3389],{"class":6164},[3417,17428,16921],{"class":4037},[3417,17430,17431],{"class":4048}," 4000",[3417,17433,16881],{"class":7550},[3417,17435,17437,17440,17442,17444,17446,17449,17451,17453,17455,17458],{"class":3419,"line":17436},365,[3417,17438,17439],{"class":6164},"        loginError",[3417,17441,4253],{"class":7550},[3417,17443,17129],{"class":6164},[3417,17445,16530],{"class":4037},[3417,17447,17448],{"class":4060}," `❌ Нікнейм \"",[3417,17450,16954],{"class":4041},[3417,17452,6569],{"class":6164},[3417,17454,7555],{"class":4041},[3417,17456,17457],{"class":4060},"\" вже зайнятий`",[3417,17459,6130],{"class":7550},[3417,17461,17463,17466,17468,17470,17472,17474,17476,17479],{"class":3419,"line":17462},366,[3417,17464,17465],{"class":6164},"        overlay",[3417,17467,4253],{"class":7550},[3417,17469,12947],{"class":6164},[3417,17471,4253],{"class":7550},[3417,17473,14713],{"class":6164},[3417,17475,16530],{"class":4037},[3417,17477,17478],{"class":4060}," 'flex'",[3417,17480,6130],{"class":7550},[3417,17482,17484,17487,17490],{"class":3419,"line":17483},367,[3417,17485,17486],{"class":7550},"      } ",[3417,17488,17489],{"class":4033},"else",[3417,17491,13013],{"class":7550},[3417,17493,17495,17498,17500,17503,17505,17507,17509,17511,17513,17515,17517,17519,17521,17524],{"class":3419,"line":17494},368,[3417,17496,17497],{"class":6043},"        addSystemMessage",[3417,17499,6537],{"class":7550},[3417,17501,17502],{"class":4060},"`🔴 Відключено (код ",[3417,17504,16954],{"class":4041},[3417,17506,12680],{"class":6164},[3417,17508,4253],{"class":7550},[3417,17510,3389],{"class":6164},[3417,17512,7555],{"class":4041},[3417,17514,5407],{"class":4060},[3417,17516,16954],{"class":4041},[3417,17518,4985],{"class":6164},[3417,17520,7555],{"class":4041},[3417,17522,17523],{"class":4060},")`",[3417,17525,6577],{"class":7550},[3417,17527,17529],{"class":3419,"line":17528},369,[3417,17530,17531],{"class":7550},"      }\n",[3417,17533,17535],{"class":3419,"line":17534},370,[3417,17536,6520],{"class":7550},[3417,17538,17540],{"class":3419,"line":17539},371,[3417,17541,17542],{"class":7550},"  }\n",[3417,17544,17546],{"class":3419,"line":17545},372,[3417,17547,3442],{"emptyLinePlaceholder":3441},[3417,17549,17551],{"class":3419,"line":17550},373,[3417,17552,17553],{"class":6183},"  \u002F\u002F ── Обробка вхідних повідомлень ──────────────────────\n",[3417,17555,17557,17559,17562,17564,17566],{"class":3419,"line":17556},374,[3417,17558,16871],{"class":4041},[3417,17560,17561],{"class":6043}," handleMessage",[3417,17563,6537],{"class":7550},[3417,17565,8381],{"class":6164},[3417,17567,16881],{"class":7550},[3417,17569,17571,17574,17576,17578,17580,17582],{"class":3419,"line":17570},375,[3417,17572,17573],{"class":4033},"    switch",[3417,17575,7254],{"class":7550},[3417,17577,8381],{"class":6164},[3417,17579,4253],{"class":7550},[3417,17581,11029],{"class":6164},[3417,17583,16881],{"class":7550},[3417,17585,17587,17590,17593],{"class":3419,"line":17586},376,[3417,17588,17589],{"class":4033},"      case",[3417,17591,17592],{"class":4060}," 'chat'",[3417,17594,8488],{"class":7550},[3417,17596,17598,17601,17603,17605,17607,17609,17611,17613,17615,17617,17619,17621,17623,17626,17628,17631],{"class":3419,"line":17597},377,[3417,17599,17600],{"class":6043},"        addChatMessage",[3417,17602,6537],{"class":7550},[3417,17604,8381],{"class":6164},[3417,17606,4253],{"class":7550},[3417,17608,8531],{"class":6164},[3417,17610,4246],{"class":7550},[3417,17612,8381],{"class":6164},[3417,17614,4253],{"class":7550},[3417,17616,3387],{"class":6164},[3417,17618,4246],{"class":7550},[3417,17620,8381],{"class":6164},[3417,17622,4253],{"class":7550},[3417,17624,17625],{"class":6164},"timestamp",[3417,17627,4246],{"class":7550},[3417,17629,17630],{"class":4041},"false",[3417,17632,6577],{"class":7550},[3417,17634,17636,17639],{"class":3419,"line":17635},378,[3417,17637,17638],{"class":4033},"        break",[3417,17640,6130],{"class":7550},[3417,17642,17644],{"class":3419,"line":17643},379,[3417,17645,3442],{"emptyLinePlaceholder":3441},[3417,17647,17649,17651,17654],{"class":3419,"line":17648},380,[3417,17650,17589],{"class":4033},[3417,17652,17653],{"class":4060}," 'private'",[3417,17655,8488],{"class":7550},[3417,17657,17659],{"class":3419,"line":17658},381,[3417,17660,17661],{"class":6183},"        \u002F\u002F msg.from: хто надіслав, msg.to: кому\n",[3417,17663,17665,17668,17671,17673,17675,17677,17679,17681,17683],{"class":3419,"line":17664},382,[3417,17666,17667],{"class":4041},"        const",[3417,17669,17670],{"class":12627}," isOutgoing",[3417,17672,16530],{"class":4037},[3417,17674,8577],{"class":6164},[3417,17676,4253],{"class":7550},[3417,17678,8531],{"class":6164},[3417,17680,16921],{"class":4037},[3417,17682,16542],{"class":6164},[3417,17684,6130],{"class":7550},[3417,17686,17688,17691,17693,17695,17697,17699,17701,17703,17705,17707,17709,17711,17713,17715,17717,17719,17721,17723,17725,17728],{"class":3419,"line":17687},383,[3417,17689,17690],{"class":6043},"        addPrivateMessage",[3417,17692,6537],{"class":7550},[3417,17694,8381],{"class":6164},[3417,17696,4253],{"class":7550},[3417,17698,8531],{"class":6164},[3417,17700,4246],{"class":7550},[3417,17702,8381],{"class":6164},[3417,17704,4253],{"class":7550},[3417,17706,11907],{"class":6164},[3417,17708,4246],{"class":7550},[3417,17710,8381],{"class":6164},[3417,17712,4253],{"class":7550},[3417,17714,3387],{"class":6164},[3417,17716,4246],{"class":7550},[3417,17718,8381],{"class":6164},[3417,17720,4253],{"class":7550},[3417,17722,17625],{"class":6164},[3417,17724,4246],{"class":7550},[3417,17726,17727],{"class":6164},"isOutgoing",[3417,17729,6577],{"class":7550},[3417,17731,17733,17735],{"class":3419,"line":17732},384,[3417,17734,17638],{"class":4033},[3417,17736,6130],{"class":7550},[3417,17738,17740],{"class":3419,"line":17739},385,[3417,17741,3442],{"emptyLinePlaceholder":3441},[3417,17743,17745,17747,17750],{"class":3419,"line":17744},386,[3417,17746,17589],{"class":4033},[3417,17748,17749],{"class":4060}," 'system'",[3417,17751,8488],{"class":7550},[3417,17753,17755,17757,17759,17761,17763,17765],{"class":3419,"line":17754},387,[3417,17756,17497],{"class":6043},[3417,17758,6537],{"class":7550},[3417,17760,8381],{"class":6164},[3417,17762,4253],{"class":7550},[3417,17764,3387],{"class":6164},[3417,17766,6577],{"class":7550},[3417,17768,17770,17772],{"class":3419,"line":17769},388,[3417,17771,17638],{"class":4033},[3417,17773,6130],{"class":7550},[3417,17775,17777],{"class":3419,"line":17776},389,[3417,17778,3442],{"emptyLinePlaceholder":3441},[3417,17780,17782,17784,17787],{"class":3419,"line":17781},390,[3417,17783,17589],{"class":4033},[3417,17785,17786],{"class":4060}," 'users'",[3417,17788,8488],{"class":7550},[3417,17790,17792,17795,17797,17799,17801,17803],{"class":3419,"line":17791},391,[3417,17793,17794],{"class":6043},"        renderUsersList",[3417,17796,6537],{"class":7550},[3417,17798,8381],{"class":6164},[3417,17800,4253],{"class":7550},[3417,17802,6012],{"class":6164},[3417,17804,6577],{"class":7550},[3417,17806,17808,17810],{"class":3419,"line":17807},392,[3417,17809,17638],{"class":4033},[3417,17811,6130],{"class":7550},[3417,17813,17815],{"class":3419,"line":17814},393,[3417,17816,5765],{"class":7550},[3417,17818,17820],{"class":3419,"line":17819},394,[3417,17821,17542],{"class":7550},[3417,17823,17825],{"class":3419,"line":17824},395,[3417,17826,3442],{"emptyLinePlaceholder":3441},[3417,17828,17830],{"class":3419,"line":17829},396,[3417,17831,17832],{"class":6183},"  \u002F\u002F ── Відображення повідомлень ─────────────────────────\n",[3417,17834,17836,17838,17841,17843,17845,17847,17849,17851,17853,17855,17858],{"class":3419,"line":17835},397,[3417,17837,16871],{"class":4041},[3417,17839,17840],{"class":6043}," addChatMessage",[3417,17842,6537],{"class":7550},[3417,17844,8531],{"class":6164},[3417,17846,4246],{"class":7550},[3417,17848,3387],{"class":6164},[3417,17850,4246],{"class":7550},[3417,17852,17625],{"class":6164},[3417,17854,4246],{"class":7550},[3417,17856,17857],{"class":6164},"isMine",[3417,17859,16881],{"class":7550},[3417,17861,17863,17865,17868,17870,17872,17874,17876],{"class":3419,"line":17862},398,[3417,17864,16905],{"class":4041},[3417,17866,17867],{"class":12627}," isMineMsg",[3417,17869,16530],{"class":4037},[3417,17871,8356],{"class":6164},[3417,17873,16921],{"class":4037},[3417,17875,16542],{"class":6164},[3417,17877,6130],{"class":7550},[3417,17879,17881,17883,17885,17887,17889,17891,17894,17896,17899],{"class":3419,"line":17880},399,[3417,17882,16905],{"class":4041},[3417,17884,12157],{"class":12627},[3417,17886,16530],{"class":4037},[3417,17888,16589],{"class":6164},[3417,17890,4253],{"class":7550},[3417,17892,17893],{"class":6043},"createElement",[3417,17895,6537],{"class":7550},[3417,17897,17898],{"class":4060},"'div'",[3417,17900,6577],{"class":7550},[3417,17902,17904,17907,17909,17912,17914,17917,17919,17922,17924,17927,17929,17932,17934,17936],{"class":3419,"line":17903},400,[3417,17905,17906],{"class":6164},"    el",[3417,17908,4253],{"class":7550},[3417,17910,17911],{"class":6164},"className",[3417,17913,16530],{"class":4037},[3417,17915,17916],{"class":4060}," `msg ",[3417,17918,16954],{"class":4041},[3417,17920,17921],{"class":6164},"isMineMsg",[3417,17923,16927],{"class":4037},[3417,17925,17926],{"class":4060}," 'msg-mine'",[3417,17928,16933],{"class":4037},[3417,17930,17931],{"class":4060}," 'msg-other'",[3417,17933,7555],{"class":4041},[3417,17935,16992],{"class":4060},[3417,17937,6130],{"class":7550},[3417,17939,17941,17943,17945,17948,17950],{"class":3419,"line":17940},401,[3417,17942,17906],{"class":6164},[3417,17944,4253],{"class":7550},[3417,17946,17947],{"class":6164},"innerHTML",[3417,17949,16530],{"class":4037},[3417,17951,17952],{"class":4060}," `\n",[3417,17954,17956,17959,17962,17964,17966,17969,17971,17974,17976,17978,17980,17982,17985,17987,17989],{"class":3419,"line":17955},402,[3417,17957,17958],{"class":4041},"      ${",[3417,17960,17961],{"class":4037},"!",[3417,17963,17921],{"class":6164},[3417,17965,16927],{"class":4037},[3417,17967,17968],{"class":4060}," `\u003Cdiv class=\"msg-header\">",[3417,17970,16954],{"class":4041},[3417,17972,17973],{"class":6043},"escapeHtml",[3417,17975,6537],{"class":7550},[3417,17977,8531],{"class":6164},[3417,17979,3996],{"class":7550},[3417,17981,7555],{"class":4041},[3417,17983,17984],{"class":4060},"\u003C\u002Fdiv>`",[3417,17986,16933],{"class":4037},[3417,17988,16547],{"class":4060},[3417,17990,3869],{"class":4041},[3417,17992,17994,17997,17999,18001,18003,18005,18007,18010,18012,18016,18019,18021,18024,18026,18028],{"class":3419,"line":17993},403,[3417,17995,17996],{"class":4060},"      \u003Cdiv>",[3417,17998,16954],{"class":4041},[3417,18000,17973],{"class":6043},[3417,18002,6537],{"class":7550},[3417,18004,3387],{"class":6164},[3417,18006,10992],{"class":7550},[3417,18008,18009],{"class":6043},"replace",[3417,18011,6537],{"class":7550},[3417,18013,18015],{"class":18014},"shsrj","\u002F\\n\u002F",[3417,18017,18018],{"class":4041},"g",[3417,18020,4246],{"class":7550},[3417,18022,18023],{"class":4060},"'\u003Cbr>'",[3417,18025,3996],{"class":7550},[3417,18027,7555],{"class":4041},[3417,18029,18030],{"class":4060},"\u003C\u002Fdiv>\n",[3417,18032,18034,18037,18039,18042,18044,18046,18048,18050],{"class":3419,"line":18033},404,[3417,18035,18036],{"class":4060},"      \u003Cdiv class=\"msg-time\">",[3417,18038,16954],{"class":4041},[3417,18040,18041],{"class":6043},"formatTime",[3417,18043,6537],{"class":7550},[3417,18045,17625],{"class":6164},[3417,18047,3996],{"class":7550},[3417,18049,7555],{"class":4041},[3417,18051,18030],{"class":4060},[3417,18053,18055,18058],{"class":3419,"line":18054},405,[3417,18056,18057],{"class":4060},"    `",[3417,18059,6130],{"class":7550},[3417,18061,18063,18066,18068,18070],{"class":3419,"line":18062},406,[3417,18064,18065],{"class":6043},"    appendMessage",[3417,18067,6537],{"class":7550},[3417,18069,12174],{"class":6164},[3417,18071,6577],{"class":7550},[3417,18073,18075],{"class":3419,"line":18074},407,[3417,18076,17542],{"class":7550},[3417,18078,18080],{"class":3419,"line":18079},408,[3417,18081,3442],{"emptyLinePlaceholder":3441},[3417,18083,18085,18087,18090,18092,18094,18096,18098,18100,18102,18104,18106,18108,18110],{"class":3419,"line":18084},409,[3417,18086,16871],{"class":4041},[3417,18088,18089],{"class":6043}," addPrivateMessage",[3417,18091,6537],{"class":7550},[3417,18093,8531],{"class":6164},[3417,18095,4246],{"class":7550},[3417,18097,11907],{"class":6164},[3417,18099,4246],{"class":7550},[3417,18101,3387],{"class":6164},[3417,18103,4246],{"class":7550},[3417,18105,17625],{"class":6164},[3417,18107,4246],{"class":7550},[3417,18109,17727],{"class":6164},[3417,18111,16881],{"class":7550},[3417,18113,18115,18117,18119,18121,18123,18125,18127,18129,18131],{"class":3419,"line":18114},410,[3417,18116,16905],{"class":4041},[3417,18118,12157],{"class":12627},[3417,18120,16530],{"class":4037},[3417,18122,16589],{"class":6164},[3417,18124,4253],{"class":7550},[3417,18126,17893],{"class":6043},[3417,18128,6537],{"class":7550},[3417,18130,17898],{"class":4060},[3417,18132,6577],{"class":7550},[3417,18134,18136,18138,18140,18142,18144,18147,18149,18151,18153,18155,18157,18159,18161,18163],{"class":3419,"line":18135},411,[3417,18137,17906],{"class":6164},[3417,18139,4253],{"class":7550},[3417,18141,17911],{"class":6164},[3417,18143,16530],{"class":4037},[3417,18145,18146],{"class":4060}," `msg msg-private ",[3417,18148,16954],{"class":4041},[3417,18150,17727],{"class":6164},[3417,18152,16927],{"class":4037},[3417,18154,17926],{"class":4060},[3417,18156,16933],{"class":4037},[3417,18158,16547],{"class":4060},[3417,18160,7555],{"class":4041},[3417,18162,16992],{"class":4060},[3417,18164,6130],{"class":7550},[3417,18166,18168,18170,18173,18175],{"class":3419,"line":18167},412,[3417,18169,16905],{"class":4041},[3417,18171,18172],{"class":12627}," label",[3417,18174,16530],{"class":4037},[3417,18176,18177],{"class":6164}," isOutgoing\n",[3417,18179,18181,18184,18187,18189,18191,18193,18195,18197,18199],{"class":3419,"line":18180},413,[3417,18182,18183],{"class":4037},"      ?",[3417,18185,18186],{"class":4060}," `🔒 → ",[3417,18188,16954],{"class":4041},[3417,18190,17973],{"class":6043},[3417,18192,6537],{"class":7550},[3417,18194,11907],{"class":6164},[3417,18196,3996],{"class":7550},[3417,18198,7555],{"class":4041},[3417,18200,18201],{"class":4060},"`\n",[3417,18203,18205,18208,18211,18213,18215,18217,18219,18221,18223,18225],{"class":3419,"line":18204},414,[3417,18206,18207],{"class":4037},"      :",[3417,18209,18210],{"class":4060}," `🔒 ",[3417,18212,16954],{"class":4041},[3417,18214,17973],{"class":6043},[3417,18216,6537],{"class":7550},[3417,18218,8531],{"class":6164},[3417,18220,3996],{"class":7550},[3417,18222,7555],{"class":4041},[3417,18224,16992],{"class":4060},[3417,18226,6130],{"class":7550},[3417,18228,18230,18232,18234,18236,18238],{"class":3419,"line":18229},415,[3417,18231,17906],{"class":6164},[3417,18233,4253],{"class":7550},[3417,18235,17947],{"class":6164},[3417,18237,16530],{"class":4037},[3417,18239,17952],{"class":4060},[3417,18241,18243,18246,18248,18251,18253],{"class":3419,"line":18242},416,[3417,18244,18245],{"class":4060},"      \u003Cdiv class=\"msg-header\">",[3417,18247,16954],{"class":4041},[3417,18249,18250],{"class":6164},"label",[3417,18252,7555],{"class":4041},[3417,18254,18030],{"class":4060},[3417,18256,18258,18260,18262,18264,18266,18268,18270,18272,18274,18276,18278,18280,18282,18284,18286],{"class":3419,"line":18257},417,[3417,18259,17996],{"class":4060},[3417,18261,16954],{"class":4041},[3417,18263,17973],{"class":6043},[3417,18265,6537],{"class":7550},[3417,18267,3387],{"class":6164},[3417,18269,10992],{"class":7550},[3417,18271,18009],{"class":6043},[3417,18273,6537],{"class":7550},[3417,18275,18015],{"class":18014},[3417,18277,18018],{"class":4041},[3417,18279,4246],{"class":7550},[3417,18281,18023],{"class":4060},[3417,18283,3996],{"class":7550},[3417,18285,7555],{"class":4041},[3417,18287,18030],{"class":4060},[3417,18289,18291,18293,18295,18297,18299,18301,18303,18305],{"class":3419,"line":18290},418,[3417,18292,18036],{"class":4060},[3417,18294,16954],{"class":4041},[3417,18296,18041],{"class":6043},[3417,18298,6537],{"class":7550},[3417,18300,17625],{"class":6164},[3417,18302,3996],{"class":7550},[3417,18304,7555],{"class":4041},[3417,18306,18030],{"class":4060},[3417,18308,18310,18312],{"class":3419,"line":18309},419,[3417,18311,18057],{"class":4060},[3417,18313,6130],{"class":7550},[3417,18315,18317,18319,18321,18323],{"class":3419,"line":18316},420,[3417,18318,18065],{"class":6043},[3417,18320,6537],{"class":7550},[3417,18322,12174],{"class":6164},[3417,18324,6577],{"class":7550},[3417,18326,18328],{"class":3419,"line":18327},421,[3417,18329,17542],{"class":7550},[3417,18331,18333],{"class":3419,"line":18332},422,[3417,18334,3442],{"emptyLinePlaceholder":3441},[3417,18336,18338,18340,18343,18345,18347],{"class":3419,"line":18337},423,[3417,18339,16871],{"class":4041},[3417,18341,18342],{"class":6043}," addSystemMessage",[3417,18344,6537],{"class":7550},[3417,18346,3387],{"class":6164},[3417,18348,16881],{"class":7550},[3417,18350,18352,18354,18356,18358,18360,18362,18364,18366,18368],{"class":3419,"line":18351},424,[3417,18353,16905],{"class":4041},[3417,18355,12157],{"class":12627},[3417,18357,16530],{"class":4037},[3417,18359,16589],{"class":6164},[3417,18361,4253],{"class":7550},[3417,18363,17893],{"class":6043},[3417,18365,6537],{"class":7550},[3417,18367,17898],{"class":4060},[3417,18369,6577],{"class":7550},[3417,18371,18373,18375,18377,18379,18381,18384],{"class":3419,"line":18372},425,[3417,18374,17906],{"class":6164},[3417,18376,4253],{"class":7550},[3417,18378,17911],{"class":6164},[3417,18380,16530],{"class":4037},[3417,18382,18383],{"class":4060}," 'msg msg-system'",[3417,18385,6130],{"class":7550},[3417,18387,18389,18391,18393,18395,18397,18400],{"class":3419,"line":18388},426,[3417,18390,17906],{"class":6164},[3417,18392,4253],{"class":7550},[3417,18394,17129],{"class":6164},[3417,18396,16530],{"class":4037},[3417,18398,18399],{"class":6164}," text",[3417,18401,6130],{"class":7550},[3417,18403,18405,18407,18409,18411],{"class":3419,"line":18404},427,[3417,18406,18065],{"class":6043},[3417,18408,6537],{"class":7550},[3417,18410,12174],{"class":6164},[3417,18412,6577],{"class":7550},[3417,18414,18416],{"class":3419,"line":18415},428,[3417,18417,17542],{"class":7550},[3417,18419,18421],{"class":3419,"line":18420},429,[3417,18422,3442],{"emptyLinePlaceholder":3441},[3417,18424,18426,18428,18431,18433,18435],{"class":3419,"line":18425},430,[3417,18427,16871],{"class":4041},[3417,18429,18430],{"class":6043}," appendMessage",[3417,18432,6537],{"class":7550},[3417,18434,12174],{"class":6164},[3417,18436,16881],{"class":7550},[3417,18438,18440,18443,18445,18448,18450,18452],{"class":3419,"line":18439},431,[3417,18441,18442],{"class":6164},"    messagesList",[3417,18444,4253],{"class":7550},[3417,18446,18447],{"class":6043},"appendChild",[3417,18449,6537],{"class":7550},[3417,18451,12174],{"class":6164},[3417,18453,6577],{"class":7550},[3417,18455,18457],{"class":3419,"line":18456},432,[3417,18458,18459],{"class":6183},"    \u002F\u002F Автоскрол донизу\n",[3417,18461,18463,18465,18467,18470,18472,18474,18476,18479],{"class":3419,"line":18462},433,[3417,18464,18442],{"class":6164},[3417,18466,4253],{"class":7550},[3417,18468,18469],{"class":6164},"scrollTop",[3417,18471,16530],{"class":4037},[3417,18473,16737],{"class":6164},[3417,18475,4253],{"class":7550},[3417,18477,18478],{"class":6164},"scrollHeight",[3417,18480,6130],{"class":7550},[3417,18482,18484],{"class":3419,"line":18483},434,[3417,18485,17542],{"class":7550},[3417,18487,18489],{"class":3419,"line":18488},435,[3417,18490,3442],{"emptyLinePlaceholder":3441},[3417,18492,18494],{"class":3419,"line":18493},436,[3417,18495,18496],{"class":6183},"  \u002F\u002F ── Список користувачів ──────────────────────────────\n",[3417,18498,18500,18502,18505,18507,18509],{"class":3419,"line":18499},437,[3417,18501,16871],{"class":4041},[3417,18503,18504],{"class":6043}," renderUsersList",[3417,18506,6537],{"class":7550},[3417,18508,6012],{"class":6164},[3417,18510,16881],{"class":7550},[3417,18512,18514,18517,18519,18521,18523,18525],{"class":3419,"line":18513},438,[3417,18515,18516],{"class":6164},"    usersList",[3417,18518,4253],{"class":7550},[3417,18520,17947],{"class":6164},[3417,18522,16530],{"class":4037},[3417,18524,16547],{"class":4060},[3417,18526,6130],{"class":7550},[3417,18528,18530,18533,18535,18538,18540,18543,18545],{"class":3419,"line":18529},439,[3417,18531,18532],{"class":6164},"    users",[3417,18534,4253],{"class":7550},[3417,18536,18537],{"class":6043},"forEach",[3417,18539,6537],{"class":7550},[3417,18541,18542],{"class":6164},"user",[3417,18544,11313],{"class":4041},[3417,18546,13013],{"class":7550},[3417,18548,18550,18552,18554,18556,18558,18560,18562,18564,18567],{"class":3419,"line":18549},440,[3417,18551,17386],{"class":4041},[3417,18553,13633],{"class":12627},[3417,18555,16530],{"class":4037},[3417,18557,16589],{"class":6164},[3417,18559,4253],{"class":7550},[3417,18561,17893],{"class":6043},[3417,18563,6537],{"class":7550},[3417,18565,18566],{"class":4060},"'li'",[3417,18568,6577],{"class":7550},[3417,18570,18572,18575,18577,18579,18581,18584],{"class":3419,"line":18571},441,[3417,18573,18574],{"class":6164},"      li",[3417,18576,4253],{"class":7550},[3417,18578,17129],{"class":6164},[3417,18580,16530],{"class":4037},[3417,18582,18583],{"class":6164}," user",[3417,18585,6130],{"class":7550},[3417,18587,18589,18591,18593,18595,18597,18599,18601,18603,18605,18608,18610,18613,18615,18618],{"class":3419,"line":18588},442,[3417,18590,17418],{"class":4033},[3417,18592,7254],{"class":7550},[3417,18594,18542],{"class":6164},[3417,18596,16921],{"class":4037},[3417,18598,16542],{"class":6164},[3417,18600,8271],{"class":7550},[3417,18602,3984],{"class":6164},[3417,18604,4253],{"class":7550},[3417,18606,18607],{"class":6164},"classList",[3417,18609,4253],{"class":7550},[3417,18611,18612],{"class":6043},"add",[3417,18614,6537],{"class":7550},[3417,18616,18617],{"class":4060},"'me'",[3417,18619,6577],{"class":7550},[3417,18621,18623,18625,18627,18629,18631,18633,18635,18637,18639,18642,18644,18647,18649,18651,18653,18655],{"class":3419,"line":18622},443,[3417,18624,18574],{"class":6164},[3417,18626,4253],{"class":7550},[3417,18628,12927],{"class":6164},[3417,18630,16530],{"class":4037},[3417,18632,18583],{"class":6164},[3417,18634,16921],{"class":4037},[3417,18636,16542],{"class":6164},[3417,18638,16927],{"class":4037},[3417,18640,18641],{"class":4060}," 'Це ви'",[3417,18643,16933],{"class":4037},[3417,18645,18646],{"class":4060}," `Написати приватно ",[3417,18648,16954],{"class":4041},[3417,18650,18542],{"class":6164},[3417,18652,7555],{"class":4041},[3417,18654,16992],{"class":4060},[3417,18656,6130],{"class":7550},[3417,18658,18660,18662,18664,18666,18669,18671],{"class":3419,"line":18659},444,[3417,18661,17418],{"class":4033},[3417,18663,7254],{"class":7550},[3417,18665,18542],{"class":6164},[3417,18667,18668],{"class":4037}," !==",[3417,18670,16542],{"class":6164},[3417,18672,16881],{"class":7550},[3417,18674,18676],{"class":3419,"line":18675},445,[3417,18677,18678],{"class":6183},"        \u002F\u002F Клік на користувача — переключаємо на приватний режим\n",[3417,18680,18682,18685,18687,18690,18692,18695,18698,18700,18703,18705,18707],{"class":3419,"line":18681},446,[3417,18683,18684],{"class":6164},"        li",[3417,18686,4253],{"class":7550},[3417,18688,18689],{"class":6043},"addEventListener",[3417,18691,6537],{"class":7550},[3417,18693,18694],{"class":4060},"'click'",[3417,18696,18697],{"class":7550},", () ",[3417,18699,17065],{"class":4041},[3417,18701,18702],{"class":6043}," setPrivateTarget",[3417,18704,6537],{"class":7550},[3417,18706,18542],{"class":6164},[3417,18708,6758],{"class":7550},[3417,18710,18712],{"class":3419,"line":18711},447,[3417,18713,17531],{"class":7550},[3417,18715,18717,18720,18722,18724,18726,18728],{"class":3419,"line":18716},448,[3417,18718,18719],{"class":6164},"      usersList",[3417,18721,4253],{"class":7550},[3417,18723,18447],{"class":6043},[3417,18725,6537],{"class":7550},[3417,18727,3984],{"class":6164},[3417,18729,6577],{"class":7550},[3417,18731,18733],{"class":3419,"line":18732},449,[3417,18734,18735],{"class":7550},"    });\n",[3417,18737,18739],{"class":3419,"line":18738},450,[3417,18740,17542],{"class":7550},[3417,18742,18744],{"class":3419,"line":18743},451,[3417,18745,3442],{"emptyLinePlaceholder":3441},[3417,18747,18749],{"class":3419,"line":18748},452,[3417,18750,18751],{"class":6183},"  \u002F\u002F ── Приватний режим ──────────────────────────────────\n",[3417,18753,18755,18757,18759,18761,18763],{"class":3419,"line":18754},453,[3417,18756,16871],{"class":4041},[3417,18758,18702],{"class":6043},[3417,18760,6537],{"class":7550},[3417,18762,6569],{"class":6164},[3417,18764,16881],{"class":7550},[3417,18766,18768,18771,18773,18775],{"class":3419,"line":18767},454,[3417,18769,18770],{"class":6164},"    privateTarget",[3417,18772,16530],{"class":4037},[3417,18774,6542],{"class":6164},[3417,18776,6130],{"class":7550},[3417,18778,18780,18783,18785,18787,18789,18791],{"class":3419,"line":18779},455,[3417,18781,18782],{"class":6164},"    privateLabel",[3417,18784,4253],{"class":7550},[3417,18786,17129],{"class":6164},[3417,18788,16530],{"class":4037},[3417,18790,6542],{"class":6164},[3417,18792,6130],{"class":7550},[3417,18794,18796,18799,18801,18803,18805,18807,18809,18812],{"class":3419,"line":18795},456,[3417,18797,18798],{"class":6164},"    privateDiv",[3417,18800,4253],{"class":7550},[3417,18802,18607],{"class":6164},[3417,18804,4253],{"class":7550},[3417,18806,18612],{"class":6043},[3417,18808,6537],{"class":7550},[3417,18810,18811],{"class":4060},"'visible'",[3417,18813,6577],{"class":7550},[3417,18815,18817,18820,18822,18825,18827,18830,18832,18834,18836,18839],{"class":3419,"line":18816},457,[3417,18818,18819],{"class":6164},"    msgInput",[3417,18821,4253],{"class":7550},[3417,18823,18824],{"class":6164},"placeholder",[3417,18826,16530],{"class":4037},[3417,18828,18829],{"class":4060}," `Приватне для ",[3417,18831,16954],{"class":4041},[3417,18833,6569],{"class":6164},[3417,18835,7555],{"class":4041},[3417,18837,18838],{"class":4060},"...`",[3417,18840,6130],{"class":7550},[3417,18842,18844,18846,18848,18851],{"class":3419,"line":18843},458,[3417,18845,18819],{"class":6164},[3417,18847,4253],{"class":7550},[3417,18849,18850],{"class":6043},"focus",[3417,18852,6473],{"class":7550},[3417,18854,18856],{"class":3419,"line":18855},459,[3417,18857,17542],{"class":7550},[3417,18859,18861],{"class":3419,"line":18860},460,[3417,18862,3442],{"emptyLinePlaceholder":3441},[3417,18864,18866,18869,18871,18874,18876,18878,18880,18882,18884,18886],{"class":3419,"line":18865},461,[3417,18867,18868],{"class":6043},"  $",[3417,18870,6537],{"class":7550},[3417,18872,18873],{"class":4060},"'cancel-private'",[3417,18875,10992],{"class":7550},[3417,18877,18689],{"class":6043},[3417,18879,6537],{"class":7550},[3417,18881,18694],{"class":4060},[3417,18883,18697],{"class":7550},[3417,18885,17065],{"class":4041},[3417,18887,13013],{"class":7550},[3417,18889,18891,18893,18895,18897],{"class":3419,"line":18890},462,[3417,18892,18770],{"class":6164},[3417,18894,16530],{"class":4037},[3417,18896,8443],{"class":4041},[3417,18898,6130],{"class":7550},[3417,18900,18902,18904,18906,18908,18910,18913,18915,18917],{"class":3419,"line":18901},463,[3417,18903,18798],{"class":6164},[3417,18905,4253],{"class":7550},[3417,18907,18607],{"class":6164},[3417,18909,4253],{"class":7550},[3417,18911,18912],{"class":6043},"remove",[3417,18914,6537],{"class":7550},[3417,18916,18811],{"class":4060},[3417,18918,6577],{"class":7550},[3417,18920,18922,18924,18926,18928,18930,18933],{"class":3419,"line":18921},464,[3417,18923,18819],{"class":6164},[3417,18925,4253],{"class":7550},[3417,18927,18824],{"class":6164},[3417,18929,16530],{"class":4037},[3417,18931,18932],{"class":4060}," 'Введіть повідомлення...'",[3417,18934,6130],{"class":7550},[3417,18936,18938],{"class":3419,"line":18937},465,[3417,18939,18940],{"class":7550},"  });\n",[3417,18942,18944],{"class":3419,"line":18943},466,[3417,18945,3442],{"emptyLinePlaceholder":3441},[3417,18947,18949],{"class":3419,"line":18948},467,[3417,18950,18951],{"class":6183},"  \u002F\u002F ── Відправлення повідомлення ────────────────────────\n",[3417,18953,18955,18957,18960],{"class":3419,"line":18954},468,[3417,18956,16871],{"class":4041},[3417,18958,18959],{"class":6043}," sendMessage",[3417,18961,18962],{"class":7550},"() {\n",[3417,18964,18966,18968,18970,18972,18974,18976,18979,18981,18984],{"class":3419,"line":18965},469,[3417,18967,16905],{"class":4041},[3417,18969,18399],{"class":12627},[3417,18971,16530],{"class":4037},[3417,18973,16776],{"class":6164},[3417,18975,4253],{"class":7550},[3417,18977,18978],{"class":6164},"value",[3417,18980,4253],{"class":7550},[3417,18982,18983],{"class":6043},"trim",[3417,18985,6473],{"class":7550},[3417,18987,18989,18991,18993,18995,18997,18999,19002,19004,19006,19008,19010,19013,19015,19017,19019,19022,19024,19026],{"class":3419,"line":18988},470,[3417,18990,9328],{"class":4033},[3417,18992,7254],{"class":7550},[3417,18994,17961],{"class":4037},[3417,18996,3387],{"class":6164},[3417,18998,17401],{"class":4037},[3417,19000,19001],{"class":4037}," !",[3417,19003,7511],{"class":6164},[3417,19005,17401],{"class":4037},[3417,19007,7461],{"class":6164},[3417,19009,4253],{"class":7550},[3417,19011,19012],{"class":6164},"readyState",[3417,19014,18668],{"class":4037},[3417,19016,6987],{"class":6164},[3417,19018,4253],{"class":7550},[3417,19020,19021],{"class":12627},"OPEN",[3417,19023,8271],{"class":7550},[3417,19025,8426],{"class":4033},[3417,19027,6130],{"class":7550},[3417,19029,19031],{"class":3419,"line":19030},471,[3417,19032,3442],{"emptyLinePlaceholder":3441},[3417,19034,19036,19038,19041,19043],{"class":3419,"line":19035},472,[3417,19037,16905],{"class":4041},[3417,19039,19040],{"class":12627}," payload",[3417,19042,16530],{"class":4037},[3417,19044,19045],{"class":6164}," privateTarget\n",[3417,19047,19049,19051,19053,19056,19058,19060,19063,19065,19067,19069],{"class":3419,"line":19048},473,[3417,19050,18183],{"class":4037},[3417,19052,6168],{"class":7550},[3417,19054,19055],{"class":6164},"type:",[3417,19057,17653],{"class":4060},[3417,19059,4246],{"class":7550},[3417,19061,19062],{"class":6164},"to:",[3417,19064,16557],{"class":6164},[3417,19066,4246],{"class":7550},[3417,19068,3387],{"class":6164},[3417,19070,7266],{"class":7550},[3417,19072,19074,19076,19078,19080,19082,19084,19086],{"class":3419,"line":19073},474,[3417,19075,18207],{"class":4037},[3417,19077,6168],{"class":7550},[3417,19079,19055],{"class":6164},[3417,19081,17592],{"class":4060},[3417,19083,4246],{"class":7550},[3417,19085,3387],{"class":6164},[3417,19087,11974],{"class":7550},[3417,19089,19091],{"class":3419,"line":19090},475,[3417,19092,3442],{"emptyLinePlaceholder":3441},[3417,19094,19096],{"class":3419,"line":19095},476,[3417,19097,19098],{"class":6183},"    \u002F\u002F ws.send() — передає рядок як Text WebSocket frame\n",[3417,19100,19102,19104,19106,19109,19111,19114,19116,19119,19121,19124],{"class":3419,"line":19101},477,[3417,19103,17005],{"class":6164},[3417,19105,4253],{"class":7550},[3417,19107,19108],{"class":6043},"send",[3417,19110,6537],{"class":7550},[3417,19112,19113],{"class":12627},"JSON",[3417,19115,4253],{"class":7550},[3417,19117,19118],{"class":6043},"stringify",[3417,19120,6537],{"class":7550},[3417,19122,19123],{"class":6164},"payload",[3417,19125,6758],{"class":7550},[3417,19127,19129,19131,19133,19135,19137,19139],{"class":3419,"line":19128},478,[3417,19130,18819],{"class":6164},[3417,19132,4253],{"class":7550},[3417,19134,18978],{"class":6164},[3417,19136,16530],{"class":4037},[3417,19138,16547],{"class":4060},[3417,19140,6130],{"class":7550},[3417,19142,19144,19146,19148,19150,19152,19155,19157,19160],{"class":3419,"line":19143},479,[3417,19145,18819],{"class":6164},[3417,19147,4253],{"class":7550},[3417,19149,12947],{"class":6164},[3417,19151,4253],{"class":7550},[3417,19153,19154],{"class":6164},"height",[3417,19156,16530],{"class":4037},[3417,19158,19159],{"class":4060}," 'auto'",[3417,19161,6130],{"class":7550},[3417,19163,19165],{"class":3419,"line":19164},480,[3417,19166,17542],{"class":7550},[3417,19168,19170],{"class":3419,"line":19169},481,[3417,19171,3442],{"emptyLinePlaceholder":3441},[3417,19173,19175],{"class":3419,"line":19174},482,[3417,19176,19177],{"class":6183},"  \u002F\u002F ── Обробники подій UI ───────────────────────────────\n",[3417,19179,19181,19184,19186,19188,19190,19192,19194,19196],{"class":3419,"line":19180},483,[3417,19182,19183],{"class":6164},"  joinBtn",[3417,19185,4253],{"class":7550},[3417,19187,18689],{"class":6043},[3417,19189,6537],{"class":7550},[3417,19191,18694],{"class":4060},[3417,19193,18697],{"class":7550},[3417,19195,17065],{"class":4041},[3417,19197,13013],{"class":7550},[3417,19199,19201,19203,19205,19207,19209,19211,19213,19215,19217],{"class":3419,"line":19200},484,[3417,19202,16905],{"class":4041},[3417,19204,6542],{"class":12627},[3417,19206,16530],{"class":4037},[3417,19208,16640],{"class":6164},[3417,19210,4253],{"class":7550},[3417,19212,18978],{"class":6164},[3417,19214,4253],{"class":7550},[3417,19216,18983],{"class":6043},[3417,19218,6473],{"class":7550},[3417,19220,19222,19224,19226,19228,19230,19232,19235,19237,19239,19241,19244,19246,19248],{"class":3419,"line":19221},485,[3417,19223,9328],{"class":4033},[3417,19225,7254],{"class":7550},[3417,19227,17961],{"class":4037},[3417,19229,6569],{"class":6164},[3417,19231,7260],{"class":7550},[3417,19233,19234],{"class":6164},"loginError",[3417,19236,4253],{"class":7550},[3417,19238,17129],{"class":6164},[3417,19240,16530],{"class":4037},[3417,19242,19243],{"class":4060}," 'Введіть нікнейм'",[3417,19245,6174],{"class":7550},[3417,19247,8426],{"class":4033},[3417,19249,6207],{"class":7550},[3417,19251,19253,19255,19257,19259,19261,19264,19267,19270,19272,19274,19276,19278,19280,19283,19285,19287],{"class":3419,"line":19252},486,[3417,19254,9328],{"class":4033},[3417,19256,7254],{"class":7550},[3417,19258,6569],{"class":6164},[3417,19260,4253],{"class":7550},[3417,19262,19263],{"class":6164},"length",[3417,19265,19266],{"class":4037}," >",[3417,19268,19269],{"class":4048}," 20",[3417,19271,7260],{"class":7550},[3417,19273,19234],{"class":6164},[3417,19275,4253],{"class":7550},[3417,19277,17129],{"class":6164},[3417,19279,16530],{"class":4037},[3417,19281,19282],{"class":4060}," 'Максимум 20 символів'",[3417,19284,6174],{"class":7550},[3417,19286,8426],{"class":4033},[3417,19288,6207],{"class":7550},[3417,19290,19292,19295,19297,19299,19301,19303],{"class":3419,"line":19291},487,[3417,19293,19294],{"class":6164},"    loginError",[3417,19296,4253],{"class":7550},[3417,19298,17129],{"class":6164},[3417,19300,16530],{"class":4037},[3417,19302,16547],{"class":4060},[3417,19304,6130],{"class":7550},[3417,19306,19308,19311,19313,19315],{"class":3419,"line":19307},488,[3417,19309,19310],{"class":6043},"    connect",[3417,19312,6537],{"class":7550},[3417,19314,6569],{"class":6164},[3417,19316,6577],{"class":7550},[3417,19318,19320],{"class":3419,"line":19319},489,[3417,19321,18940],{"class":7550},[3417,19323,19325],{"class":3419,"line":19324},490,[3417,19326,3442],{"emptyLinePlaceholder":3441},[3417,19328,19330,19333,19335,19337,19339,19342,19344,19346,19348],{"class":3419,"line":19329},491,[3417,19331,19332],{"class":6164},"  usernameIn",[3417,19334,4253],{"class":7550},[3417,19336,18689],{"class":6043},[3417,19338,6537],{"class":7550},[3417,19340,19341],{"class":4060},"'keydown'",[3417,19343,4246],{"class":7550},[3417,19345,9031],{"class":6164},[3417,19347,11313],{"class":4041},[3417,19349,13013],{"class":7550},[3417,19351,19353,19355,19357,19359,19361,19364,19366,19369,19371,19374,19376,19379],{"class":3419,"line":19352},492,[3417,19354,9328],{"class":4033},[3417,19356,7254],{"class":7550},[3417,19358,9031],{"class":6164},[3417,19360,4253],{"class":7550},[3417,19362,19363],{"class":6164},"key",[3417,19365,16921],{"class":4037},[3417,19367,19368],{"class":4060}," 'Enter'",[3417,19370,8271],{"class":7550},[3417,19372,19373],{"class":6164},"joinBtn",[3417,19375,4253],{"class":7550},[3417,19377,19378],{"class":6043},"click",[3417,19380,6473],{"class":7550},[3417,19382,19384],{"class":3419,"line":19383},493,[3417,19385,18940],{"class":7550},[3417,19387,19389],{"class":3419,"line":19388},494,[3417,19390,3442],{"emptyLinePlaceholder":3441},[3417,19392,19394,19397,19399,19401,19403,19405,19407,19410],{"class":3419,"line":19393},495,[3417,19395,19396],{"class":6164},"  sendBtn",[3417,19398,4253],{"class":7550},[3417,19400,18689],{"class":6043},[3417,19402,6537],{"class":7550},[3417,19404,18694],{"class":4060},[3417,19406,4246],{"class":7550},[3417,19408,19409],{"class":6164},"sendMessage",[3417,19411,6577],{"class":7550},[3417,19413,19415],{"class":3419,"line":19414},496,[3417,19416,3442],{"emptyLinePlaceholder":3441},[3417,19418,19420,19423,19425,19427,19429,19431,19433,19435,19437],{"class":3419,"line":19419},497,[3417,19421,19422],{"class":6164},"  msgInput",[3417,19424,4253],{"class":7550},[3417,19426,18689],{"class":6043},[3417,19428,6537],{"class":7550},[3417,19430,19341],{"class":4060},[3417,19432,4246],{"class":7550},[3417,19434,9031],{"class":6164},[3417,19436,11313],{"class":4041},[3417,19438,13013],{"class":7550},[3417,19440,19442,19444,19446,19448,19450,19452,19454,19456,19459,19461,19463,19465,19468],{"class":3419,"line":19441},498,[3417,19443,9328],{"class":4033},[3417,19445,7254],{"class":7550},[3417,19447,9031],{"class":6164},[3417,19449,4253],{"class":7550},[3417,19451,19363],{"class":6164},[3417,19453,16921],{"class":4037},[3417,19455,19368],{"class":4060},[3417,19457,19458],{"class":4037}," &&",[3417,19460,19001],{"class":4037},[3417,19462,9031],{"class":6164},[3417,19464,4253],{"class":7550},[3417,19466,19467],{"class":6164},"shiftKey",[3417,19469,16881],{"class":7550},[3417,19471,19473,19476,19478,19481,19484],{"class":3419,"line":19472},499,[3417,19474,19475],{"class":6164},"      e",[3417,19477,4253],{"class":7550},[3417,19479,19480],{"class":6043},"preventDefault",[3417,19482,19483],{"class":7550},"(); ",[3417,19485,19486],{"class":6183},"\u002F\u002F Запобігаємо новому рядку\n",[3417,19488,19490,19493],{"class":3419,"line":19489},500,[3417,19491,19492],{"class":6043},"      sendMessage",[3417,19494,6473],{"class":7550},[3417,19496,19498],{"class":3419,"line":19497},501,[3417,19499,5765],{"class":7550},[3417,19501,19503],{"class":3419,"line":19502},502,[3417,19504,18940],{"class":7550},[3417,19506,19508],{"class":3419,"line":19507},503,[3417,19509,3442],{"emptyLinePlaceholder":3441},[3417,19511,19513],{"class":3419,"line":19512},504,[3417,19514,19515],{"class":6183},"  \u002F\u002F Автозростання textarea\n",[3417,19517,19519,19521,19523,19525,19527,19530,19532,19534],{"class":3419,"line":19518},505,[3417,19520,19422],{"class":6164},[3417,19522,4253],{"class":7550},[3417,19524,18689],{"class":6043},[3417,19526,6537],{"class":7550},[3417,19528,19529],{"class":4060},"'input'",[3417,19531,18697],{"class":7550},[3417,19533,17065],{"class":4041},[3417,19535,13013],{"class":7550},[3417,19537,19539,19541,19543,19545,19547,19549,19551,19553],{"class":3419,"line":19538},506,[3417,19540,18819],{"class":6164},[3417,19542,4253],{"class":7550},[3417,19544,12947],{"class":6164},[3417,19546,4253],{"class":7550},[3417,19548,19154],{"class":6164},[3417,19550,16530],{"class":4037},[3417,19552,19159],{"class":4060},[3417,19554,6130],{"class":7550},[3417,19556,19558,19560,19562,19564,19566,19568,19570,19573,19575,19578,19580,19583,19585,19587,19589,19592,19594,19597,19600],{"class":3419,"line":19557},507,[3417,19559,18819],{"class":6164},[3417,19561,4253],{"class":7550},[3417,19563,12947],{"class":6164},[3417,19565,4253],{"class":7550},[3417,19567,19154],{"class":6164},[3417,19569,16530],{"class":4037},[3417,19571,19572],{"class":6164}," Math",[3417,19574,4253],{"class":7550},[3417,19576,19577],{"class":6043},"min",[3417,19579,6537],{"class":7550},[3417,19581,19582],{"class":6164},"msgInput",[3417,19584,4253],{"class":7550},[3417,19586,18478],{"class":6164},[3417,19588,4246],{"class":7550},[3417,19590,19591],{"class":4048},"120",[3417,19593,8271],{"class":7550},[3417,19595,19596],{"class":4037},"+",[3417,19598,19599],{"class":4060}," 'px'",[3417,19601,6130],{"class":7550},[3417,19603,19605],{"class":3419,"line":19604},508,[3417,19606,18940],{"class":7550},[3417,19608,19610],{"class":3419,"line":19609},509,[3417,19611,3442],{"emptyLinePlaceholder":3441},[3417,19613,19615],{"class":3419,"line":19614},510,[3417,19616,19617],{"class":6183},"  \u002F\u002F Коректне закриття при закритті вкладки\n",[3417,19619,19621,19624,19626,19628,19630,19633,19635,19637],{"class":3419,"line":19620},511,[3417,19622,19623],{"class":6164},"  window",[3417,19625,4253],{"class":7550},[3417,19627,18689],{"class":6043},[3417,19629,6537],{"class":7550},[3417,19631,19632],{"class":4060},"'beforeunload'",[3417,19634,18697],{"class":7550},[3417,19636,17065],{"class":4041},[3417,19638,13013],{"class":7550},[3417,19640,19642,19644,19646,19648,19650,19652,19654,19656,19658,19660,19662,19664],{"class":3419,"line":19641},512,[3417,19643,9328],{"class":4033},[3417,19645,7254],{"class":7550},[3417,19647,7511],{"class":6164},[3417,19649,19458],{"class":4037},[3417,19651,7461],{"class":6164},[3417,19653,4253],{"class":7550},[3417,19655,19012],{"class":6164},[3417,19657,16921],{"class":4037},[3417,19659,6987],{"class":6164},[3417,19661,4253],{"class":7550},[3417,19663,19021],{"class":12627},[3417,19665,16881],{"class":7550},[3417,19667,19669],{"class":3419,"line":19668},513,[3417,19670,19671],{"class":6183},"      \u002F\u002F Надсилаємо Close Frame із кодом 1001 \"Going Away\"\n",[3417,19673,19675,19678,19680,19683,19685,19687,19689,19692],{"class":3419,"line":19674},514,[3417,19676,19677],{"class":6164},"      ws",[3417,19679,4253],{"class":7550},[3417,19681,19682],{"class":6043},"close",[3417,19684,6537],{"class":7550},[3417,19686,5021],{"class":4048},[3417,19688,4246],{"class":7550},[3417,19690,19691],{"class":4060},"'Page closing'",[3417,19693,6577],{"class":7550},[3417,19695,19697],{"class":3419,"line":19696},515,[3417,19698,5765],{"class":7550},[3417,19700,19702],{"class":3419,"line":19701},516,[3417,19703,18940],{"class":7550},[3417,19705,19707],{"class":3419,"line":19706},517,[3417,19708,3442],{"emptyLinePlaceholder":3441},[3417,19710,19712],{"class":3419,"line":19711},518,[3417,19713,19714],{"class":6183},"  \u002F\u002F ── Допоміжні функції ────────────────────────────────\n",[3417,19716,19718,19720,19723,19725,19728],{"class":3419,"line":19717},519,[3417,19719,16871],{"class":4041},[3417,19721,19722],{"class":6043}," setStatus",[3417,19724,6537],{"class":7550},[3417,19726,19727],{"class":6164},"state",[3417,19729,16881],{"class":7550},[3417,19731,19733,19735,19738,19740],{"class":3419,"line":19732},520,[3417,19734,16905],{"class":4041},[3417,19736,19737],{"class":12627}," labels",[3417,19739,16530],{"class":4037},[3417,19741,13013],{"class":7550},[3417,19743,19745,19748,19751,19754,19756,19759],{"class":3419,"line":19744},521,[3417,19746,19747],{"class":6164},"      connecting:",[3417,19749,19750],{"class":7550}," [",[3417,19752,19753],{"class":4060},"'● Підключення...'",[3417,19755,4246],{"class":7550},[3417,19757,19758],{"class":4060},"'badge-connecting'",[3417,19760,19761],{"class":7550},"],\n",[3417,19763,19765,19768,19771,19774,19777,19780],{"class":3419,"line":19764},522,[3417,19766,19767],{"class":6164},"      open:",[3417,19769,19770],{"class":7550},"       [",[3417,19772,19773],{"class":4060},"'● Онлайн'",[3417,19775,19776],{"class":7550},",         ",[3417,19778,19779],{"class":4060},"'badge-open'",[3417,19781,19761],{"class":7550},[3417,19783,19785,19788,19791,19794,19797,19800],{"class":3419,"line":19784},523,[3417,19786,19787],{"class":6164},"      closed:",[3417,19789,19790],{"class":7550},"     [",[3417,19792,19793],{"class":4060},"'● Відключено'",[3417,19795,19796],{"class":7550},",      ",[3417,19798,19799],{"class":4060},"'badge-closed'",[3417,19801,19761],{"class":7550},[3417,19803,19805],{"class":3419,"line":19804},524,[3417,19806,6520],{"class":7550},[3417,19808,19810,19812,19814,19816,19818,19821,19824,19826,19828,19830,19832],{"class":3419,"line":19809},525,[3417,19811,16905],{"class":4041},[3417,19813,19750],{"class":7550},[3417,19815,3387],{"class":12627},[3417,19817,4246],{"class":7550},[3417,19819,19820],{"class":12627},"cls",[3417,19822,19823],{"class":7550},"] ",[3417,19825,12862],{"class":4037},[3417,19827,19737],{"class":6164},[3417,19829,7969],{"class":7550},[3417,19831,19727],{"class":6164},[3417,19833,6661],{"class":7550},[3417,19835,19837,19840,19842,19844,19846,19848],{"class":3419,"line":19836},526,[3417,19838,19839],{"class":6164},"    statusBadge",[3417,19841,4253],{"class":7550},[3417,19843,17129],{"class":6164},[3417,19845,16530],{"class":4037},[3417,19847,18399],{"class":6164},[3417,19849,6130],{"class":7550},[3417,19851,19853,19855,19857,19859,19861,19864],{"class":3419,"line":19852},527,[3417,19854,19839],{"class":6164},[3417,19856,4253],{"class":7550},[3417,19858,17911],{"class":6164},[3417,19860,16530],{"class":4037},[3417,19862,19863],{"class":6164}," cls",[3417,19865,6130],{"class":7550},[3417,19867,19869],{"class":3419,"line":19868},528,[3417,19870,17542],{"class":7550},[3417,19872,19874],{"class":3419,"line":19873},529,[3417,19875,3442],{"emptyLinePlaceholder":3441},[3417,19877,19879,19881,19884,19886,19889],{"class":3419,"line":19878},530,[3417,19880,16871],{"class":4041},[3417,19882,19883],{"class":6043}," formatTime",[3417,19885,6537],{"class":7550},[3417,19887,19888],{"class":6164},"iso",[3417,19890,16881],{"class":7550},[3417,19892,19894,19896,19898,19900,19902,19904,19906,19908],{"class":3419,"line":19893},531,[3417,19895,9328],{"class":4033},[3417,19897,7254],{"class":7550},[3417,19899,17961],{"class":4037},[3417,19901,19888],{"class":6164},[3417,19903,8271],{"class":7550},[3417,19905,8426],{"class":4033},[3417,19907,16547],{"class":4060},[3417,19909,6130],{"class":7550},[3417,19911,19913,19916],{"class":3419,"line":19912},532,[3417,19914,19915],{"class":4033},"    try",[3417,19917,13013],{"class":7550},[3417,19919,19921,19924,19926,19929,19931,19933,19935,19938,19940,19943],{"class":3419,"line":19920},533,[3417,19922,19923],{"class":4033},"      return",[3417,19925,6047],{"class":4041},[3417,19927,19928],{"class":6043}," Date",[3417,19930,6537],{"class":7550},[3417,19932,19888],{"class":6164},[3417,19934,10992],{"class":7550},[3417,19936,19937],{"class":6043},"toLocaleTimeString",[3417,19939,6537],{"class":7550},[3417,19941,19942],{"class":4060},"'uk-UA'",[3417,19944,7216],{"class":7550},[3417,19946,19948,19951,19954,19957,19959,19962,19964],{"class":3419,"line":19947},534,[3417,19949,19950],{"class":7550},"        { ",[3417,19952,19953],{"class":6164},"hour:",[3417,19955,19956],{"class":4060}," '2-digit'",[3417,19958,4246],{"class":7550},[3417,19960,19961],{"class":6164},"minute:",[3417,19963,19956],{"class":4060},[3417,19965,19966],{"class":7550}," });\n",[3417,19968,19970,19973,19975,19977,19979,19981],{"class":3419,"line":19969},535,[3417,19971,19972],{"class":7550},"    } ",[3417,19974,9163],{"class":4033},[3417,19976,6168],{"class":7550},[3417,19978,8426],{"class":4033},[3417,19980,16547],{"class":4060},[3417,19982,6207],{"class":7550},[3417,19984,19986],{"class":3419,"line":19985},536,[3417,19987,17542],{"class":7550},[3417,19989,19991],{"class":3419,"line":19990},537,[3417,19992,3442],{"emptyLinePlaceholder":3441},[3417,19994,19996,19998,20001,20003,20006,20008,20010],{"class":3419,"line":19995},538,[3417,19997,16871],{"class":4041},[3417,19999,20000],{"class":6043}," escapeHtml",[3417,20002,6537],{"class":7550},[3417,20004,20005],{"class":6164},"str",[3417,20007,16530],{"class":4037},[3417,20009,16547],{"class":4060},[3417,20011,16881],{"class":7550},[3417,20013,20015,20017],{"class":3419,"line":20014},539,[3417,20016,10361],{"class":4033},[3417,20018,20019],{"class":6164}," str\n",[3417,20021,20023,20026,20028,20030,20033,20035,20037,20040],{"class":3419,"line":20022},540,[3417,20024,20025],{"class":7550},"      .",[3417,20027,18009],{"class":6043},[3417,20029,6537],{"class":7550},[3417,20031,20032],{"class":18014},"\u002F&\u002F",[3417,20034,18018],{"class":4041},[3417,20036,4246],{"class":7550},[3417,20038,20039],{"class":4060},"'&amp;'",[3417,20041,6552],{"class":7550},[3417,20043,20045,20047,20049,20051,20054,20056,20058,20061],{"class":3419,"line":20044},541,[3417,20046,20025],{"class":7550},[3417,20048,18009],{"class":6043},[3417,20050,6537],{"class":7550},[3417,20052,20053],{"class":18014},"\u002F\u003C\u002F",[3417,20055,18018],{"class":4041},[3417,20057,4246],{"class":7550},[3417,20059,20060],{"class":4060},"'&lt;'",[3417,20062,6552],{"class":7550},[3417,20064,20066,20068,20070,20072,20075,20077,20079,20082],{"class":3419,"line":20065},542,[3417,20067,20025],{"class":7550},[3417,20069,18009],{"class":6043},[3417,20071,6537],{"class":7550},[3417,20073,20074],{"class":18014},"\u002F>\u002F",[3417,20076,18018],{"class":4041},[3417,20078,4246],{"class":7550},[3417,20080,20081],{"class":4060},"'&gt;'",[3417,20083,6552],{"class":7550},[3417,20085,20087,20089,20091,20093,20096,20098,20100,20103],{"class":3419,"line":20086},543,[3417,20088,20025],{"class":7550},[3417,20090,18009],{"class":6043},[3417,20092,6537],{"class":7550},[3417,20094,20095],{"class":18014},"\u002F\"\u002F",[3417,20097,18018],{"class":4041},[3417,20099,4246],{"class":7550},[3417,20101,20102],{"class":4060},"'&quot;'",[3417,20104,6577],{"class":7550},[3417,20106,20108],{"class":3419,"line":20107},544,[3417,20109,17542],{"class":7550},[3417,20111,20113,20115,20117],{"class":3419,"line":20112},545,[3417,20114,12936],{"class":12839},[3417,20116,16511],{"class":4054},[3417,20118,12850],{"class":12839},[3417,20120,20122,20124,20126],{"class":3419,"line":20121},546,[3417,20123,12936],{"class":12839},[3417,20125,9442],{"class":4054},[3417,20127,12850],{"class":12839},[3417,20129,20131,20133,20135],{"class":3419,"line":20130},547,[3417,20132,12936],{"class":12839},[3417,20134,12832],{"class":4054},[3417,20136,12850],{"class":12839},[3364,20138],{},[3374,20140,20142],{"id":20141},"розбір-ключових-рішень-у-javascript-клієнті","Розбір ключових рішень у JavaScript-клієнті",[3317,20144,20145,20148],{},[3321,20146,20147],{},"Визначення URL (ws:\u002F\u002F або wss:\u002F\u002F)"," — скрипт автоматично обирає схему залежно від протоколу сторінки:",[3382,20150,20152],{"className":12615,"code":20151,"language":12617,"meta":3391,"style":3391},"const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst url = `${protocol}\u002F\u002F${location.host}\u002Fws?user=${encodeURIComponent(username)}`;\n",[3389,20153,20154,20186],{"__ignoreMap":3391},[3417,20155,20156,20158,20160,20162,20164,20166,20168,20171,20174,20176,20179,20181,20184],{"class":3419,"line":3420},[3417,20157,12624],{"class":4041},[3417,20159,16908],{"class":12627},[3417,20161,6467],{"class":4037},[3417,20163,16966],{"class":6164},[3417,20165,4253],{"class":4037},[3417,20167,16918],{"class":6164},[3417,20169,20170],{"class":4037}," === ",[3417,20172,20173],{"class":4060},"'https:'",[3417,20175,8721],{"class":4037},[3417,20177,20178],{"class":4060},"'wss:'",[3417,20180,8726],{"class":4037},[3417,20182,20183],{"class":4060},"'ws:'",[3417,20185,6130],{"class":4037},[3417,20187,20188,20190,20192,20194,20196,20198,20200,20202,20204,20206,20208,20210,20212,20214,20216,20218,20220,20222,20224,20226,20228,20230],{"class":3419,"line":3426},[3417,20189,12624],{"class":4041},[3417,20191,16946],{"class":12627},[3417,20193,6467],{"class":4037},[3417,20195,16992],{"class":4060},[3417,20197,16954],{"class":4041},[3417,20199,16918],{"class":6164},[3417,20201,7555],{"class":4041},[3417,20203,16961],{"class":4060},[3417,20205,16954],{"class":4041},[3417,20207,16966],{"class":6164},[3417,20209,4253],{"class":7550},[3417,20211,16971],{"class":6164},[3417,20213,7555],{"class":4041},[3417,20215,16976],{"class":4060},[3417,20217,16954],{"class":4041},[3417,20219,16981],{"class":6043},[3417,20221,6537],{"class":7550},[3417,20223,6569],{"class":6164},[3417,20225,3996],{"class":7550},[3417,20227,7555],{"class":4041},[3417,20229,16992],{"class":4060},[3417,20231,6130],{"class":4037},[3317,20233,20234,20235,4246,20238,20241,20242,20244],{},"Це критично важлива деталь: сторінка, завантажена по ",[3389,20236,20237],{},"https:\u002F\u002F",[3321,20239,20240],{},"не може"," відкрити незашифроване ",[3389,20243,5192],{}," з'єднання (Mixed Content Policy браузера). Завжди синхронізуйте схему сторінки та WebSocket.",[3317,20246,20247,20250],{},[3321,20248,20249],{},"Ідентифікація власних повідомлень"," — сервер повертає повідомлення відправника назад у broadcast:",[3382,20252,20254],{"className":12615,"code":20253,"language":12617,"meta":3391,"style":3391},"case 'chat':\n  const isMineMsg = msg.from === myUsername;  \u002F\u002F порівнюємо з myUsername\n  addChatMessage(msg.from, msg.text, msg.timestamp, isMineMsg);\n",[3389,20255,20256,20265,20289],{"__ignoreMap":3391},[3417,20257,20258,20261,20263],{"class":3419,"line":3420},[3417,20259,20260],{"class":4033},"case",[3417,20262,17592],{"class":4060},[3417,20264,8488],{"class":4037},[3417,20266,20267,20269,20271,20273,20275,20277,20279,20281,20283,20286],{"class":3419,"line":3426},[3417,20268,16577],{"class":4041},[3417,20270,17867],{"class":12627},[3417,20272,6467],{"class":4037},[3417,20274,8381],{"class":6164},[3417,20276,4253],{"class":4037},[3417,20278,8531],{"class":6164},[3417,20280,20170],{"class":4037},[3417,20282,17139],{"class":6164},[3417,20284,20285],{"class":4037},";  ",[3417,20287,20288],{"class":6183},"\u002F\u002F порівнюємо з myUsername\n",[3417,20290,20291,20294,20296,20298,20300,20302,20304,20306,20308,20310,20312,20314,20316,20318,20320,20322],{"class":3419,"line":3432},[3417,20292,20293],{"class":6043},"  addChatMessage",[3417,20295,6537],{"class":4037},[3417,20297,8381],{"class":6164},[3417,20299,4253],{"class":4037},[3417,20301,8531],{"class":6164},[3417,20303,4246],{"class":4037},[3417,20305,8381],{"class":6164},[3417,20307,4253],{"class":4037},[3417,20309,3387],{"class":6164},[3417,20311,4246],{"class":4037},[3417,20313,8381],{"class":6164},[3417,20315,4253],{"class":4037},[3417,20317,17625],{"class":6164},[3417,20319,4246],{"class":4037},[3417,20321,17921],{"class":6164},[3417,20323,6577],{"class":4037},[3317,20325,20326],{},"Завдяки цьому не потрібен окремий механізм «підтвердження»: відправник бачить своє повідомлення лише після того, як воно пройшло через сервер. Це гарантує, що відображений текст відповідає тому, що реально отримали інші клієнти.",[3317,20328,20329,20335,20336,20338,20339,20342,20343,20346],{},[3321,20330,20331,20332],{},"Безпека: ",[3389,20333,20334],{},"escapeHtml()"," — усі рядки від сервера проходять HTML-екранування перед вставкою через ",[3389,20337,17947],{},". Це захист від ",[3321,20340,20341],{},"Stored XSS",": якщо зловмисний користувач надішле ",[3389,20344,20345],{},"\u003Cscript>alert(1)\u003C\u002Fscript>",", браузер відобразить цей рядок як текст, а не виконає його.",[3317,20348,20349,20354],{},[3321,20350,20351,20352],{},"Автозростання ",[3389,20353,16394],{}," — замість фіксованої висоти поле введення динамічно розширюється:",[3382,20356,20358],{"className":12615,"code":20357,"language":12617,"meta":3391,"style":3391},"msgInput.addEventListener('input', () => {\n  msgInput.style.height = 'auto';\n  msgInput.style.height = Math.min(msgInput.scrollHeight, 120) + 'px';\n});\n",[3389,20359,20360,20378,20397,20438],{"__ignoreMap":3391},[3417,20361,20362,20364,20366,20368,20370,20372,20374,20376],{"class":3419,"line":3420},[3417,20363,19582],{"class":6164},[3417,20365,4253],{"class":4037},[3417,20367,18689],{"class":6043},[3417,20369,6537],{"class":4037},[3417,20371,19529],{"class":4060},[3417,20373,18697],{"class":4037},[3417,20375,17065],{"class":4041},[3417,20377,13013],{"class":4037},[3417,20379,20380,20382,20384,20386,20388,20390,20392,20395],{"class":3419,"line":3426},[3417,20381,19422],{"class":6164},[3417,20383,4253],{"class":4037},[3417,20385,12947],{"class":6164},[3417,20387,4253],{"class":4037},[3417,20389,19154],{"class":6164},[3417,20391,6467],{"class":4037},[3417,20393,20394],{"class":4060},"'auto'",[3417,20396,6130],{"class":4037},[3417,20398,20399,20401,20403,20405,20407,20409,20411,20414,20416,20418,20420,20422,20424,20426,20428,20430,20433,20436],{"class":3419,"line":3432},[3417,20400,19422],{"class":6164},[3417,20402,4253],{"class":4037},[3417,20404,12947],{"class":6164},[3417,20406,4253],{"class":4037},[3417,20408,19154],{"class":6164},[3417,20410,6467],{"class":4037},[3417,20412,20413],{"class":6164},"Math",[3417,20415,4253],{"class":4037},[3417,20417,19577],{"class":6043},[3417,20419,6537],{"class":4037},[3417,20421,19582],{"class":6164},[3417,20423,4253],{"class":4037},[3417,20425,18478],{"class":6164},[3417,20427,4246],{"class":4037},[3417,20429,19591],{"class":4048},[3417,20431,20432],{"class":4037},") + ",[3417,20434,20435],{"class":4060},"'px'",[3417,20437,6130],{"class":4037},[3417,20439,20440],{"class":3419,"line":3438},[3417,20441,20442],{"class":4037},"});\n",[3317,20444,20445,20446,20448,20449,20451],{},"Скидання до ",[3389,20447,20394],{}," змушує браузер перерахувати ",[3389,20450,18478],{}," — без цього кроку висота ніколи не зменшується після видалення тексту.",[3317,20453,20454],{},[3321,20455,20456],{},"Коректне закриття при закритті вкладки:",[3382,20458,20460],{"className":12615,"code":20459,"language":12617,"meta":3391,"style":3391},"window.addEventListener('beforeunload', () => {\n  if (ws && ws.readyState === WebSocket.OPEN)\n    ws.close(1001, 'Page closing');\n});\n",[3389,20461,20462,20481,20508,20526],{"__ignoreMap":3391},[3417,20463,20464,20467,20469,20471,20473,20475,20477,20479],{"class":3419,"line":3420},[3417,20465,20466],{"class":6164},"window",[3417,20468,4253],{"class":4037},[3417,20470,18689],{"class":6043},[3417,20472,6537],{"class":4037},[3417,20474,19632],{"class":4060},[3417,20476,18697],{"class":4037},[3417,20478,17065],{"class":4041},[3417,20480,13013],{"class":4037},[3417,20482,20483,20486,20488,20490,20492,20494,20496,20498,20500,20502,20504,20506],{"class":3419,"line":3426},[3417,20484,20485],{"class":4033},"  if",[3417,20487,7254],{"class":4037},[3417,20489,7511],{"class":6164},[3417,20491,9344],{"class":4037},[3417,20493,7511],{"class":6164},[3417,20495,4253],{"class":4037},[3417,20497,19012],{"class":6164},[3417,20499,20170],{"class":4037},[3417,20501,3649],{"class":6164},[3417,20503,4253],{"class":4037},[3417,20505,19021],{"class":12627},[3417,20507,6552],{"class":4037},[3417,20509,20510,20512,20514,20516,20518,20520,20522,20524],{"class":3419,"line":3432},[3417,20511,17005],{"class":6164},[3417,20513,4253],{"class":4037},[3417,20515,19682],{"class":6043},[3417,20517,6537],{"class":4037},[3417,20519,5021],{"class":4048},[3417,20521,4246],{"class":4037},[3417,20523,19691],{"class":4060},[3417,20525,6577],{"class":4037},[3417,20527,20528],{"class":3419,"line":3438},[3417,20529,20442],{"class":4037},[3317,20531,20532,20533,20536],{},"Код ",[3389,20534,20535],{},"1001 Going Away"," є семантично правильним: він сигналізує серверу, що клієнт навмисно йде (закрита вкладка, перехід на іншу сторінку), а не аварійний розрив. Сервер може використовувати це для відображення іншого системного повідомлення.",[3364,20538],{},[3374,20540,20542],{"id":20541},"запуск-браузерного-клієнта","Запуск браузерного клієнта",[12226,20544,20545,20549,20554,20557,20577,20581,20588,20592],{},[12229,20546,20548],{"id":20547},"створіть-файл","Створіть файл",[3317,20550,20551,20552,4253],{},"Збережіть HTML у ",[3389,20553,12819],{},[12229,20555,12232],{"id":20556},"запустіть-сервер-1",[3382,20558,20559],{"className":6034,"code":12235,"language":6036,"meta":3391,"style":3391},[3389,20560,20561,20567,20573],{"__ignoreMap":3391},[3417,20562,20563,20565],{"class":3419,"line":3420},[3417,20564,12242],{"class":6043},[3417,20566,6070],{"class":4060},[3417,20568,20569,20571],{"class":3419,"line":3426},[3417,20570,6044],{"class":6043},[3417,20572,12251],{"class":4060},[3417,20574,20575],{"class":3419,"line":3432},[3417,20576,12256],{"class":6183},[12229,20578,20580],{"id":20579},"відкрийте-браузер","Відкрийте браузер",[3317,20582,20583,20584,20587],{},"Перейдіть на ",[3389,20585,20586],{},"http:\u002F\u002Flocalhost:5000"," — буде відкрита сторінка чату.",[12229,20589,20591],{"id":20590},"тестуйте-з-кількох-вкладок-або-браузерів","Тестуйте з кількох вкладок або браузерів",[3317,20593,20594,20595,20597],{},"Відкрийте ",[3389,20596,20586],{}," у двох різних вкладках або браузерах із різними нікнеймами. Спілкуйтеся!",[3801,20599,20600,20601,20603],{},"Ви можете одночасно підключити і браузерних, і консольних клієнтів до одного сервера — вони обмінюватимуться повідомленнями в режимі реального часу, бо використовують один і той самий ",[3389,20602,7392],{}," на сервері.",[3364,20605],{},[3374,20607,20609],{"id":20608},"порівняння-консольний-net-vs-браузерний-js-клієнт","Порівняння: консольний (.NET) vs браузерний (JS) клієнт",[3624,20611,20612,20630],{},[3627,20613,20614],{},[3630,20615,20616,20619,20624],{},[3633,20617,20618],{},"Аспект",[3633,20620,20621,20623],{},[3389,20622,4799],{}," (.NET)",[3633,20625,20626,20627,20629],{},"Browser ",[3389,20628,3649],{}," (JS)",[3651,20631,20632,20645,20657,20674,20689,20707,20723,20736],{},[3630,20633,20634,20639,20642],{},[3656,20635,20636],{},[3321,20637,20638],{},"API стиль",[3656,20640,20641],{},"Async\u002Fawait, явний цикл читання",[3656,20643,20644],{},"Event-driven, колбеки",[3630,20646,20647,20651,20654],{},[3656,20648,20649],{},[3321,20650,4740],{},[3656,20652,20653],{},"Повний контроль",[3656,20655,20656],{},"Автоматично (браузер)",[3630,20658,20659,20664,20667],{},[3656,20660,20661],{},[3321,20662,20663],{},"Заголовки при Handshake",[3656,20665,20666],{},"Можна будь-які",[3656,20668,20669,20670,20673],{},"Лише ",[3389,20671,20672],{},"Sec-WebSocket-*"," + cookies",[3630,20675,20676,20680,20683],{},[3656,20677,20678],{},[3321,20679,3752],{},[3656,20681,20682],{},"Вручну (loop + delay)",[3656,20684,20685,20686,3996],{},"Вручну (новий ",[3389,20687,20688],{},"new WebSocket()",[3630,20690,20691,20696,20701],{},[3656,20692,20693],{},[3321,20694,20695],{},"Binary data",[3656,20697,20698],{},[3389,20699,20700],{},"ArraySegment\u003Cbyte>",[3656,20702,20703,12760,20705],{},[3389,20704,12704],{},[3389,20706,12701],{},[3630,20708,20709,20714,20720],{},[3656,20710,20711],{},[3321,20712,20713],{},"Доступ до фреймів",[3656,20715,20716,20717,3996],{},"Так (",[3389,20718,20719],{},"WebSocketReceiveResult",[3656,20721,20722],{},"Ні (тільки цілі повідомлення)",[3630,20724,20725,20730,20733],{},[3656,20726,20727],{},[3321,20728,20729],{},"Середовище",[3656,20731,20732],{},".NET Desktop, Server, MAUI",[3656,20734,20735],{},"Браузер, Node.js (lib)",[3630,20737,20738,20742,20745],{},[3656,20739,20740],{},[3321,20741,3787],{},[3656,20743,20744],{},"Серверні з'єднання, IoT, тести",[3656,20746,20747],{},"Веб-застосунки, SPA",[12947,20749,20750],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s8xlr, html code.shiki .s8xlr{--shiki-light:#AF00DB;--shiki-default:#C586C0;--shiki-dark:#C586C0}html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html pre.shiki code .sKtos, html code.shiki .sKtos{--shiki-light:#800000;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sLwNe, html code.shiki .sLwNe{--shiki-light:#0451A5;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}html pre.shiki code .s-QsJ, html code.shiki .s-QsJ{--shiki-light:#0070C1;--shiki-default:#4FC1FF;--shiki-dark:#4FC1FF}html pre.shiki code .s0P7L, html code.shiki .s0P7L{--shiki-light:#800000;--shiki-default:#808080;--shiki-dark:#808080}html pre.shiki code .sa4r_, html code.shiki .sa4r_{--shiki-light:#E50000;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .su9tN, html code.shiki .su9tN{--shiki-light:#0000FF;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sqdDX, html code.shiki .sqdDX{--shiki-light:#800000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}html pre.shiki code .sDUd3, html code.shiki .sDUd3{--shiki-light:#0451A5;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .se1LK, html code.shiki .se1LK{--shiki-light:#CD3131;--shiki-default:#F44747;--shiki-dark:#F44747}html pre.shiki code .shsrj, html code.shiki .shsrj{--shiki-light:#811F3F;--shiki-default:#D16969;--shiki-dark:#D16969}",{"title":3391,"searchDepth":3426,"depth":3426,"links":20752},[20753,20754,20760,20766,20770,20775,20779,20785,20786],{"id":3314,"depth":3426,"text":3315},{"id":3368,"depth":3426,"text":3369,"children":20755},[20756,20757,20758,20759],{"id":3376,"depth":3432,"text":3377},{"id":3397,"depth":3432,"text":3398},{"id":3585,"depth":3432,"text":3586},{"id":3621,"depth":3432,"text":3622},{"id":3808,"depth":3426,"text":3809,"children":20761},[20762,20763,20764,20765],{"id":3812,"depth":3432,"text":3813},{"id":4008,"depth":3432,"text":4009},{"id":4422,"depth":3432,"text":4423},{"id":4551,"depth":3432,"text":4552},{"id":4729,"depth":3426,"text":4730,"children":20767},[20768,20769],{"id":4733,"depth":3432,"text":4734},{"id":4803,"depth":3432,"text":4804},{"id":5181,"depth":3426,"text":5182,"children":20771},[20772,20773,20774],{"id":5185,"depth":3432,"text":5186},{"id":5317,"depth":3432,"text":5318},{"id":5441,"depth":3432,"text":5442},{"id":5504,"depth":3426,"text":5505,"children":20776},[20777,20778],{"id":5508,"depth":3432,"text":5509},{"id":5650,"depth":3432,"text":5651},{"id":5695,"depth":3426,"text":5696,"children":20780},[20781,20782,20783,20784],{"id":5706,"depth":3432,"text":5707},{"id":6025,"depth":3432,"text":6026},{"id":9981,"depth":3432,"text":9982},{"id":12223,"depth":3432,"text":12224},{"id":12518,"depth":3426,"text":12519},{"id":12587,"depth":3426,"text":12588,"children":20787},[20788,20789,20790,20792,20793,20794],{"id":12605,"depth":3432,"text":12606},{"id":12795,"depth":3432,"text":12796},{"id":12824,"depth":3432,"text":20791},"Повний HTML-клієнт: wwwroot\u002Findex.html",{"id":20141,"depth":3432,"text":20142},{"id":20541,"depth":3432,"text":20542},{"id":20608,"depth":3432,"text":20609},"Детальне вивчення протоколу WebSocket — відкриття з'єднання, фреймування, opcodes, ping\u002Fpong, закриття, порівняння з Long Polling та SSE; безпека WSS; практичний проєкт чату на HttpListener та ClientWebSocket без сторонніх бібліотек.","md",null,{},{"title":1696,"description":20795},"zLmaDvi6txNRfuBssKDVTX1NTqZ543K-hooVTV9EdRY",[20802,20804],{"title":1692,"path":1693,"stem":1694,"description":20803,"children":-1},"Детальне вивчення SMTP-протоколу — сесія, команди, аутентифікація, MIME, STARTTLS\u002FSMTPS; відправлення листів через System.Net.Mail у .NET 10; огляд суміжних протоколів IMAP та POP3.",{"title":1700,"path":1701,"stem":1702,"description":20805,"children":-1},"Академічне вивчення протоколу TLS — від симетричної та асиметричної криптографії до X.509 сертифікатів, PKI, TLS Handshake 1.2\u002F1.3, Record Layer, атак BEAST\u002FPOODLE\u002FHEARTBLEED та практичної реалізації в .NET через SslStream і HttpClient.",1781795350246]