Compare commits

...

4 Commits

Author SHA1 Message Date
Matteo Rosati
fe4f135b92 add cursor style 2026-02-11 22:16:55 +01:00
Matteo Rosati
f05e91c2ce add styles 2026-02-11 21:52:15 +01:00
Matteo Rosati
9587995354 simplify apps 2026-02-11 21:05:49 +01:00
Matteo Rosati
93dafb6a81 add django-stubs 2026-02-11 20:52:23 +01:00
33 changed files with 234 additions and 218 deletions

View File

@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
name = 'api'

View File

@@ -1,22 +0,0 @@
# Generated by Django 6.0.2 on 2026-02-10 18:07
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Building',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('geojson_id', models.IntegerField()),
('health', models.FloatField()),
],
),
]

View File

@@ -1,38 +0,0 @@
# Generated by Django 6.0.2 on 2026-02-11 00:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("api", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="PlayerInfo",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("energy", models.PositiveIntegerField()),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="player_info",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@@ -1,16 +0,0 @@
from django.conf import settings
from django.db import models
class Building(models.Model):
geojson_id = models.IntegerField()
health = models.FloatField()
class PlayerInfo(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="player_info",
)
energy = models.PositiveIntegerField()

View File

@@ -1,5 +0,0 @@
from django.urls import path
from .views import prova
urlpatterns = [path("prova/", prova)]

View File

@@ -1,6 +0,0 @@
from django.http.request import HttpRequest
from django.http.response import JsonResponse
def prova(reques: HttpRequest):
return JsonResponse({"message": "OK"})

View File

@@ -10,7 +10,7 @@ const config = {
format: "iife", // o 'iife' se serve per un tag <script> classico
target: "es2020",
sourcemap: isDev,
minify: !isDev,
minify: true,
logLevel: "info",
};

View File

@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"dependencies": {

Binary file not shown.

View File

@@ -40,12 +40,11 @@ ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
# Application definition
INSTALLED_APPS = [
"api",
"game",
"frontend",
"rest_framework",
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",

View File

@@ -15,11 +15,8 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("", include("frontend.urls")),
path("admin/", admin.site.urls),
path("api/", include("api.urls")),
]

View File

@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@@ -1,8 +0,0 @@
from django.apps import AppConfig
class FrontendConfig(AppConfig):
name = "frontend"
def ready(self) -> None:
from . import models # noqa: F401

View File

@@ -1,37 +0,0 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="UserProfile",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("display_name", models.CharField(max_length=150)),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="profile",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@@ -1,15 +0,0 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("frontend", "0001_userprofile"),
]
operations = [
migrations.AlterField(
model_name="userprofile",
name="display_name",
field=models.CharField(max_length=150, unique=True),
),
]

View File

