From b53aa305e8bf57142c91ed4948b6c57c47f372a3 Mon Sep 17 00:00:00 2001 From: greyjoy Date: Fri, 31 Jul 2026 00:49:40 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 7 + config/__init__.py | 1 + config/asgi.py | 9 + config/settings.py | 69 +++++++ config/urls.py | 14 ++ config/wsgi.py | 9 + consultations/__init__.py | 1 + consultations/admin.py | 22 ++ consultations/apps.py | 8 + consultations/forms.py | 47 +++++ consultations/migrations/0001_initial.py | 86 ++++++++ consultations/migrations/__init__.py | 1 + consultations/models.py | 42 ++++ .../templates/consultations/home.html | 194 ++++++++++++++++++ consultations/urls.py | 11 + consultations/views.py | 21 ++ main.py | 16 ++ manage.py | 21 ++ requirements.txt | 2 + static/css/style.css | 172 ++++++++++++++++ 20 files changed, 753 insertions(+) create mode 100644 .gitignore create mode 100644 config/__init__.py create mode 100644 config/asgi.py create mode 100644 config/settings.py create mode 100644 config/urls.py create mode 100644 config/wsgi.py create mode 100644 consultations/__init__.py create mode 100644 consultations/admin.py create mode 100644 consultations/apps.py create mode 100644 consultations/forms.py create mode 100644 consultations/migrations/0001_initial.py create mode 100644 consultations/migrations/__init__.py create mode 100644 consultations/models.py create mode 100644 consultations/templates/consultations/home.html create mode 100644 consultations/urls.py create mode 100644 consultations/views.py create mode 100644 main.py create mode 100644 manage.py create mode 100644 requirements.txt create mode 100644 static/css/style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add9661 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.venv/ +__pycache__/ +*.py[cod] +db.sqlite3 +.idea/ +.env + diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ + diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..2ce97ca --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,9 @@ +"""ASGI configuration for the teacher site.""" +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_asgi_application() + diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..6848d2e --- /dev/null +++ b/config/settings.py @@ -0,0 +1,69 @@ +"""Development settings for the teacher consultation website.""" +from pathlib import Path + + +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRET_KEY = "django-insecure-change-this-key-before-production" +DEBUG = True +ALLOWED_HOSTS = [] + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "consultations", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "config.wsgi.application" +ASGI_APPLICATION = "config.asgi.application" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + +AUTH_PASSWORD_VALIDATORS = [] + +LANGUAGE_CODE = "ru-ru" +TIME_ZONE = "Europe/Astrakhan" +USE_I18N = True +USE_TZ = True + +STATIC_URL = "static/" +STATICFILES_DIRS = [BASE_DIR / "static"] + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..30fabfc --- /dev/null +++ b/config/urls.py @@ -0,0 +1,14 @@ +"""URL configuration for the teacher site.""" +from django.contrib import admin +from django.urls import include, path + + +urlpatterns = [ + path("admin/", admin.site.urls), + path("", include("consultations.urls")), +] + +admin.site.site_header = "Записи на консультации" +admin.site.site_title = "Консультации" +admin.site.index_title = "Заявки учеников" + diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..8dc559c --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,9 @@ +"""WSGI configuration for the teacher site.""" +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_wsgi_application() + diff --git a/consultations/__init__.py b/consultations/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/consultations/__init__.py @@ -0,0 +1 @@ + diff --git a/consultations/admin.py b/consultations/admin.py new file mode 100644 index 0000000..fe36dc8 --- /dev/null +++ b/consultations/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin + +from .models import ConsultationRequest + + +@admin.register(ConsultationRequest) +class ConsultationRequestAdmin(admin.ModelAdmin): + list_display = ( + "name", + "subject", + "exam", + "preferred_date", + "preferred_time", + "phone", + "is_processed", + ) + list_filter = ("subject", "exam", "is_processed", "preferred_date") + search_fields = ("name", "phone", "contact", "comment") + list_editable = ("is_processed",) + readonly_fields = ("created_at",) + date_hierarchy = "preferred_date" + diff --git a/consultations/apps.py b/consultations/apps.py new file mode 100644 index 0000000..5516537 --- /dev/null +++ b/consultations/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class ConsultationsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "consultations" + verbose_name = "Консультации" + diff --git a/consultations/forms.py b/consultations/forms.py new file mode 100644 index 0000000..2599c96 --- /dev/null +++ b/consultations/forms.py @@ -0,0 +1,47 @@ +from django import forms +from django.utils import timezone + +from .models import ConsultationRequest + + +class ConsultationRequestForm(forms.ModelForm): + class Meta: + model = ConsultationRequest + fields = ( + "name", + "phone", + "contact", + "subject", + "exam", + "preferred_date", + "preferred_time", + "comment", + ) + widgets = { + "name": forms.TextInput(attrs={"placeholder": "Как к вам обращаться"}), + "phone": forms.TelInput(attrs={"placeholder": "+7 (999) 123-45-67"}), + "contact": forms.TextInput(attrs={"placeholder": "@username, WhatsApp и т. п."}), + "preferred_date": forms.DateInput(attrs={"type": "date"}), + "preferred_time": forms.TimeInput(attrs={"type": "time"}), + "comment": forms.Textarea( + attrs={ + "placeholder": "Например: текущий класс, темы, которые вызывают сложности", + "rows": 4, + } + ), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields.values(): + field.widget.attrs["class"] = "form-control" + self.fields["preferred_date"].widget.attrs["min"] = timezone.localdate().isoformat() + self.fields["contact"].required = False + self.fields["comment"].required = False + + def clean_preferred_date(self): + preferred_date = self.cleaned_data["preferred_date"] + if preferred_date < timezone.localdate(): + raise forms.ValidationError("Выберите сегодняшнюю дату или более позднюю.") + return preferred_date + diff --git a/consultations/migrations/0001_initial.py b/consultations/migrations/0001_initial.py new file mode 100644 index 0000000..bb7fa0d --- /dev/null +++ b/consultations/migrations/0001_initial.py @@ -0,0 +1,86 @@ +# Generated by Django 5.0 on 2026-07-30 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="ConsultationRequest", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100, verbose_name="Имя ученика")), + ( + "phone", + models.CharField( + max_length=30, + validators=[ + django.core.validators.RegexValidator( + message="Введите телефон в привычном формате.", + regex="^[0-9+()\\-\\s]{7,30}$", + ) + ], + verbose_name="Телефон", + ), + ), + ( + "contact", + models.CharField( + blank=True, + max_length=100, + verbose_name="Telegram или другой способ связи", + ), + ), + ( + "subject", + models.CharField( + choices=[("physics", "Физика"), ("mathematics", "Математика")], + max_length=20, + verbose_name="Предмет", + ), + ), + ( + "exam", + models.CharField( + choices=[ + ("oge", "ОГЭ"), + ("ege", "ЕГЭ"), + ("other", "Другая консультация"), + ], + max_length=20, + verbose_name="Цель подготовки", + ), + ), + ("preferred_date", models.DateField(verbose_name="Желаемая дата")), + ("preferred_time", models.TimeField(verbose_name="Желаемое время")), + ( + "comment", + models.TextField(blank=True, max_length=1000, verbose_name="Комментарий"), + ), + ("created_at", models.DateTimeField(auto_now_add=True, verbose_name="Создана")), + ( + "is_processed", + models.BooleanField(default=False, verbose_name="Заявка обработана"), + ), + ], + options={ + "verbose_name": "Заявка на консультацию", + "verbose_name_plural": "Заявки на консультации", + "ordering": ("is_processed", "preferred_date", "preferred_time"), + }, + ), + ] + diff --git a/consultations/migrations/__init__.py b/consultations/migrations/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/consultations/migrations/__init__.py @@ -0,0 +1 @@ + diff --git a/consultations/models.py b/consultations/models.py new file mode 100644 index 0000000..338cdaf --- /dev/null +++ b/consultations/models.py @@ -0,0 +1,42 @@ +from django.core.validators import RegexValidator +from django.db import models + + +class ConsultationRequest(models.Model): + class Subject(models.TextChoices): + PHYSICS = "physics", "Физика" + MATHEMATICS = "mathematics", "Математика" + + class Exam(models.TextChoices): + OGE = "oge", "ОГЭ" + EGE = "ege", "ЕГЭ" + OTHER = "other", "Другая консультация" + + name = models.CharField("Имя ученика", max_length=100) + phone = models.CharField( + "Телефон", + max_length=30, + validators=[ + RegexValidator( + regex=r"^[0-9+()\-\s]{7,30}$", + message="Введите телефон в привычном формате.", + ) + ], + ) + contact = models.CharField("Telegram или другой способ связи", max_length=100, blank=True) + subject = models.CharField("Предмет", max_length=20, choices=Subject.choices) + exam = models.CharField("Цель подготовки", max_length=20, choices=Exam.choices) + preferred_date = models.DateField("Желаемая дата") + preferred_time = models.TimeField("Желаемое время") + comment = models.TextField("Комментарий", max_length=1000, blank=True) + created_at = models.DateTimeField("Создана", auto_now_add=True) + is_processed = models.BooleanField("Заявка обработана", default=False) + + class Meta: + verbose_name = "Заявка на консультацию" + verbose_name_plural = "Заявки на консультации" + ordering = ("is_processed", "preferred_date", "preferred_time") + + def __str__(self): + return f"{self.name}: {self.get_subject_display()} — {self.preferred_date:%d.%m.%Y}" + diff --git a/consultations/templates/consultations/home.html b/consultations/templates/consultations/home.html new file mode 100644 index 0000000..f81ae64 --- /dev/null +++ b/consultations/templates/consultations/home.html @@ -0,0 +1,194 @@ +{% load static %} + + + + + + + Физика и математика — Игорь Олегович Безрукавов + + + + + + + + +
+
+
+
+

Физика и математика · 5–11 классы

+

Готовимся к экзаменам с пониманием, а не наугад.

+

Индивидуальные консультации по подготовке к ОГЭ и ЕГЭ: разбираем сложные темы, выстраиваем логику решения и спокойно идём к цели.

+ +
+
10+лет опыта
+
2предмета
+
ОГЭ · ЕГЭподготовка к экзаменам
+
+
+
+
+
+ E = mc² + √x + Σ +
+

точные науки

+ понятно
и спокойно
+
+
+
+
+ +
+
+

О преподавателе

+
+

Игорь Олегович
Безрукавов

+

Преподаватель физики и математики с опытом преподавания и репетиторства более 10 лет.

+

Окончил Астраханский государственный университет: бакалавриат и магистратуру по специальности «Преподавание физики и математики».

+

На консультации важно не просто получить ответ, а разобраться, почему решение работает — тогда знания остаются с учеником и на контрольной, и на экзамене.

+
+
+
+ +
+
+
+
+

Направления

+

Подготовка в нужном темпе

+
+

Подберём фокус консультации под текущий уровень, цели и дату экзамена.

+
+
+
+ F +

Предмет

+

Физика

+
    +
  • Механика, термодинамика, электродинамика
  • +
  • Задачи с формулами и графиками
  • +
  • Подготовка к ОГЭ и ЕГЭ
  • +
+ Записаться +
+
+ π +

Предмет

+

Математика

+
    +
  • Алгебра, геометрия, вероятность
  • +
  • Уравнения, неравенства, задачи
  • +
  • Подготовка к ОГЭ и ЕГЭ
  • +
+ Записаться +
+
+
+
+ +
+
+

Как это работает

+

Три простых шага

+
+
01

Оставьте заявку

Выберите предмет, экзамен и удобное время.

+
02

Подтвердим время

Игорь Олегович свяжется с вами по указанным контактам.

+
03

Начнём подготовку

Определим цель и разберём первые задачи.

+
+
+
+ +
+
+
+

Запись на консультацию

+

Сделайте первый шаг к уверенному экзамену.

+

Заполните форму — время будет окончательно подтверждено после связи с преподавателем.

+

Контакты используются только для ответа на вашу заявку.

+
+
+ {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% if form.non_field_errors %} +
{{ form.non_field_errors }}
+ {% endif %} +
+ {% csrf_token %} +
+
+ + {{ form.name }} + {% for error in form.name.errors %}{{ error }}{% endfor %} +
+
+ + {{ form.phone }} + {% for error in form.phone.errors %}{{ error }}{% endfor %} +
+
+ + {{ form.contact }} + {% for error in form.contact.errors %}{{ error }}{% endfor %} +
+
+ + {{ form.subject }} + {% for error in form.subject.errors %}{{ error }}{% endfor %} +
+
+ + {{ form.exam }} + {% for error in form.exam.errors %}{{ error }}{% endfor %} +
+
+ + {{ form.preferred_date }} + {% for error in form.preferred_date.errors %}{{ error }}{% endfor %} +
+
+ + {{ form.preferred_time }} + {% for error in form.preferred_time.errors %}{{ error }}{% endfor %} +
+
+ + {{ form.comment }} + {% for error in form.comment.errors %}{{ error }}{% endfor %} +
+
+ +
+
+
+
+
+ + + + diff --git a/consultations/urls.py b/consultations/urls.py new file mode 100644 index 0000000..c2038b0 --- /dev/null +++ b/consultations/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + + +app_name = "consultations" + +urlpatterns = [ + path("", views.home, name="home"), +] + diff --git a/consultations/views.py b/consultations/views.py new file mode 100644 index 0000000..90ba748 --- /dev/null +++ b/consultations/views.py @@ -0,0 +1,21 @@ +from django.contrib import messages +from django.shortcuts import redirect, render +from django.urls import reverse + +from .forms import ConsultationRequestForm + + +def home(request): + if request.method == "POST": + form = ConsultationRequestForm(request.POST) + if form.is_valid(): + form.save() + messages.success( + request, + "Заявка отправлена. Игорь Олегович свяжется с вами, чтобы подтвердить время консультации.", + ) + return redirect(f"{reverse('consultations:home')}#booking") + else: + form = ConsultationRequestForm() + + return render(request, "consultations/home.html", {"form": form}) diff --git a/main.py b/main.py new file mode 100644 index 0000000..5596b44 --- /dev/null +++ b/main.py @@ -0,0 +1,16 @@ +# This is a sample Python script. + +# Press Shift+F10 to execute it or replace it with your code. +# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. + + +def print_hi(name): + # Use a breakpoint in the code line below to debug your script. + print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. + + +# Press the green button in the gutter to run the script. +if __name__ == '__main__': + print_hi('PyCharm') + +# See PyCharm help at https://www.jetbrains.com/help/pycharm/ diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..9ea4563 --- /dev/null +++ b/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative commands.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Не удалось импортировать Django. Установите зависимости из requirements.txt." + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ca6c5c5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django>=5.0,<6.0 + diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..16774a3 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,172 @@ +:root { + --ink: #12212d; + --muted: #5f6a72; + --paper: #f8f8f3; + --sand: #eeece3; + --blue: #195d78; + --blue-dark: #10475f; + --orange: #e86d3e; + --line: #d8ddd6; + --white: #ffffff; +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + margin: 0; + color: var(--ink); + background: var(--paper); + font-family: "Manrope", Arial, sans-serif; + font-size: 16px; + line-height: 1.6; +} + +a { color: inherit; text-decoration: none; } + +.container { width: min(1120px, calc(100% - 48px)); margin: 0 auto; } + +.site-header { background: rgba(248, 248, 243, .93); border-bottom: 1px solid rgba(18, 33, 45, .08); position: sticky; top: 0; z-index: 10; backdrop-filter: blur(12px); } + +.navigation { min-height: 76px; display: flex; align-items: center; justify-content: space-between; gap: 28px; } + +.brand { color: var(--ink); font-size: 20px; font-weight: 800; letter-spacing: -.07em; } +.brand span { color: var(--orange); padding: 0 2px; } + +.nav-links { display: flex; gap: 28px; font-size: 14px; font-weight: 600; color: #43515b; } +.nav-links a:hover, .text-link:hover, .subject-card a:hover { color: var(--orange); } + +.nav-button { padding: 9px 17px; color: var(--blue); border: 1px solid var(--blue); border-radius: 999px; font-size: 13px; font-weight: 700; } +.nav-button:hover { background: var(--blue); color: var(--white); } + +.hero { overflow: hidden; padding: 84px 0 74px; background: linear-gradient(115deg, #e8f1ee 0%, #f8f8f3 60%, #f8eee5 100%); } +.hero-grid { display: grid; grid-template-columns: 1.22fr .78fr; align-items: center; gap: 76px; } + +.eyebrow, .section-kicker, .card-kicker { margin: 0 0 14px; color: var(--orange); font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; } +h1, h2, h3, p { margin-top: 0; } +h1, h2, h3 { font-family: "Source Serif 4", Georgia, serif; line-height: 1.08; } +h1 { max-width: 680px; margin-bottom: 24px; font-size: clamp(44px, 5vw, 72px); font-weight: 700; letter-spacing: -.05em; } +h1 em { color: var(--blue); font-style: italic; } +.hero-text { max-width: 595px; margin-bottom: 32px; color: #42515a; font-size: 18px; } + +.hero-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 24px; } +.button { display: inline-block; border: 0; border-radius: 5px; padding: 15px 22px; font: inherit; font-size: 14px; font-weight: 800; cursor: pointer; transition: transform .2s, background .2s; } +.button-primary { color: var(--white); background: var(--orange); box-shadow: 0 8px 20px rgba(232, 109, 62, .18); } +.button-primary:hover { background: #d75f32; transform: translateY(-2px); } +.text-link { color: var(--blue); font-size: 14px; font-weight: 800; } +.text-link span { margin-left: 6px; font-size: 18px; } + +.hero-stats { display: flex; gap: 31px; margin-top: 55px; } +.hero-stats div { display: grid; gap: 2px; } +.hero-stats strong { color: var(--blue); font-size: 18px; font-weight: 800; } +.hero-stats span { color: var(--muted); font-size: 11px; } + +.formula-card { position: relative; min-height: 420px; overflow: hidden; border-radius: 50% 50% 4% 4%; background: var(--blue); box-shadow: 23px 27px 0 #d5e2dc; } +.formula-card::after { position: absolute; width: 100%; height: 30%; left: 0; bottom: 0; background: #10475f; content: ""; } +.formula-center { position: absolute; top: 50%; left: 50%; z-index: 2; width: 210px; height: 210px; padding-top: 60px; border: 1px solid rgba(255, 255, 255, .45); border-radius: 50%; text-align: center; color: white; transform: translate(-50%, -50%); } +.formula-center p { margin-bottom: 6px; color: #b9d4dc; font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; } +.formula-center strong { font-family: "Source Serif 4", Georgia, serif; font-size: 28px; line-height: 1; } +.card-orbit { position: absolute; top: 50%; left: 50%; border: 1px solid rgba(255, 255, 255, .27); border-radius: 50%; transform: translate(-50%, -50%); } +.orbit-one { width: 310px; height: 310px; } +.orbit-two { width: 465px; height: 465px; } +.formula { position: absolute; z-index: 3; color: #f5c05a; font-family: "Source Serif 4", Georgia, serif; } +.formula-e { top: 64px; left: 48px; font-size: 30px; transform: rotate(-11deg); } +.formula-root { right: 47px; bottom: 80px; font-size: 56px; } +.formula-sum { top: 115px; right: 64px; color: #e88c68; font-size: 62px; } + +.section { padding: 110px 0; } +.split-layout { display: grid; grid-template-columns: .72fr 1.28fr; gap: 80px; } +.split-layout h2, .section-heading h2, .process h2, .booking h2 { margin-bottom: 25px; font-size: clamp(36px, 4vw, 54px); letter-spacing: -.045em; } +.split-layout .lead { max-width: 610px; color: var(--blue); font-family: "Source Serif 4", Georgia, serif; font-size: 25px; line-height: 1.35; } +.split-layout p:not(.lead) { max-width: 650px; color: var(--muted); } + +.subjects { background: var(--sand); } +.section-heading { display: flex; align-items: end; justify-content: space-between; gap: 40px; margin-bottom: 46px; } +.section-heading h2 { margin-bottom: 0; } +.section-heading > p { max-width: 350px; margin-bottom: 6px; color: var(--muted); font-size: 14px; } +.subject-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; } +.subject-card { min-height: 351px; padding: 35px; background: var(--white); border-radius: 3px; } +.subject-card.physics-card { border-top: 5px solid var(--blue); } +.subject-card.math-card { border-top: 5px solid var(--orange); } +.subject-icon { display: inline-flex; width: 50px; height: 50px; align-items: center; justify-content: center; margin-bottom: 35px; border-radius: 50%; color: var(--white); background: var(--blue); font-family: "Source Serif 4", Georgia, serif; font-size: 28px; font-weight: 700; } +.math-card .subject-icon { background: var(--orange); } +.subject-card .card-kicker { margin-bottom: 6px; font-size: 10px; } +.subject-card h3 { margin-bottom: 18px; font-size: 34px; letter-spacing: -.04em; } +.subject-card ul { min-height: 90px; margin: 0 0 23px; padding: 0; list-style: none; color: var(--muted); font-size: 13px; } +.subject-card li { padding: 3px 0 3px 18px; position: relative; } +.subject-card li::before { position: absolute; left: 0; color: var(--orange); content: "•"; } +.subject-card a { color: var(--blue); font-size: 13px; font-weight: 800; } +.subject-card a span { padding-left: 4px; } + +.process { background: #f2f6f4; } +.process h2 { margin-bottom: 47px; } +.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; } +.steps article { position: relative; padding: 30px 25px 10px 0; border-top: 1px solid #aec1bb; } +.steps span { display: block; margin-bottom: 35px; color: var(--orange); font-size: 13px; font-weight: 800; } +.steps h3 { margin-bottom: 11px; font-size: 25px; letter-spacing: -.03em; } +.steps p { color: var(--muted); font-size: 14px; } + +.booking { padding: 108px 0; color: var(--white); background: #153545; } +.booking-layout { display: grid; grid-template-columns: .82fr 1.18fr; gap: 70px; align-items: start; } +.booking .section-kicker { color: #f4a483; } +.booking h2 { max-width: 500px; } +.booking-copy > p:not(.section-kicker) { max-width: 415px; color: #c0d0d2; } +.privacy-note { margin-top: 42px; font-size: 12px; } +.privacy-note::before { margin-right: 8px; content: "⌁"; color: #f4a483; } + +.form-card { padding: 33px; color: var(--ink); background: var(--white); border-radius: 4px; } +.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; } +.field { display: grid; gap: 7px; } +.field.full-width { grid-column: 1 / -1; } +.field label { font-size: 12px; font-weight: 800; } +.form-control { width: 100%; min-height: 43px; padding: 9px 11px; border: 1px solid #ccd4d2; border-radius: 3px; outline: none; color: var(--ink); background: #fdfdfb; font: inherit; font-size: 13px; } +textarea.form-control { min-height: 89px; resize: vertical; } +.form-control:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(25, 93, 120, .12); } +.submit-button { width: 100%; margin-top: 25px; } +.error { color: #b33f26; font-size: 11px; line-height: 1.3; } +.form-errors { margin-bottom: 15px; padding: 11px; color: #8f341f; background: #fce9e4; font-size: 13px; } +.message { margin-bottom: 18px; padding: 13px 15px; font-size: 13px; font-weight: 600; } +.message-success { color: #275c43; background: #e3f2e8; border-left: 3px solid #4b9a71; } + +.site-footer { padding: 27px 0; color: #b5c0c3; background: #0f2937; font-size: 12px; } +.footer-content { display: flex; align-items: center; justify-content: space-between; gap: 20px; } +.site-footer .brand { color: var(--white); } +.site-footer p { margin: 0; } + +@media (max-width: 800px) { + .nav-links { display: none; } + .hero { padding: 64px 0; } + .hero-grid, .booking-layout { grid-template-columns: 1fr; gap: 48px; } + .formula-card { min-height: 350px; max-width: 500px; width: calc(100% - 20px); margin: 0 auto; } + .split-layout { grid-template-columns: 1fr; gap: 5px; } + .section-heading { display: block; } + .section-heading > p { margin-top: 18px; } + .section { padding: 78px 0; } + .booking { padding: 78px 0; } +} + +@media (max-width: 560px) { + .container { width: min(100% - 32px, 1120px); } + .navigation { min-height: 65px; } + .nav-button { padding: 7px 12px; } + h1 { font-size: 43px; } + .hero-text { font-size: 16px; } + .hero-stats { gap: 16px; margin-top: 38px; } + .hero-stats strong { font-size: 15px; } + .hero-stats span { font-size: 9px; } + .formula-card { min-height: 300px; } + .orbit-one { width: 245px; height: 245px; } + .orbit-two { width: 365px; height: 365px; } + .formula-e { top: 40px; left: 28px; } + .formula-sum { top: 66px; right: 35px; } + .formula-root { right: 28px; bottom: 48px; } + .subject-grid, .steps, .form-grid { grid-template-columns: 1fr; } + .subject-card { min-height: auto; } + .subject-card ul { min-height: auto; } + .steps { gap: 12px; } + .steps span { margin-bottom: 18px; } + .form-card { padding: 22px 18px; } + .footer-content { align-items: flex-start; flex-direction: column; gap: 7px; } +} +