Cover Image

Органайзер без единой строчки кода

Ноябрь 2, 2025 - Время чтения: 29 минуты

(и да, он будет реально работать)

Хочешь личный цифровой органайзер, который:
✅ сохраняет задачи,
✅ запоминает заметки,
✅ показывает календарь с событиями,
✅ напоминает о встречах,
✅ работает на телефоне и компьютере —
и при этом не требует подписки, облака или регистрации в десяти сервисах?

Такое возможно. И сделать его может любой — даже если ты впервые слышишь слово «localStorage».


🛠️ Как? С помощью harvi.pro — российского ИИ-ассистента для разработчиков

harvi.pro — это не просто чат. Это интеллектуальный соавтор, который пишет готовый, рабочий код по твоему описанию.
Он поддерживает вход через Google, GitHub и VK — быстро, без SMS и паспортов.

Да, сервис платный, но при регистрации тебе дарят 2 000 000 токенов — этого хватит с головой, чтобы создать полноценное приложение, протестировать его и даже доработать.

А работает harvi.pro на Claude Sonnet 4.5 — одной из самых мощных и логически последовательных моделей на сегодня. Она понимает сложные требования, не путает переменные и генерирует чистый, структурированный код — даже с календарём, модальными окнами и логикой напоминаний.

Идеально для вайб-кодера, который хочет результат — без боли.


💡 Как это сделать?

Просто вставь в harvi.pro вот такой промпт:

Создай **однофайловое HTML-приложение** — «Личный Органайзер-планировщик» — с имитацией входа и сохранением данных в `localStorage`.

**Требования:**
- Сначала экран входа: поле ввода «Твоё имя» и кнопка «Войти». После ввода — переход на главную.
- Главная страница содержит:
• Список дел (можно добавлять, отмечать как выполненное, удалять),
• Текстовую заметку (одно поле, сохраняется автоматически),
• Счётчик «Дней в потоке» (увеличивается, если приложение открывали вчера и сегодня),
- Все данные **привязаны к имени** и сохраняются между перезагрузками.
- Дизайн: профессиональный, корпоративный
- Полностью **адаптивно под мобильные** (используй `viewport`, touch-friendly кнопки).
- Всё в **одном .html-файле** (встроенные `<style>` и `<script>`).
- Код с **комментариями на русском** для новичков.
- Отображение календаря с кликабельными датами, при клике на дату открывается модальное окно, которое позволяет запланировать мероприятие: дата - время.
- Настройка, позволяющая установить время оповещения до мероприятия.

Название приложения: **«Органайзер»**

Через несколько минут ИИ выдаст полный HTML-файл — с календарём, модальными окнами, логикой напоминаний и даже счётчиком «дней в потоке».


📲 Как этим пользоваться?

  1. Скопируй код → сохрани как organizer.html
  2. Открой файл в браузере (на любом устройстве!)
  3. Введи имя → начни планировать
  4. Все данные остаются только у тебя — в браузере, без облака, без слежки.

Это твоё личное цифровое пространство. И ты создал его словами, а не строчками кода.


💫 Это и есть вайб-кодинг:

Не учить синтаксис.
Не бояться ошибок.
Просто описать идею — и получить рабочий инструмент.

Попробуй. У тебя получится.