@@ -1,6 +1,52 @@
:root {
color-scheme: dark;
--map-ink: #041005;
--map-void: #020a03;
--map-forest: #0b1f0e;
--map-grid: #0f2f17;
--map-road: #1de85b;
--map-road-soft: #119a3b;
--map-node: #6dff9d;
--map-label: #7dffa6;
--map-glow: rgba(19, 240, 97, 0.35);
--map-glow-strong: rgba(19, 240, 97, 0.65);
--map-surface: rgba(6, 20, 9, 0.85);
--map-surface-strong: rgba(6, 24, 10, 0.96);
--map-border: rgba(29, 232, 91, 0.35);
--map-border-strong: rgba(29, 232, 91, 0.6);
--map-text: #b5ffd0;
--map-muted: #6bbf83;
--map-accent: #1de85b;
--map-accent-hot: #9bff5c;
}
body {
background: radial-gradient(
circle at top,
#0b1f0e 0%,
#041005 45%,
#020a03 100%
);
color: var(--map-text);
font-family:
"JetBrains Mono", "SFMono-Regular", "Menlo", "Monaco", "Consolas",
monospace;
}
strong {
font-weight: 800;
}
#map {
width: 100%;
height: 100vh;
background:
radial-gradient(
circle at 20% 10%,
rgba(12, 48, 20, 0.85),
rgba(4, 14, 7, 0.9)
),
linear-gradient(140deg, rgba(5, 24, 10, 0.95), rgba(2, 8, 4, 0.98));
}
.site-header {
@@ -15,14 +61,20 @@
justify-content: space-between;
padding: 0.85rem 1.5rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.85);
background: linear-gradient(
120deg,
rgba(7, 24, 11, 0.9),
rgba(6, 20, 9, 0.75)
);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 18px 40px rgba(24, 31, 59, 0.16);
border: 1px solid var(--map-border);
box-shadow:
0 18px 40px rgba(0, 0, 0, 0.45),
0 0 24px var(--map-glow);
font-family:
"JetBrains Mono", "SFMono-Regular", "Menlo", "Monaco", "Consolas",
monospace;
color: #111827;
color: var(--map-text);
}
.site-brand {
@@ -32,6 +84,7 @@
color: inherit;
text-decoration: none;
font-weight: 600;
text-shadow: 0 0 12px var(--map-glow);
}
.site-actions {
@@ -42,16 +95,16 @@
}
.site-user {
color: #4b5563;
color: var(--map-muted);
font-weight: 500;
}
.site-link {
text-decoration: none;
color: #111827;
color: var(--map-text);
padding: 0.45rem 0.9rem;
border-radius: 999px;
border: 1px solid rgba(17, 24, 39, 0.12);
border: 1px solid rgba(29, 232, 91, 0.25);
transition:
transform 0.2s ease,
box-shadow 0.2s ease,
@@ -60,15 +113,18 @@
.site-link:hover {
transform: translateY(-1px);
border-color: rgba(17, 24, 39, 0.28);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.12);
border-color: var(--map-border-strong);
box-shadow:
0 10px 20px rgba(0, 0, 0, 0.35),
0 0 16px var(--map-glow);
}
.site-link--accent {
border: none;
background: linear-gradient(130deg, #f97316, #facc15);
color: #1f2937;
background: linear-gradient(130deg, #0fd64f, #9bff5c);
color: #041005;
font-weight: 600;
box-shadow: 0 0 18px rgba(15, 214, 79, 0.45);
}
@media (max-width: 720px) {
@@ -97,12 +153,12 @@
padding: 4rem 1.5rem;
background: radial-gradient(
circle at top,
#eef5ff,
#f7f0ff 35%,
#f7f6ff 65%,
#fdf6ef
#0e2613,
#07180c 35%,
#041106 65%,
#020a03
);
color: #1f1f1f;
color: var(--map-text);
font-family:
"JetBrains Mono", "SFMono-Regular", "Menlo", "Monaco", "Consolas",
monospace;
@@ -110,11 +166,13 @@
.auth-card {
width: min(420px, 100%);
background: #ffffff;
background: var(--map-surface);
border-radius: 24px;
padding: 2.5rem;
box-shadow: 0 24px 60px rgba(24, 31, 59, 0.12);
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow:
0 24px 60px rgba(0, 0, 0, 0.5),
0 0 22px var(--map-glow);
border: 1px solid var(--map-border);
}
.auth-header {
@@ -125,28 +183,28 @@
text-transform: uppercase;
letter-spacing: 0.2em;
font-size: 0.7rem;
color: #57606a;
color: var(--map-muted);
margin-bottom: 0.75rem;
}
.auth-title {
font-size: 1.6rem;
margin-bottom: 0.5rem;
color: #111827;
color: var(--map-text);
}
.auth-subtitle {
font-size: 0.95rem;
color: #6b7280;
color: var(--map-muted);
}
.auth-messages {
margin-bottom: 1.5rem;
background: #fff4f4;
border: 1px solid #ffccd2;
background: rgba(18, 55, 26, 0.75);
border: 1px solid rgba(110, 255, 157, 0.35);
border-radius: 16px;
padding: 0.75rem 1rem;
color: #b42318;
color: var(--map-accent-hot);
}
.auth-message {
@@ -158,6 +216,7 @@
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
}
.auth-field {
@@ -165,20 +224,26 @@
flex-direction: column;
gap: 0.45rem;
font-size: 0.85rem;
color: #374151;
color: var(--map-muted);
width: 100%;
}
.auth-field input {
padding: 0.7rem 0.9rem;
border-radius: 12px;
border: 1px solid #d1d5db;
border: 1px solid rgba(109, 255, 157, 0.3);
font-size: 0.95rem;
font-family: inherit;
background: var(--map-surface-strong);
color: var(--map-text);
box-shadow: inset 0 0 0 1px rgba(3, 18, 7, 0.8);
width: 100%;
min-width: 0;
}
.auth-field input:focus {
outline: 2px solid rgba(76, 110, 245, 0.35);
border-color: #6d7bf7;
outline: 2px solid rgba(29, 232, 91, 0.35);
border-color: var(--map-accent);
}
.auth-submit {
@@ -186,8 +251,8 @@
padding: 0.8rem 1rem;
border-radius: 999px;
border: none;
background: linear-gradient(130deg, #f97316, #facc15);
color: #1f2937;
background: linear-gradient(130deg, #0fd64f, #9bff5c);
color: #041005;
font-weight: 600;
cursor: pointer;
transition:
@@ -197,17 +262,19 @@
.auth-submit:hover {
transform: translateY(-1px);
box-shadow: 0 12px 25px rgba(249, 115, 22, 0.3);
box-shadow:
0 12px 25px rgba(15, 214, 79, 0.35),
0 0 18px var(--map-glow-strong);
}
.auth-footer {
margin-top: 1.5rem;
font-size: 0.85rem;
color: #6b7280;
color: var(--map-muted);
}
.auth-footer a {
color: #111827;
color: var(--map-text);
font-weight: 600;
text-decoration: none;
}

View File

@@ -0,0 +1,13 @@
import { AddLayerObject } from "maplibre-gl";
export const HIGHLIGHT_LAYER = {
id: "buildings-highlight", // nome arbitrario del layer
type: "fill",
source: "buildings",
"source-layer": "building",
paint: {
"fill-color": "#41ff44",
"fill-opacity": 1,
},
filter: ["==", ["id"], ""],
} as AddLayerObject;

View File

@@ -1,14 +1,12 @@
import add from "./lib/add";
import { HIGHLIGHT_LAYER } from "./layers";
import { Map } from "maplibre-gl";
console.log(add(1, 2));
const map = new Map({
style:
"https://api.maptiler.com/maps/019be805-c88e-7c8b-9850-bc704d72e604/style.json?key=8nmgHEIZQiIgqQj3RZNa",
container: "map",
zoom: 15,
zoom: 17,
});
map.on("load", () => {
@@ -20,17 +18,7 @@ map.on("load", () => {
});
// Il layer per gli highlight
map.addLayer({
id: "buildings-highlight", // nome arbitrario del layer
type: "fill",
source: "buildings",
"source-layer": "building",
paint: {
"fill-color": "#41ff44",
"fill-opacity": 1,
},
filter: ["==", ["id"], ""],
});
map.addLayer(HIGHLIGHT_LAYER);
map.on("mousemove", (ev) => {
const features = map.queryRenderedFeatures(ev.point, {
@@ -38,13 +26,16 @@ map.on("load", () => {
});
if (features && features.length > 0) {
const hoveredId = features[0].id;
const feature = features[0];
const hoveredId = feature.id;
if (hoveredId) {
map.setFilter("buildings-highlight", ["==", ["id"], hoveredId]);
map.getCanvas().style.cursor = "pointer";
}
} else {
map.setFilter("buildings-highlight", ["==", ["id"], ""]);
map.getCanvas().style.cursor = "";
}
});
});

View File

@@ -3,7 +3,7 @@
<div class="site-actions">
{% if user.is_authenticated %}
<span class="site-user">
{{ user.profile.display_name|default:user.get_username }}
Welcome, <strong>{{ user.profile.display_name|default:user.get_username }}</strong>
</span>
<a class="site-link" href="{% url 'logout' %}">Logout</a>
{% else %}

View File

@@ -2,7 +2,6 @@ from django.urls import path
from . import views
urlpatterns = [
path("", views.home, name="home"),
path("auth/register/", views.register, name="register"),

View File

@@ -10,7 +10,7 @@ from django.shortcuts import redirect, render
from django.views.decorators.http import require_http_methods
from django_ratelimit.decorators import ratelimit
from .models import UserProfile
from game.models import UserProfile
def home(request):

5
game/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class GameConfig(AppConfig):
name = 'game'

View File

@@ -0,0 +1,41 @@
# Generated by Django 6.0.2 on 2026-02-11 20:00
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Building',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('geojson_id', models.IntegerField()),
('health', models.FloatField()),
],
),
migrations.CreateModel(
name='PlayerInfo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('energy', models.PositiveIntegerField()),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='player_info', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('display_name', models.CharField(max_length=150, unique=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -4,6 +4,20 @@ from django.db.models.signals import post_save
from django.dispatch import receiver
class Building(models.Model):
geojson_id = models.IntegerField()
health = models.FloatField()
class PlayerInfo(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="player_info",
)
energy = models.PositiveIntegerField()
class UserProfile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
@@ -19,7 +33,8 @@ class UserProfile(models.Model):
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_user(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create( # type: ignore[attr-defined]
UserProfile.objects.create(
user=instance,
display_name=instance.username,
)
PlayerInfo.objects.create(user=instance, energy=10_000)

3
game/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -7,6 +7,7 @@ requires-python = ">=3.13"
dependencies = [
"django>=6.0.2",
"django-ratelimit>=4.1.0",
"django-stubs>=5.2.9",
"djangorestframework>=3.16.1",
"djangorestframework-simplejwt>=5.5.0",
"ipython>=9.10.0",

View File

@@ -5,6 +5,8 @@ click==8.3.1
decorator==5.2.1
django==6.0.2
django-ratelimit==4.1.0
django-stubs==5.2.9
django-stubs-ext==5.2.9
djangorestframework==3.16.1
djangorestframework-simplejwt==5.5.1
executing==2.2.1
@@ -26,6 +28,7 @@ python-dotenv==1.2.1
sqlparse==0.5.5
stack-data==0.6.3
traitlets==5.14.3
types-pyyaml==6.0.12.20250915
typing-extensions==4.15.0
typing-inspection==0.4.2
uvicorn==0.40.0

39
uv.lock generated
View File

@@ -82,6 +82,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/78/2c59b30cd8bc8068d02349acb6aeed5c4e05eb01cdf2107ccd76f2e81487/django_ratelimit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d047a31cf94d83ef1465d7543ca66c6fc16695559b5f8d814d1b51df15110b92", size = 11608, upload-time = "2023-07-24T20:34:31.362Z" },
]
[[package]]
name = "django-stubs"
version = "5.2.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "django-stubs-ext" },
{ name = "types-pyyaml" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9c/01/86c921e0e19c9fa7e705bf795998dbf55eb183e7be0342a3027dc1bcbc9f/django_stubs-5.2.9.tar.gz", hash = "sha256:c192257120b08785cfe6f2f1c91f1797aceae8e9daa689c336e52c91e8f6a493", size = 257970, upload-time = "2026-01-20T23:59:27.018Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/05/4c9c419b7051eb4b350100b086be6df487f968ab672d3d370f8ccf7c3746/django_stubs-5.2.9-py3-none-any.whl", hash = "sha256:2317a7130afdaa76f6ff7f623650d7f3bf1b6c86a60f95840e14e6ec6de1a7cd", size = 508656, upload-time = "2026-01-20T23:59:25.12Z" },
]
[[package]]
name = "django-stubs-ext"
version = "5.2.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/03/9c2be939490d2282328db4611bc5956899f5ff7eabc3e88bd4b964a87373/django_stubs_ext-5.2.9.tar.gz", hash = "sha256:6db4054d1580657b979b7d391474719f1a978773e66c7070a5e246cd445a25a9", size = 6497, upload-time = "2026-01-20T23:58:59.462Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/f7/0d5f7d7e76fe972d9f560f687fdc0cab4db9e1624fd90728ca29b4ed7a63/django_stubs_ext-5.2.9-py3-none-any.whl", hash = "sha256:230c51575551b0165be40177f0f6805f1e3ebf799b835c85f5d64c371ca6cf71", size = 9974, upload-time = "2026-01-20T23:58:58.438Z" },
]
[[package]]
name = "djangorestframework"
version = "3.16.1"
@@ -115,6 +143,7 @@ source = { virtual = "." }
dependencies = [
{ name = "django" },
{ name = "django-ratelimit" },
{ name = "django-stubs" },
{ name = "djangorestframework" },
{ name = "djangorestframework-simplejwt" },
{ name = "ipython" },
@@ -128,6 +157,7 @@ dependencies = [
requires-dist = [
{ name = "django", specifier = ">=6.0.2" },
{ name = "django-ratelimit", specifier = ">=4.1.0" },
{ name = "django-stubs", specifier = ">=5.2.9" },
{ name = "djangorestframework", specifier = ">=3.16.1" },
{ name = "djangorestframework-simplejwt", specifier = ">=5.5.0" },
{ name = "ipython", specifier = ">=9.10.0" },
@@ -390,6 +420,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
]
[[package]]
name = "types-pyyaml"
version = "6.0.12.20250915"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"