diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 423a636..07b9b8e 100644 Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..4c85067 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94..67044e4 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 9c49e09..4522bc8 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 74b1112..5aedc64 100644 Binary files a/core/__pycache__/__init__.cpython-311.pyc and b/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..0b6abcb 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc index 6f131d4..c86bdb7 100644 Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 75bf223..7bbd1d6 100644 Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..bc40a42 Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..158fc71 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..10e79dd 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..fd109e1 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..46e35d2 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,42 @@ +from django import forms + + +class LotterySimulatorForm(forms.Form): + lottery_type = forms.ChoiceField( + label="Loteria", + choices=[], + widget=forms.Select( + attrs={ + "class": "form-select form-select-lg", + } + ), + ) + draws_to_consider = forms.IntegerField( + label="Ultimos sorteios", + min_value=3, + max_value=20, + initial=10, + widget=forms.NumberInput( + attrs={ + "class": "form-control", + "inputmode": "numeric", + } + ), + ) + games_to_generate = forms.IntegerField( + label="Jogos sugeridos", + min_value=1, + max_value=12, + initial=4, + widget=forms.NumberInput( + attrs={ + "class": "form-control", + "inputmode": "numeric", + } + ), + ) + + def __init__(self, *args, **kwargs): + lottery_choices = kwargs.pop("lottery_choices", []) + super().__init__(*args, **kwargs) + self.fields["lottery_type"].choices = lottery_choices diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 9c833c8..debfaca 100644 Binary files a/core/migrations/__pycache__/__init__.cpython-311.pyc and b/core/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..9f7bdca 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,8 +1,9 @@ - + + {% block title %}Knowledge Base{% endblock %} {% if project_description %} @@ -14,12 +15,26 @@ {% endif %} {% load static %} + + + + {% block head %}{% endblock %} - + {% block content %}{% endblock %} + diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..35b56fc 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,257 @@ {% extends "base.html" %} +{% load static %} -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% block title %}{{ project_name }} · Analise Loterias BR{% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+ + +
+
+
+
+
+
Inteligencia matematica aplicada
+

Geracoes inteligentes de numeros para todas as loterias do Brasil.

+

+ Use historico recente, frequencias e probabilidades reais para montar seus jogos. Configure cada + loteria e acompanhe os indicadores mais relevantes antes do proximo sorteio. +

+ +
+
+ 5 loterias + Mega-Sena, Quina, Dupla Sena, Lotomania, Lotofacil +
+
+ Analise recencia + Ultimos sorteios para ajustar pesos e tendencias +
+
+ Probabilidade real + Calculo matematico de combinacoes e odds +
+
+
+
+
+
+

Simulador de jogos

+

Escolha a loteria, ajuste o recorte e gere combinacoes sugeridas.

+
+
+ {% csrf_token %} +
+ + {{ form.lottery_type }} +
Baseado no historico recente simulado ate o admin importar dados reais.
+ {{ form.lottery_type.errors }} +
+
+
+ + {{ form.draws_to_consider }} + {{ form.draws_to_consider.errors }} +
+
+ + {{ form.games_to_generate }} + {{ form.games_to_generate.errors }} +
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+

Resumo matematico

+

Resultados calculados com base no recorte selecionado.

-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

+ {% if result %} +
+
+
+
+

{{ result.lottery }}

+ {{ result.draws_used }} sorteios analisados +
+
+
+ Combinacoes possiveis + {{ result.total_combinations }} +
+
+ Odds de acerto total + {{ result.odds }} +
+
+ Probabilidade + {{ result.percent }} +
+
+
+
+
Quentes no recorte
+
+ {% for number in result.hot_numbers %} + {{ number }} + {% endfor %} +
+
+
+
Frias no recorte
+
+ {% for number in result.cold_numbers %} + {{ number }} + {% endfor %} +
+
+
+
+
+
+
+
+

Jogos sugeridos

+ {{ result.suggestions|length }} combinacoes +
+
+ {% for suggestion in result.suggestions %} +
+ {% for number in suggestion %} + {{ number }} + {% endfor %} +
+ {% endfor %} +
+
+
+
+ {% else %} +
+
···
+
+

Simule para ver as probabilidades

+

Escolha a loteria e o recorte no simulador acima para gerar os primeiros jogos sugeridos.

+
+
+ {% endif %} +
+
+ +
+
+
+

Configuracoes por loteria

+

Regras atuais e odds de acerto total para cada jogo.

+
+
+ {% for lottery in lottery_cards %} +
+
+
+

{{ lottery.label }}

+ {{ lottery.tagline }} +
+
+ Dezenas: {{ lottery.picks }} + Universo: {{ lottery.range_max }} +
+
Odds de acerto total: {{ lottery.odds }}
+ Simular {{ lottery.label }} +
+
+ {% endfor %} +
+
+
+ +
+
+
+

Como a inteligencia funciona

+

Fluxo simples para gerar jogos com base matematica.

+
+
+
+
+
01
+

Importar sorteios

+

O admin registra os ultimos concursos e atualiza o motor de analise.

+
+
+
+
+
02
+

Calcular pesos

+

Frequencias e recencia criam o mapa de probabilidades por dezena.

+
+
+
+
+
03
+

Gerar combinacoes

+

Jogos sugeridos sao montados com equilibrio matematico e transparencia.

+
+
+
+
+
+ +
+
+
+
+

Pronto para configurar seus sorteios?

+

Acesse o painel de admin para registrar concursos reais e refinar a analise.

+
+ Ir para admin +
+
+
+ +
- -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/core/views.py b/core/views.py index c9aed12..bd09fd5 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,194 @@ +import math import os import platform +import random +from collections import Counter from django import get_version as django_version from django.shortcuts import render from django.utils import timezone +from .forms import LotterySimulatorForm + + +LOTTERY_CONFIGS = { + "mega_sena": { + "label": "Mega-Sena", + "range_max": 60, + "picks": 6, + "sample_draws": 15, + "seed": 1982, + "tagline": "6 dezenas entre 60", + }, + "quina": { + "label": "Quina", + "range_max": 80, + "picks": 5, + "sample_draws": 15, + "seed": 1971, + "tagline": "5 dezenas entre 80", + }, + "dupla_sena": { + "label": "Dupla Sena", + "range_max": 50, + "picks": 6, + "sample_draws": 15, + "seed": 1990, + "tagline": "6 dezenas entre 50", + }, + "lotomania": { + "label": "Lotomania", + "range_max": 100, + "picks": 50, + "sample_draws": 12, + "seed": 1999, + "tagline": "50 dezenas entre 100", + }, + "lotofacil": { + "label": "Lotofacil", + "range_max": 25, + "picks": 15, + "sample_draws": 20, + "seed": 1994, + "tagline": "15 dezenas entre 25", + }, +} + + +def _generate_sample_draws(config): + rng = random.Random(config["seed"]) + draws = [] + population = list(range(1, config["range_max"] + 1)) + for _ in range(config["sample_draws"]): + draws.append(sorted(rng.sample(population, config["picks"]))) + return draws + + +def _weighted_unique_sample(numbers, weights, picks, rng): + available = list(zip(numbers, weights)) + selection = [] + for _ in range(picks): + total_weight = sum(weight for _, weight in available) + if total_weight <= 0: + choice = rng.choice(available) + selection.append(choice[0]) + available.remove(choice) + continue + pick = rng.uniform(0, total_weight) + cumulative = 0 + for index, (number, weight) in enumerate(available): + cumulative += weight + if cumulative >= pick: + selection.append(number) + del available[index] + break + return selection + + +def _format_odds(total_combinations): + if total_combinations >= 1_000_000_000_000: + return f"1 em {total_combinations:.2e}" + return f"1 em {total_combinations:,}".replace(",", ".") + + +def _format_percent(odds): + percent = 100 / odds + if percent < 0.000001: + return "<0,000001%" + return f"{percent:.6f}%".replace(".", ",") + def home(request): - """Render the landing screen with loader and environment details.""" + """Render the landing screen with lottery simulator and insights.""" host_name = request.get_host().lower() agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" now = timezone.now() + lottery_choices = [ + (key, config["label"]) for key, config in LOTTERY_CONFIGS.items() + ] + lottery_cards = [] + for key, config in LOTTERY_CONFIGS.items(): + total_combinations = math.comb(config["range_max"], config["picks"]) + lottery_cards.append( + { + "key": key, + "label": config["label"], + "tagline": config["tagline"], + "range_max": config["range_max"], + "picks": config["picks"], + "odds": _format_odds(total_combinations), + } + ) + + form = LotterySimulatorForm( + request.POST or None, + lottery_choices=lottery_choices, + ) + result = None + + if form.is_valid(): + lottery_key = form.cleaned_data["lottery_type"] + draws_to_consider = form.cleaned_data["draws_to_consider"] + games_to_generate = form.cleaned_data["games_to_generate"] + config = LOTTERY_CONFIGS[lottery_key] + draws = _generate_sample_draws(config) + draws_to_consider = min(draws_to_consider, len(draws)) + recent_draws = draws[-draws_to_consider:] + frequency = Counter( + number for draw in recent_draws for number in draw + ) + numbers = list(range(1, config["range_max"] + 1)) + weights = [frequency.get(number, 0) + 1 for number in numbers] + rng = random.Random(f"{lottery_key}-{draws_to_consider}-{games_to_generate}") + + suggestions = [] + for _ in range(games_to_generate): + suggestion = _weighted_unique_sample( + numbers, weights, config["picks"], rng + ) + suggestions.append(sorted(suggestion)) + + total_combinations = math.comb(config["range_max"], config["picks"]) + hot_numbers = [ + number for number, _ in frequency.most_common(8) + ] + cold_numbers = [ + number + for number, _ in sorted( + frequency.items(), key=lambda item: item[1] + ) + ] + missing_numbers = [ + number for number in numbers if number not in frequency + ] + cold_numbers = (missing_numbers + cold_numbers)[:8] + result = { + "lottery": config["label"], + "draws_used": draws_to_consider, + "total_combinations": f"{total_combinations:,}".replace(",", "."), + "odds": _format_odds(total_combinations), + "percent": _format_percent(total_combinations), + "suggestions": suggestions, + "hot_numbers": hot_numbers, + "cold_numbers": cold_numbers, + } + context = { - "project_name": "New Style", + "project_name": "LotoPulse", + "project_description": ( + "Analise loterias brasileiras, gere jogos e acompanhe " + "probabilidades com base nos sorteios mais recentes." + ), "agent_brand": agent_brand, "django_version": django_version(), "python_version": platform.python_version(), "current_time": now, "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "deployment_timestamp": now.strftime("%Y%m%d%H%M%S"), + "form": form, + "result": result, + "lottery_cards": lottery_cards, } return render(request, "core/index.html", context) diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..e9c1684 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,479 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +:root { + --ink: #0b1f2a; + --ink-soft: #123041; + --primary: #0f766e; + --primary-strong: #0b5f59; + --secondary: #f59e0b; + --accent: #ea580c; + --surface: #ffffff; + --surface-muted: #f4f7f7; + --line: rgba(12, 42, 58, 0.12); + --glow: rgba(15, 118, 110, 0.25); +} + +* { + box-sizing: border-box; +} + +body.app-body { + margin: 0; + font-family: "Work Sans", sans-serif; + color: var(--ink); + background: radial-gradient(circle at top, #e9f2f1 0%, #f7faf9 40%, #ffffff 100%); +} + +body.app-body::before { + content: ""; + position: fixed; + inset: 0; + background-image: radial-gradient(circle at 20% 20%, rgba(15, 118, 110, 0.1), transparent 45%), + radial-gradient(circle at 90% 10%, rgba(245, 158, 11, 0.12), transparent 40%), + linear-gradient(120deg, rgba(15, 118, 110, 0.07), transparent 50%); + z-index: -2; +} + +.hero-shell { + position: relative; + background: linear-gradient(160deg, #0b1f2a 0%, #0f2f3d 60%, #113844 100%); + color: #f8fbfb; + overflow: hidden; +} + +.hero-shell::after { + content: ""; + position: absolute; + inset: 0; + background-image: radial-gradient(circle at 10% 20%, rgba(245, 158, 11, 0.2), transparent 40%), + radial-gradient(circle at 80% 20%, rgba(15, 118, 110, 0.3), transparent 45%); + opacity: 0.8; + pointer-events: none; +} + +.site-header { + position: relative; + z-index: 1; + padding: 1.5rem 0 0.5rem; +} + +.navbar-brand.brand { + font-family: "Space Grotesk", sans-serif; + font-size: 1.4rem; + font-weight: 700; + color: #fdfdfd; + letter-spacing: 0.02em; +} + +.navbar .nav-link { + color: rgba(248, 251, 251, 0.85); + font-weight: 500; +} + +.navbar .nav-link:hover, +.navbar .nav-link:focus { + color: #ffffff; +} + +.btn-brand { + background: linear-gradient(130deg, var(--secondary), var(--accent)); + border: none; + color: #1b1b1b; + font-weight: 600; + box-shadow: 0 10px 30px rgba(234, 88, 12, 0.3); +} + +.btn-brand:hover, +.btn-brand:focus { + color: #1b1b1b; + transform: translateY(-1px); + box-shadow: 0 14px 35px rgba(234, 88, 12, 0.35); +} + +.btn-ghost { + border: 1px solid rgba(248, 251, 251, 0.3); + color: #f8fbfb; + background: rgba(255, 255, 255, 0.05); +} + +.btn-ghost:hover, +.btn-ghost:focus { + color: #f8fbfb; + border-color: rgba(248, 251, 251, 0.6); +} + +.site-main { + position: relative; + z-index: 1; + padding: 2rem 0 5rem; +} + +.hero-section { + padding: 2rem 0 4rem; +} + +.eyebrow { + text-transform: uppercase; + letter-spacing: 0.24em; + font-size: 0.75rem; + color: rgba(248, 251, 251, 0.6); + margin-bottom: 1rem; +} + +.hero-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2.5rem, 3.5vw, 3.6rem); + font-weight: 700; + line-height: 1.1; + margin-bottom: 1.5rem; +} + +.hero-lead { + font-size: 1.1rem; + color: rgba(248, 251, 251, 0.75); + max-width: 36rem; + margin-bottom: 2rem; +} + +.hero-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; + margin-bottom: 2rem; +} + +.hero-stats { + display: grid; + gap: 1rem; +} + +.stat-card { + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.08); + padding: 1rem 1.25rem; + border-radius: 16px; +} + +.stat-value { + display: block; + font-weight: 600; +} + +.stat-label { + font-size: 0.9rem; + color: rgba(248, 251, 251, 0.7); +} + +.card-glass { + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 20px; + padding: 2rem; + box-shadow: 0 24px 50px rgba(3, 19, 29, 0.4); + backdrop-filter: blur(18px); +} + +.card-glass-header { + margin-bottom: 1.5rem; +} + +.card-title { + font-family: "Space Grotesk", sans-serif; + font-size: 1.5rem; + margin-bottom: 0.5rem; +} + +.card-subtitle { + color: rgba(248, 251, 251, 0.7); + font-size: 0.95rem; +} + +.generator-form .form-label, +.generator-form .form-text { + color: rgba(248, 251, 251, 0.75); +} + +.generator-form .form-select, +.generator-form .form-control { + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #fdfdfd; +} + +.generator-form .form-select:focus, +.generator-form .form-control:focus { + border-color: var(--secondary); + box-shadow: 0 0 0 0.2rem rgba(245, 158, 11, 0.2); +} + +.form-actions { + margin-top: 1.5rem; +} + +.insights-section, +.games-section, +.process-section, +.cta-section { + padding: 4rem 0; +} + +.section-header { + margin-bottom: 2rem; +} + +.section-header h2 { + font-family: "Space Grotesk", sans-serif; + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.section-header p { + color: var(--ink-soft); + max-width: 36rem; +} + +.card-soft { + background: var(--surface); + border-radius: 20px; + padding: 2rem; + box-shadow: 0 18px 40px rgba(11, 31, 42, 0.08); + border: 1px solid var(--line); + height: 100%; +} + +.card-soft-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.pill { + display: inline-flex; + align-items: center; + padding: 0.35rem 0.8rem; + border-radius: 999px; + background: rgba(15, 118, 110, 0.1); + color: var(--primary-strong); + font-size: 0.8rem; + font-weight: 600; +} + +.metrics { + display: grid; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.metric { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1rem; + background: var(--surface-muted); + border-radius: 12px; +} + +.metric-label { + color: var(--ink-soft); + font-size: 0.9rem; +} + +.metric-value { + font-weight: 600; +} + +.number-groups { + display: grid; + gap: 1rem; +} + +.group-title { + font-weight: 600; + margin-bottom: 0.5rem; +} + +.badge-grid { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.badge { + border-radius: 999px; + padding: 0.3rem 0.7rem; + font-size: 0.85rem; + font-weight: 600; +} + +.badge-hot { + background: rgba(234, 88, 12, 0.15); + color: #c2410c; +} + +.badge-cold { + background: rgba(15, 118, 110, 0.12); + color: var(--primary-strong); +} + +.suggestions-grid { + display: grid; + gap: 1rem; +} + +.suggestion { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + padding: 0.75rem; + background: var(--surface-muted); + border-radius: 12px; +} + +.ball { + width: 36px; + height: 36px; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + background: #ffffff; + border: 1px solid var(--line); + font-weight: 600; +} + +.placeholder-card { + display: flex; + gap: 1.5rem; + align-items: center; + background: var(--surface-muted); +} + +.placeholder-icon { + font-size: 2.4rem; + color: var(--primary); +} + +.games-section { + background: #f9fbfb; +} + +.game-card { + background: #ffffff; + border-radius: 18px; + padding: 1.75rem; + border: 1px solid var(--line); + box-shadow: 0 14px 30px rgba(11, 31, 42, 0.08); + height: 100%; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.game-card-header h3 { + font-family: "Space Grotesk", sans-serif; + font-size: 1.4rem; +} + +.game-meta { + display: flex; + justify-content: space-between; + color: var(--ink-soft); + font-size: 0.9rem; +} + +.game-odds { + font-weight: 600; + color: var(--ink); +} + +.process-card { + background: #ffffff; + border-radius: 18px; + padding: 2rem; + border: 1px solid var(--line); + box-shadow: 0 12px 28px rgba(11, 31, 42, 0.08); + height: 100%; +} + +.process-card h3 { + font-family: "Space Grotesk", sans-serif; + font-size: 1.2rem; + margin-bottom: 0.6rem; +} + +.step { + display: inline-flex; + align-items: center; + justify-content: center; + width: 42px; + height: 42px; + border-radius: 12px; + background: rgba(15, 118, 110, 0.15); + color: var(--primary-strong); + font-weight: 700; + margin-bottom: 1rem; +} + +.cta-section { + background: linear-gradient(140deg, rgba(15, 118, 110, 0.12), rgba(245, 158, 11, 0.18)); +} + +.cta-card { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 2rem; + padding: 2.5rem; + border-radius: 20px; + background: #ffffff; + box-shadow: 0 18px 40px rgba(11, 31, 42, 0.08); + border: 1px solid var(--line); +} + +.cta-card h2 { + font-family: "Space Grotesk", sans-serif; + font-size: 1.8rem; + margin-bottom: 0.5rem; +} + +.site-footer { + padding: 2rem 0 3rem; + color: var(--ink-soft); +} + +.site-footer .container { + display: flex; + flex-wrap: wrap; + gap: 1rem; + justify-content: space-between; +} + +.footer-meta { + font-size: 0.9rem; +} + +@media (max-width: 992px) { + .hero-actions { + flex-direction: column; + } + + .card-glass { + margin-top: 2rem; + } +} + +@media (max-width: 768px) { + .hero-shell { + text-align: left; + } + + .stat-card { + padding: 0.9rem 1rem; + } + + .card-soft-header { + flex-direction: column; + align-items: flex-start; + } }