Если не хочешь ждать, пока harvi напишет тебе код, просто скопируй код, вставь его в блокнот и сохрани как, например, органайзер.html Потом открой его в любом браузере и пользуйся.

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="mobile-web-app-capable" content="yes">
    <title>Органайзер — Личный планировщик</title>
    <style>
        /* ============================================
           ГЛОБАЛЬНЫЕ СТИЛИ И ПЕРЕМЕННЫЕ
        ============================================ */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        /* CSS переменные для цветовой схемы */
        :root {
            --primary: #2563eb;
            --primary-hover: #1d4ed8;
            --primary-light: #dbeafe;
            --success: #10b981;
            --danger: #ef4444;
            --warning: #f59e0b;
            --bg-main: #ffffff;
            --bg-secondary: #f8fafc;
            --bg-tertiary: #f1f5f9;
            --text-primary: #0f172a;
            --text-secondary: #64748b;
            --text-tertiary: #94a3b8;
            --border: #e2e8f0;
            --border-hover: #cbd5e1;
            --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
            --shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
            --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
            --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.08);
            --radius: 12px;
            --radius-sm: 8px;
            --radius-lg: 16px;
            --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            color: var(--text-primary);
            line-height: 1.5;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            overflow-x: hidden;
        }

        /* ============================================
           ЭКРАН ВХОДА
        ============================================ */
        #loginScreen {
            display: flex;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            padding: 20px;
        }

        .login-card {
            background: var(--bg-main);
            border-radius: var(--radius-lg);
            box-shadow: var(--shadow-lg);
            padding: 48px 40px;
            width: 100%;
            max-width: 420px;
            text-align: center;
        }

        .login-logo {
            width: 64px;
            height: 64px;
            background: linear-gradient(135deg, var(--primary) 0%, #7c3aed 100%);
            border-radius: var(--radius);
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 0 auto 24px;
            font-size: 32px;
            color: white;
        }

        .login-title {
            font-size: 28px;
            font-weight: 600;
            margin-bottom: 8px;
            color: var(--text-primary);
        }

        .login-subtitle {
            font-size: 15px;
            color: var(--text-secondary);
            margin-bottom: 32px;
        }

        .input-group {
            margin-bottom: 24px;
            text-align: left;
        }

        .input-label {
            display: block;
            font-size: 14px;
            font-weight: 500;
            color: var(--text-secondary);
            margin-bottom: 8px;
        }

        .input-field {
            width: 100%;
            padding: 14px 16px;
            border: 2px solid var(--border);
            border-radius: var(--radius);
            font-size: 16px;
            transition: var(--transition);
            font-family: inherit;
            background: var(--bg-main);
        }

        .input-field:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 3px var(--primary-light);
        }

        .btn-primary {
            width: 100%;
            padding: 14px 24px;
            background: var(--primary);
            color: white;
            border: none;
            border-radius: var(--radius);
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: var(--transition);
            font-family: inherit;
            box-shadow: var(--shadow-sm);
        }

        .btn-primary:hover {
            background: var(--primary-hover);
            transform: translateY(-1px);
            box-shadow: var(--shadow-md);
        }

        .btn-primary:active {
            transform: translateY(0);
        }

        /* ============================================
           ОСНОВНОЕ ПРИЛОЖЕНИЕ И СТРУКТУРА
        ============================================ */
        #appScreen {
            display: none;
            min-height: 100vh;
            padding: 20px;
        }

        .app-container {
            max-width: 1280px;
            margin: 0 auto;
        }

        /* Шапка приложения */
        .app-header {
            background: var(--bg-main);
            border-radius: var(--radius-lg);
            padding: 24px 28px;
            margin-bottom: 20px;
            box-shadow: var(--shadow);
            display: flex;
            align-items: center;
            justify-content: space-between;
            flex-wrap: wrap;
            gap: 16px;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 16px;
        }

        .header-avatar {
            width: 48px;
            height: 48px;
            background: linear-gradient(135deg, var(--primary) 0%, #7c3aed 100%);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 20px;
            font-weight: 600;
        }

        .header-info h1 {
            font-size: 20px;
            font-weight: 600;
            margin-bottom: 2px;
        }

        .header-info p {
            font-size: 14px;
            color: var(--text-secondary);
        }

        .header-right {
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .streak-badge {
            display: flex;
            align-items: center;
            gap: 8px;
            background: var(--bg-secondary);
            padding: 10px 16px;
            border-radius: var(--radius);
            font-size: 14px;
            font-weight: 600;
            color: var(--warning);
        }

        .btn-logout {
            padding: 10px 20px;
            background: var(--bg-secondary);
            color: var(--text-primary);
            border: none;
            border-radius: var(--radius);
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: var(--transition);
            font-family: inherit;
        }

        .btn-logout:hover {
            background: var(--bg-tertiary);
        }

        /* Сетка контента */
        .content-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }

        /* Карточка (универсальный контейнер) */
        .card {
            background: var(--bg-main);
            border-radius: var(--radius-lg);
            padding: 24px;
            box-shadow: var(--shadow);
        }

        .card-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 20px;
        }

        .card-title {
            font-size: 18px;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        /* ============================================
           СПИСОК ДЕЛ
        ============================================ */
        .todo-input-group {
            display: flex;
            gap: 8px;
            margin-bottom: 20px;
        }

        .todo-input {
            flex: 1;
            padding: 12px 16px;
            border: 2px solid var(--border);
            border-radius: var(--radius);
            font-size: 15px;
            transition: var(--transition);
            font-family: inherit;
        }

        .todo-input:focus {
            outline: none;
            border-color: var(--primary);
        }

        .btn-add {
            padding: 12px 24px;
            background: var(--primary);
            color: white;
            border: none;
            border-radius: var(--radius);
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: var(--transition);
            font-family: inherit;
        }

        .btn-add:hover {
            background: var(--primary-hover);
        }

        .todo-list {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .todo-item {
            display: flex;
            align-items: center;
            gap: 12px;
            padding: 14px 16px;
            background: var(--bg-secondary);
            border-radius: var(--radius);
            transition: var(--transition);
        }

        .todo-item:hover {
            background: var(--bg-tertiary);
        }

        .todo-checkbox {
            width: 20px;
            height: 20px;
            cursor: pointer;
            accent-color: var(--success);
        }

        .todo-text {
            flex: 1;
            font-size: 15px;
        }

        .todo-text.completed {
            text-decoration: line-through;
            color: var(--text-tertiary);
        }

        .btn-delete {
            padding: 6px 12px;
            background: transparent;
            color: var(--danger);
            border: none;
            border-radius: var(--radius-sm);
            font-size: 14px;
            cursor: pointer;
            transition: var(--transition);
            font-weight: 500;
        }

        .btn-delete:hover {
            background: #fee;
        }

        .empty-state {
            text-align: center;
            padding: 32px;
            color: var(--text-tertiary);
            font-size: 14px;
        }

        /* ============================================
           ЗАМЕТКА
        ============================================ */
        .note-textarea {
            width: 100%;
            min-height: 200px;
            padding: 16px;
            border: 2px solid var(--border);
            border-radius: var(--radius);
            font-size: 15px;
            font-family: inherit;
            resize: vertical;
            transition: var(--transition);
        }

        .note-textarea:focus {
            outline: none;
            border-color: var(--primary);
        }

        .note-status {
            display: flex;
            align-items: center;
            gap: 6px;
            margin-top: 8px;
            font-size: 13px;
            color: var(--text-tertiary);
        }

        /* ============================================
           КАЛЕНДАРЬ
        ============================================ */
        .calendar-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 20px;
        }

        .calendar-month {
            font-size: 16px;
            font-weight: 600;
        }

        .calendar-nav {
            display: flex;
            gap: 8px;
        }

        .btn-nav {
            width: 32px;
            height: 32px;
            background: var(--bg-secondary);
            border: none;
            border-radius: var(--radius-sm);
            cursor: pointer;
            transition: var(--transition);
            display: flex;
            align-items: center;
            justify-content: center;
            color: var(--text-primary);
        }

        .btn-nav:hover {
            background: var(--bg-tertiary);
        }

        .calendar-grid {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 8px;
        }

        .calendar-day-header {
            text-align: center;
            font-size: 12px;
            font-weight: 600;
            color: var(--text-secondary);
            padding: 8px 0;
        }

        .calendar-day {
            aspect-ratio: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: var(--radius-sm);
            font-size: 14px;
            cursor: pointer;
            transition: var(--transition);
            position: relative;
            background: var(--bg-secondary);
        }

        .calendar-day:hover {
            background: var(--bg-tertiary);
        }

        .calendar-day.today {
            background: var(--primary);
            color: white;
            font-weight: 600;
        }

        .calendar-day.other-month {
            color: var(--text-tertiary);
        }

        .calendar-day.has-event::after {
            content: '';
            position: absolute;
            bottom: 4px;
            left: 50%;
            transform: translateX(-50%);
            width: 4px;
            height: 4px;
            background: var(--warning);
            border-radius: 50%;
        }

        .calendar-day.today.has-event::after {
            background: white;
        }

        /* ============================================
           СПИСОК СОБЫТИЙ
        ============================================ */
        .events-list {
            display: flex;
            flex-direction: column;
            gap: 12px;
            max-height: 300px;
            overflow-y: auto;
        }

        .event-item {
            padding: 14px 16px;
            background: var(--bg-secondary);
            border-radius: var(--radius);
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            gap: 12px;
        }

        .event-content {
            flex: 1;
        }

        .event-title {
            font-size: 15px;
            font-weight: 600;
            margin-bottom: 4px;
        }

        .event-date {
            font-size: 13px;
            color: var(--text-secondary);
            margin-bottom: 2px;
        }

        .event-desc {
            font-size: 13px;
            color: var(--text-tertiary);
        }

        .btn-delete-event {
            padding: 6px 10px;
            background: transparent;
            color: var(--danger);
            border: none;
            border-radius: var(--radius-sm);
            font-size: 12px;
            cursor: pointer;
            transition: var(--transition);
        }

        .btn-delete-event:hover {
            background: #fee;
        }

        /* ============================================
           НАСТРОЙКИ
        ============================================ */
        .settings-group {
            margin-bottom: 20px;
        }

        .settings-label {
            display: block;
            font-size: 14px;
            font-weight: 500;
            margin-bottom: 8px;
        }

        .settings-select {
            width: 100%;
            padding: 12px 16px;
            border: 2px solid var(--border);
            border-radius: var(--radius);
            font-size: 15px;
            font-family: inherit;
            background: var(--bg-main);
            cursor: pointer;
            transition: var(--transition);
        }

        .settings-select:focus {
            outline: none;
            border-color: var(--primary);
        }

        .settings-info {
            font-size: 13px;
            color: var(--text-tertiary);
            margin-top: 6px;
        }

        /* ============================================
           МОДАЛЬНОЕ ОКНО
        ============================================ */
        .modal-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            z-index: 1000;
            align-items: center;
            justify-content: center;
            padding: 20px;
            backdrop-filter: blur(4px);
        }

        .modal-overlay.active {
            display: flex;
        }

        .modal {
            background: var(--bg-main);
            border-radius: var(--radius-lg);
            padding: 28px;
            width: 100%;
            max-width: 500px;
            box-shadow: var(--shadow-lg);
            max-height: 90vh;
            overflow-y: auto;
        }

        .modal-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 24px;
        }

        .modal-title {
            font-size: 20px;
            font-weight: 600;
        }

        .btn-close {
            width: 32px;
            height: 32px;
            background: var(--bg-secondary);
            border: none;
            border-radius: 50%;
            cursor: pointer;
            transition: var(--transition);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 20px;
            color: var(--text-secondary);
        }

        .btn-close:hover {
            background: var(--bg-tertiary);
        }

        .modal-body {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .form-group {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .form-label {
            font-size: 14px;
            font-weight: 500;
        }

        .form-input {
            padding: 12px 16px;
            border: 2px solid var(--border);
            border-radius: var(--radius);
            font-size: 15px;
            font-family: inherit;
            transition: var(--transition);
        }

        .form-input:focus {
            outline: none;
            border-color: var(--primary);
        }

        .form-textarea {
            padding: 12px 16px;
            border: 2px solid var(--border);
            border-radius: var(--radius);
            font-size: 15px;
            font-family: inherit;
            resize: vertical;
            min-height: 100px;
            transition: var(--transition);
        }

        .form-textarea:focus {
            outline: none;
            border-color: var(--primary);
        }

        .modal-footer {
            display: flex;
            gap: 12px;
            margin-top: 24px;
        }

        .btn-secondary {
            flex: 1;
            padding: 12px 24px;
            background: var(--bg-secondary);
            color: var(--text-primary);
            border: none;
            border-radius: var(--radius);
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: var(--transition);
            font-family: inherit;
        }

        .btn-secondary:hover {
            background: var(--bg-tertiary);
        }

        .btn-submit {
            flex: 1;
            padding: 12px 24px;
            background: var(--primary);
            color: white;
            border: none;
            border-radius: var(--radius);
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: var(--transition);
            font-family: inherit;
        }

        .btn-submit:hover {
            background: var(--primary-hover);
        }

        /* ============================================
           АДАПТИВНОСТЬ
        ============================================ */
        @media (max-width: 768px) {
            .content-grid {
                grid-template-columns: 1fr;
            }

            .app-header {
                padding: 20px;
            }

            .header-left {
                width: 100%;
            }

            .header-right {
                width: 100%;
                justify-content: space-between;
            }

            .login-card {
                padding: 36px 28px;
            }

            .card {
                padding: 20px;
            }

            .calendar-day {
                font-size: 13px;
            }

            .calendar-day-header {
                font-size: 11px;
            }
        }

        @media (max-width: 480px) {
            body {
                font-size: 15px;
            }

            .app-header {
                padding: 16px;
            }

            .header-info h1 {
                font-size: 18px;
            }

            .card {
                padding: 16px;
            }

            .calendar-grid {
                gap: 4px;
            }

            .modal {
                padding: 24px;
            }
        }

        /* Скролл */
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        ::-webkit-scrollbar-track {
            background: var(--bg-secondary);
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb {
            background: var(--border-hover);
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: var(--text-tertiary);
        }
    </style>
</head>
<body>
    <!-- ============================================
         ЭКРАН ВХОДА
    ============================================ -->
    <div id="loginScreen">
        <div class="login-card">
            <div class="login-logo">📋</div>
            <h1 class="login-title">Органайзер</h1>
            <p class="login-subtitle">Ваш личный планировщик задач и событий</p>

            <div class="input-group">
                <label class="input-label" for="nameInput">Твоё имя</label>
                <input
                    type="text"
                    id="nameInput"
                    class="input-field"
                    placeholder="Введите ваше имя"
                    autocomplete="off"
                >
            </div>

            <button class="btn-primary" onclick="login()">Войти</button>
        </div>
    </div>

    <!-- ============================================
         ОСНОВНОЕ ПРИЛОЖЕНИЕ
    ============================================ -->
    <div id="appScreen">
        <div class="app-container">
            <!-- Шапка -->
            <header class="app-header">
                <div class="header-left">
                    <div class="header-avatar" id="userAvatar"></div>
                    <div class="header-info">
                        <h1>Добро пожаловать, <span id="userName"></span>!</h1>
                        <p id="currentDate"></p>
                    </div>
                </div>
                <div class="header-right">
                    <div class="streak-badge">
                        <span>🔥</span>
                        <span id="streakCount">0</span>
                        <span>дней</span>
                    </div>
                    <button class="btn-logout" onclick="logout()">Выйти</button>
                </div>
            </header>

            <!-- Основной контент -->
            <div class="content-grid">
                <!-- Список дел -->
                <div class="card">
                    <div class="card-header">
                        <h2 class="card-title">
                            <span>✓</span>
                            Список дел
                        </h2>
                    </div>

                    <div class="todo-input-group">
                        <input
                            type="text"
                            id="todoInput"
                            class="todo-input"
                            placeholder="Новая задача..."
                            onkeypress="if(event.key === 'Enter') addTodo()"
                        >
                        <button class="btn-add" onclick="addTodo()">Добавить</button>
                    </div>

                    <div class="todo-list" id="todoList">
                        <div class="empty-state">Пока нет задач. Добавьте первую!</div>
                    </div>
                </div>

                <!-- Заметка -->
                <div class="card">
                    <div class="card-header">
                        <h2 class="card-title">
                            <span>📝</span>
                            Заметка
                        </h2>
                    </div>

                    <textarea
                        id="noteText"
                        class="note-textarea"
                        placeholder="Начните писать..."
                        oninput="saveNote()"
                    ></textarea>

                    <div class="note-status">
                        <span>💾</span>
                        <span>Автосохранение</span>
                    </div>
                </div>

                <!-- Календарь -->
                <div class="card">
                    <div class="card-header">
                        <h2 class="card-title">
                            <span>📅</span>
                            Календарь
                        </h2>
                    </div>

                    <div class="calendar-header">
                        <div class="calendar-month" id="calendarMonth"></div>
                        <div class="calendar-nav">
                            <button class="btn-nav" onclick="prevMonth()">‹</button>
                            <button class="btn-nav" onclick="nextMonth()">›</button>
                        </div>
                    </div>

                    <div class="calendar-grid" id="calendarGrid"></div>
                </div>

                <!-- Ближайшие события -->
                <div class="card">
                    <div class="card-header">
                        <h2 class="card-title">
                            <span>⏰</span>
                            Ближайшие события
                        </h2>
                    </div>

                    <div class="events-list" id="eventsList">
                        <div class="empty-state">Нет запланированных событий</div>
                    </div>
                </div>

                <!-- Настройки уведомлений -->
                <div class="card" style="grid-column: 1 / -1;">
                    <div class="card-header">
                        <h2 class="card-title">
                            <span>⚙️</span>
                            Настройки уведомлений
                        </h2>
                    </div>

                    <div class="settings-group">
                        <label class="settings-label" for="notificationTime">
                            Напомнить за:
                        </label>
                        <select id="notificationTime" class="settings-select" onchange="saveNotificationSettings()">
                            <option value="0">В момент события</option>
                            <option value="5">5 минут до</option>
                            <option value="10">10 минут до</option>
                            <option value="15" selected>15 минут до</option>
                            <option value="30">30 минут до</option>
                            <option value="60">1 час до</option>
                        </select>
                        <p class="settings-info">
                            ℹ️ Уведомления работают только при открытом приложении
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- ============================================
         МОДАЛЬНОЕ ОКНО ДЛЯ СОБЫТИЙ
    ============================================ -->
    <div class="modal-overlay" id="eventModal">
        <div class="modal">
            <div class="modal-header">
                <h2 class="modal-title">Новое событие</h2>
                <button class="btn-close" onclick="closeEventModal()">×</button>
            </div>

            <div class="modal-body">
                <div class="form-group">
                    <label class="form-label" for="eventTitle">Название события</label>
                    <input
                        type="text"
                        id="eventTitle"
                        class="form-input"
                        placeholder="Встреча, день рождения..."
                    >
                </div>

                <div class="form-group">
                    <label class="form-label" for="eventDate">Дата</label>
                    <input
                        type="date"
                        id="eventDate"
                        class="form-input"
                    >
                </div>

                <div class="form-group">
                    <label class="form-label" for="eventTime">Время</label>
                    <input
                        type="time"
                        id="eventTime"
                        class="form-input"
                    >
                </div>

                <div class="form-group">
                    <label class="form-label" for="eventDesc">Описание (опционально)</label>
                    <textarea
                        id="eventDesc"
                        class="form-textarea"
                        placeholder="Добавьте детали..."
                    ></textarea>
                </div>
            </div>

            <div class="modal-footer">
                <button class="btn-secondary" onclick="closeEventModal()">Отмена</button>
                <button class="btn-submit" onclick="saveEvent()">Сохранить</button>
            </div>
        </div>
    </div>

    <!-- ============================================
         JAVASCRIPT ЛОГИКА ПРИЛОЖЕНИЯ
    ============================================ -->
    <script>
        // ========================================
        // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ И КОНСТАНТЫ
        // ========================================

        let currentUser = null; // Текущий пользователь
        let currentDate = new Date(); // Дата для календаря
        let selectedDate = null; // Выбранная дата для события

        // ========================================
        // ИНИЦИАЛИЗАЦИЯ ПРИЛОЖЕНИЯ
        // ========================================

        // Запускается при загрузке страницы
        window.addEventListener('DOMContentLoaded', () => {
            // Проверяем, есть ли сохранённый пользователь
            const savedUser = localStorage.getItem('currentUser');
            if (savedUser) {
                currentUser = savedUser;
                showApp();
            }

            // Запрашиваем разрешение на уведомления
            if ('Notification' in window && Notification.permission === 'default') {
                Notification.requestPermission();
            }

            // Запускаем проверку событий каждую минуту
            setInterval(checkUpcomingEvents, 60000);
        });

        // ========================================
        // ВХОД И ВЫХОД
        // ========================================

        // Функция входа в систему
        function login() {
            const nameInput = document.getElementById('nameInput');
            const name = nameInput.value.trim();

            if (!name) {
                alert('Пожалуйста, введите ваше имя');
                return;
            }

            currentUser = name;
            localStorage.setItem('currentUser', name);

            // Обновляем счётчик посещений
            updateStreak();

            showApp();
        }

        // Функция выхода из системы
        function logout() {
            if (confirm('Вы уверены, что хотите выйти?')) {
                currentUser = null;
                localStorage.removeItem('currentUser');

                document.getElementById('appScreen').style.display = 'none';
                document.getElementById('loginScreen').style.display = 'flex';
                document.getElementById('nameInput').value = '';
            }
        }

        // Показать главный экран приложения
        function showApp() {
            document.getElementById('loginScreen').style.display = 'none';
            document.getElementById('appScreen').style.display = 'block';

            // Инициализируем интерфейс
            updateUserInfo();
            updateCurrentDate();
            loadTodos();
            loadNote();
            loadNotificationSettings();
            renderCalendar();
            loadEvents();
            checkUpcomingEvents();
        }

        // ========================================
        // СЧЁТЧИК "ДНЕЙ В ПОТОКЕ"
        // ========================================

        function updateStreak() {
            const key = `streak_${currentUser}`;
            const today = new Date().toDateString();
            const streakData = JSON.parse(localStorage.getItem(key) || '{"count": 0, "lastVisit": null}');

            if (streakData.lastVisit === today) {
                // Уже посещали сегодня
                return;
            }

            const yesterday = new Date();
            yesterday.setDate(yesterday.getDate() - 1);

            if (streakData.lastVisit === yesterday.toDateString()) {
                // Посещали вчера — увеличиваем счётчик
                streakData.count++;
            } else if (streakData.lastVisit !== null) {
                // Был пропуск — сбрасываем
                streakData.count = 1;
            } else {
                // Первое посещение
                streakData.count = 1;
            }

            streakData.lastVisit = today;
            localStorage.setItem(key, JSON.stringify(streakData));

            // Обновляем UI
            document.getElementById('streakCount').textContent = streakData.count;
        }

        // ========================================
        // ОБНОВЛЕНИЕ ИНФОРМАЦИИ О ПОЛЬЗОВАТЕЛЕ
        // ========================================

        function updateUserInfo() {
            const userName = document.getElementById('userName');
            const userAvatar = document.getElementById('userAvatar');

            userName.textContent = currentUser;
            userAvatar.textContent = currentUser.charAt(0).toUpperCase();

            // Загружаем счётчик
            const key = `streak_${currentUser}`;
            const streakData = JSON.parse(localStorage.getItem(key) || '{"count": 0}');
            document.getElementById('streakCount').textContent = streakData.count;
        }

        function updateCurrentDate() {
            const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
            const dateString = new Date().toLocaleDateString('ru-RU', options);
            document.getElementById('currentDate').textContent = dateString;
        }

        // ========================================
        // СПИСОК ДЕЛ
        // ========================================

        function getTodosKey() {
            return `todos_${currentUser}`;
        }

        function loadTodos() {
            const todos = JSON.parse(localStorage.getItem(getTodosKey()) || '[]');
            renderTodos(todos);
        }

        function renderTodos(todos) {
            const todoList = document.getElementById('todoList');

            if (todos.length === 0) {
                todoList.innerHTML = '<div class="empty-state">Пока нет задач. Добавьте первую!</div>';
                return;
            }

            todoList.innerHTML = todos.map((todo, index) => `
                <div class="todo-item">
                    <input
                        type="checkbox"
                        class="todo-checkbox"
                        ${todo.completed ? 'checked' : ''}
                        onchange="toggleTodo(${index})"
                    >
                    <span class="todo-text ${todo.completed ? 'completed' : ''}">${escapeHtml(todo.text)}</span>
                    <button class="btn-delete" onclick="deleteTodo(${index})">Удалить</button>
                </div>
            `).join('');
        }

        function addTodo() {
            const input = document.getElementById('todoInput');
            const text = input.value.trim();

            if (!text) return;

            const todos = JSON.parse(localStorage.getItem(getTodosKey()) || '[]');
            todos.push({ text, completed: false });

            localStorage.setItem(getTodosKey(), JSON.stringify(todos));
            input.value = '';

            renderTodos(todos);
        }

        function toggleTodo(index) {
            const todos = JSON.parse(localStorage.getItem(getTodosKey()) || '[]');
            todos[index].completed = !todos[index].completed;

            localStorage.setItem(getTodosKey(), JSON.stringify(todos));
            renderTodos(todos);
        }

        function deleteTodo(index) {
            const todos = JSON.parse(localStorage.getItem(getTodosKey()) || '[]');
            todos.splice(index, 1);

            localStorage.setItem(getTodosKey(), JSON.stringify(todos));
            renderTodos(todos);
        }

        // ========================================
        // ЗАМЕТКА
        // ========================================

        function getNoteKey() {
            return `note_${currentUser}`;
        }

        function loadNote() {
            const note = localStorage.getItem(getNoteKey()) || '';
            document.getElementById('noteText').value = note;
        }

        function saveNote() {
            const text = document.getElementById('noteText').value;
            localStorage.setItem(getNoteKey(), text);
        }

        // ========================================
        // КАЛЕНДАРЬ
        // ========================================

        function renderCalendar() {
            const grid = document.getElementById('calendarGrid');
            const monthTitle = document.getElementById('calendarMonth');

            const year = currentDate.getFullYear();
            const month = currentDate.getMonth();

            // Название месяца
            const monthName = currentDate.toLocaleDateString('ru-RU', { month: 'long', year: 'numeric' });
            monthTitle.textContent = monthName.charAt(0).toUpperCase() + monthName.slice(1);

            // Первый день месяца
            const firstDay = new Date(year, month, 1);
            const lastDay = new Date(year, month + 1, 0);

            // Смещение (день недели первого числа: 0 = вс, 1 = пн, ...)
            let startDay = firstDay.getDay();
            startDay = startDay === 0 ? 6 : startDay - 1; // Преобразуем: пн = 0

            // Дни предыдущего месяца
            const prevMonthDays = new Date(year, month, 0).getDate();

            // Получаем события для отображения индикатора
            const events = getEvents();

            // Формируем HTML
            let html = '';

            // Заголовки дней недели
            const dayNames = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
            dayNames.forEach(name => {
                html += `<div class="calendar-day-header">${name}</div>`;
            });

            // Дни предыдущего месяца
            for (let i = startDay - 1; i >= 0; i--) {
                const day = prevMonthDays - i;
                html += `<div class="calendar-day other-month">${day}</div>`;
            }

            // Дни текущего месяца
            const today = new Date();
            for (let day = 1; day <= lastDay.getDate(); day++) {
                const date = new Date(year, month, day);
                const dateStr = date.toISOString().split('T')[0];

                const isToday = date.toDateString() === today.toDateString();
                const hasEvent = events.some(e => e.date === dateStr);

                let classes = 'calendar-day';
                if (isToday) classes += ' today';
                if (hasEvent) classes += ' has-event';

                html += `<div class="${classes}" onclick="openEventModal('${dateStr}')">${day}</div>`;
            }

            // Дни следующего месяца
            const remainingCells = 42 - (startDay + lastDay.getDate());
            for (let day = 1; day <= remainingCells; day++) {
                html += `<div class="calendar-day other-month">${day}</div>`;
            }

            grid.innerHTML = html;
        }

        function prevMonth() {
            currentDate.setMonth(currentDate.getMonth() - 1);
            renderCalendar();
        }

        function nextMonth() {
            currentDate.setMonth(currentDate.getMonth() + 1);
            renderCalendar();
        }

        // ========================================
        // СОБЫТИЯ
        // ========================================

        function getEventsKey() {
            return `events_${currentUser}`;
        }

        function getEvents() {
            return JSON.parse(localStorage.getItem(getEventsKey()) || '[]');
        }

        function openEventModal(dateStr) {
            selectedDate = dateStr;
            document.getElementById('eventDate').value = dateStr;
            document.getElementById('eventModal').classList.add('active');
        }

        function closeEventModal() {
            document.getElementById('eventModal').classList.remove('active');
            document.getElementById('eventTitle').value = '';
            document.getElementById('eventDate').value = '';
            document.getElementById('eventTime').value = '';
            document.getElementById('eventDesc').value = '';
            selectedDate = null;
        }

        function saveEvent() {
            const title = document.getElementById('eventTitle').value.trim();
            const date = document.getElementById('eventDate').value;
            const time = document.getElementById('eventTime').value;
            const desc = document.getElementById('eventDesc').value.trim();

            if (!title || !date || !time) {
                alert('Пожалуйста, заполните название, дату и время события');
                return;
            }

            const events = getEvents();
            events.push({
                id: Date.now(),
                title,
                date,
                time,
                desc
            });

            localStorage.setItem(getEventsKey(), JSON.stringify(events));

            closeEventModal();
            renderCalendar();
            loadEvents();
        }

        function loadEvents() {
            const events = getEvents();
            const eventsList = document.getElementById('eventsList');

            // Сортируем по дате и времени
            events.sort((a, b) => {
                const dateA = new Date(`${a.date}T${a.time}`);
                const dateB = new Date(`${b.date}T${b.time}`);
                return dateA - dateB;
            });

            // Показываем только будущие события
            const now = new Date();
            const upcoming = events.filter(e => {
                const eventDate = new Date(`${e.date}T${e.time}`);
                return eventDate >= now;
            });

            if (upcoming.length === 0) {
                eventsList.innerHTML = '<div class="empty-state">Нет запланированных событий</div>';
                return;
            }

            eventsList.innerHTML = upcoming.map(event => {
                const eventDate = new Date(`${event.date}T${event.time}`);
                const dateStr = eventDate.toLocaleDateString('ru-RU', {
                    day: 'numeric',
                    month: 'long',
                    hour: '2-digit',
                    minute: '2-digit'
                });

                return `
                    <div class="event-item">
                        <div class="event-content">
                            <div class="event-title">${escapeHtml(event.title)}</div>
                            <div class="event-date">${dateStr}</div>
                            ${event.desc ? `<div class="event-desc">${escapeHtml(event.desc)}</div>` : ''}
                        </div>
                        <button class="btn-delete-event" onclick="deleteEvent(${event.id})">×</button>
                    </div>
                `;
            }).join('');
        }

        function deleteEvent(id) {
            if (!confirm('Удалить это событие?')) return;

            const events = getEvents();
            const filtered = events.filter(e => e.id !== id);

            localStorage.setItem(getEventsKey(), JSON.stringify(filtered));
            renderCalendar();
            loadEvents();
        }

        // ========================================
        // УВЕДОМЛЕНИЯ
        // ========================================

        function saveNotificationSettings() {
            const minutes = document.getElementById('notificationTime').value;
            localStorage.setItem(`notifications_${currentUser}`, minutes);
        }

        function loadNotificationSettings() {
            const minutes = localStorage.getItem(`notifications_${currentUser}`) || '15';
            document.getElementById('notificationTime').value = minutes;
        }

        function checkUpcomingEvents() {
            if ('Notification' in window && Notification.permission !== 'granted') {
                return;
            }

            const events = getEvents();
            const notificationMinutes = parseInt(localStorage.getItem(`notifications_${currentUser}`) || '15');
            const now = new Date();

            events.forEach(event => {
                const eventDate = new Date(`${event.date}T${event.time}`);
                const diff = eventDate - now;
                const diffMinutes = Math.floor(diff / 60000);

                // Проверяем, нужно ли показать уведомление
                if (diffMinutes === notificationMinutes && diffMinutes > 0) {
                    showNotification(event, notificationMinutes);
                }
            });
        }

        function showNotification(event, minutes) {
            if ('Notification' in window && Notification.permission === 'granted') {
                const title = `Напоминание: ${event.title}`;
                const body = `Начало через ${minutes} минут`;

                new Notification(title, {
                    body: body,
                    icon: '📅',
                    requireInteraction: true
                });
            }
        }

        // ========================================
        // УТИЛИТЫ
        // ========================================

        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
    </script>
</body>
</html>