irongit

Django + Next + Postgres + Docker + Nginx

first commit

FIRST_NAME LAST_NAMEauthored
commit da78a2dc45572c034e5389cc0a296b5b391ab31bBrowse files

30 files changed, +1611 -0

+2-0.gitignore
@@ -0,0 +1,2 @@
1+venv
2+postgres
+46-0Dockerfile
@@ -0,0 +1,46 @@
1+FROM alpine:3.17
2+
3+# install node, python, postgres, and nginx
4+RUN apk add --no-cache postgresql
5+RUN apk add --no-cache nodejs
6+RUN apk add --no-cache python3
7+RUN apk add --no-cache nginx
8+RUN apk add --no-cache py3-pip
9+RUN apk add --no-cache npm
10+
11+# configure postgres
12+RUN mkdir -p /run/postgresql
13+RUN chown -R postgres:postgres /run/postgresql
14+
15+# configure nginx
16+RUN mkdir -p /run/nginx
17+RUN chown -R nginx:nginx /run/nginx
18+
19+# install node dependencies
20+COPY ./frontend/package.json /frontend/package.json
21+WORKDIR /frontend
22+RUN npm install
23+
24+# install python dependencies
25+COPY ./backend/requirements.txt /backend/requirements.txt
26+WORKDIR /backend
27+RUN pip install -r requirements.txt
28+
29+# configure node
30+COPY ./frontend /frontend
31+WORKDIR /frontend
32+RUN npm run build
33+
34+# configure django
35+ENV IS_DOCKER=1
36+ENV PYTHONDONTWRITEBYTECODE=1
37+ENV PYTHONUNBUFFERED=1
38+COPY ./backend /backend
39+
40+# copy files
41+COPY ./backend /backend
42+
43+WORKDIR /
44+COPY ./docker-entrypoint.sh /
45+
46+CMD ["sh", "docker-entrypoint.sh"]
+160-0backend/.gitignore
@@ -0,0 +1,160 @@
1+# Byte-compiled / optimized / DLL files
2+__pycache__/
3+*.py[cod]
4+*$py.class
5+
6+# C extensions
7+*.so
8+
9+# Distribution / packaging
10+.Python
11+build/
12+develop-eggs/
13+dist/
14+downloads/
15+eggs/
16+.eggs/
17+lib/
18+lib64/
19+parts/
20+sdist/
21+var/
22+wheels/
23+share/python-wheels/
24+*.egg-info/
25+.installed.cfg
26+*.egg
27+MANIFEST
28+
29+# PyInstaller
30+# Usually these files are written by a python script from a template
31+# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+*.manifest
33+*.spec
34+
35+# Installer logs
36+pip-log.txt
37+pip-delete-this-directory.txt
38+
39+# Unit test / coverage reports
40+htmlcov/
41+.tox/
42+.nox/
43+.coverage
44+.coverage.*
45+.cache
46+nosetests.xml
47+coverage.xml
48+*.cover
49+*.py,cover
50+.hypothesis/
51+.pytest_cache/
52+cover/
53+
54+# Translations
55+*.mo
56+*.pot
57+
58+# Django stuff:
59+*.log
60+local_settings.py
61+db.sqlite3
62+db.sqlite3-journal
63+
64+# Flask stuff:
65+instance/
66+.webassets-cache
67+
68+# Scrapy stuff:
69+.scrapy
70+
71+# Sphinx documentation
72+docs/_build/
73+
74+# PyBuilder
75+.pybuilder/
76+target/
77+
78+# Jupyter Notebook
79+.ipynb_checkpoints
80+
81+# IPython
82+profile_default/
83+ipython_config.py
84+
85+# pyenv
86+# For a library or package, you might want to ignore these files since the code is
87+# intended to run in multiple environments; otherwise, check them in:
88+# .python-version
89+
90+# pipenv
91+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+# having no cross-platform support, pipenv may install dependencies that don't work, or not
94+# install all needed dependencies.
95+#Pipfile.lock
96+
97+# poetry
98+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99+# This is especially recommended for binary packages to ensure reproducibility, and is more
100+# commonly ignored for libraries.
101+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102+#poetry.lock
103+
104+# pdm
105+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106+#pdm.lock
107+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108+# in version control.
109+# https://pdm.fming.dev/#use-with-ide
110+.pdm.toml
111+
112+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113+__pypackages__/
114+
115+# Celery stuff
116+celerybeat-schedule
117+celerybeat.pid
118+
119+# SageMath parsed files
120+*.sage.py
121+
122+# Environments
123+.env
124+.venv
125+env/
126+venv/
127+ENV/
128+env.bak/
129+venv.bak/
130+
131+# Spyder project settings
132+.spyderproject
133+.spyproject
134+
135+# Rope project settings
136+.ropeproject
137+
138+# mkdocs documentation
139+/site
140+
141+# mypy
142+.mypy_cache/
143+.dmypy.json
144+dmypy.json
145+
146+# Pyre type checker
147+.pyre/
148+
149+# pytype static type analyzer
150+.pytype/
151+
152+# Cython debug symbols
153+cython_debug/
154+
155+# PyCharm
156+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158+# and can be added to the global gitignore or merged into this file. For a more nuclear
159+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160+#.idea/
+0-0backend/backend/__init__.py

No content changes (mode or rename only).

+16-0backend/backend/asgi.py
@@ -0,0 +1,16 @@
1+"""
2+ASGI config for backend project.
3+
4+It exposes the ASGI callable as a module-level variable named ``application``.
5+
6+For more information on this file, see
7+https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
8+"""
9+
10+import os
11+
12+from django.core.asgi import get_asgi_application
13+
14+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
15+
16+application = get_asgi_application()
+0-0backend/backend/management/commands/__init__.py

No content changes (mode or rename only).

+16-0backend/backend/management/commands/createsecret.py
@@ -0,0 +1,16 @@
1+from django.core.management.base import BaseCommand, CommandError
2+
3+class Command(BaseCommand):
4+ help = 'Create a new secret key for the .env file.'
5+
6+ def handle(self, *args, **options):
7+ # Generate a django secret key
8+ # https://stackoverflow.com/questions/4664720/how-to-generate-a-random-secret-key-in-django
9+ from django.core.management.utils import get_random_secret_key
10+ new_secret_key = get_random_secret_key()
11+ # Update the .env file
12+ from backend.settings import BASE_DIR
13+ env_file = BASE_DIR / '.env'
14+ with open(env_file, 'w') as f:
15+ f.write(f'SECRET_KEY="{new_secret_key}"')
16+ self.stdout.write(self.style.SUCCESS('Succesfully updated new key into .env file.'))
+136-0backend/backend/settings.py
@@ -0,0 +1,136 @@
1+"""
2+Django settings for backend project.
3+
4+Generated by 'django-admin startproject' using Django 4.1.5.
5+
6+For more information on this file, see
7+https://docs.djangoproject.com/en/4.1/topics/settings/
8+
9+For the full list of settings and their values, see
10+https://docs.djangoproject.com/en/4.1/ref/settings/
11+"""
12+
13+from pathlib import Path
14+import dotenv
15+import os
16+dotenv.load_dotenv()
17+
18+# Build paths inside the project like this: BASE_DIR / 'subdir'.
19+BASE_DIR = Path(__file__).resolve().parent.parent
20+
21+
22+# Quick-start development settings - unsuitable for production
23+# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
24+
25+# SECURITY WARNING: keep the secret key used in production secret!
26+SECRET_KEY = os.environ.get('SECRET_KEY')
27+
28+# SECURITY WARNING: don't run with debug turned on in production!
29+DEBUG = True
30+
31+ALLOWED_HOSTS = ['*']
32+
33+
34+# Application definition
35+
36+INSTALLED_APPS = [
37+ 'django.contrib.admin',
38+ 'django.contrib.auth',
39+ 'django.contrib.contenttypes',
40+ 'django.contrib.sessions',
41+ 'django.contrib.messages',
42+ 'django.contrib.staticfiles',
43+ 'backend',
44+]
45+
46+MIDDLEWARE = [
47+ 'django.middleware.security.SecurityMiddleware',
48+ 'django.contrib.sessions.middleware.SessionMiddleware',
49+ 'django.middleware.common.CommonMiddleware',
50+ 'django.middleware.csrf.CsrfViewMiddleware',
51+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
52+ 'django.contrib.messages.middleware.MessageMiddleware',
53+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
54+]
55+
56+ROOT_URLCONF = 'backend.urls'
57+
58+TEMPLATES = [
59+ {
60+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
61+ 'DIRS': [],
62+ 'APP_DIRS': True,
63+ 'OPTIONS': {
64+ 'context_processors': [
65+ 'django.template.context_processors.debug',
66+ 'django.template.context_processors.request',
67+ 'django.contrib.auth.context_processors.auth',
68+ 'django.contrib.messages.context_processors.messages',
69+ ],
70+ },
71+ },
72+]
73+
74+WSGI_APPLICATION = 'backend.wsgi.application'
75+
76+
77+# Database
78+# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
79+import os
80+if os.environ.get('IS_DOCKER'):
81+ DATABASES = {
82+ 'default': {
83+ 'ENGINE': 'django.db.backends.postgresql',
84+ 'NAME': 'postgres',
85+ 'USER': 'postgres'
86+ }
87+ }
88+else:
89+ DATABASES = {
90+ 'default': {
91+ 'ENGINE': 'django.db.backends.sqlite3',
92+ 'NAME': BASE_DIR / 'db.sqlite3',
93+ }
94+ }
95+
96+
97+# Password validation
98+# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
99+
100+AUTH_PASSWORD_VALIDATORS = [
101+ {
102+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
103+ },
104+ {
105+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
106+ },
107+ {
108+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
109+ },
110+ {
111+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
112+ },
113+]
114+
115+
116+# Internationalization
117+# https://docs.djangoproject.com/en/4.1/topics/i18n/
118+
119+LANGUAGE_CODE = 'en-us'
120+
121+TIME_ZONE = 'UTC'
122+
123+USE_I18N = True
124+
125+USE_TZ = True
126+
127+
128+# Static files (CSS, JavaScript, Images)
129+# https://docs.djangoproject.com/en/4.1/howto/static-files/
130+
131+STATIC_URL = 'static/'
132+
133+# Default primary key field type
134+# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
135+
136+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+21-0backend/backend/urls.py
@@ -0,0 +1,21 @@
1+"""backend URL Configuration
2+
3+The `urlpatterns` list routes URLs to views. For more information please see:
4+ https://docs.djangoproject.com/en/4.1/topics/http/urls/
5+Examples:
6+Function views
7+ 1. Add an import: from my_app import views
8+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
9+Class-based views
10+ 1. Add an import: from other_app.views import Home
11+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12+Including another URLconf
13+ 1. Import the include() function: from django.urls import include, path
14+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15+"""
16+from django.contrib import admin
17+from django.urls import path
18+
19+urlpatterns = [
20+ path('admin/', admin.site.urls),
21+]
+16-0backend/backend/wsgi.py
@@ -0,0 +1,16 @@
1+"""
2+WSGI config for backend project.
3+
4+It exposes the WSGI callable as a module-level variable named ``application``.
5+
6+For more information on this file, see
7+https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
8+"""
9+
10+import os
11+
12+from django.core.wsgi import get_wsgi_application
13+
14+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
15+
16+application = get_wsgi_application()
+22-0backend/manage.py
@@ -0,0 +1,22 @@
1+#!/usr/bin/env python
2+"""Django's command-line utility for administrative tasks."""
3+import os
4+import sys
5+
6+
7+def main():
8+ """Run administrative tasks."""
9+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
10+ try:
11+ from django.core.management import execute_from_command_line
12+ except ImportError as exc:
13+ raise ImportError(
14+ "Couldn't import Django. Are you sure it's installed and "
15+ "available on your PYTHONPATH environment variable? Did you "
16+ "forget to activate a virtual environment?"
17+ ) from exc
18+ execute_from_command_line(sys.argv)
19+
20+
21+if __name__ == '__main__':
22+ main()
+11-0backend/requirements.txt
@@ -0,0 +1,11 @@
1+asgiref==3.6.0
2+click==8.1.3
3+colorama==0.4.6
4+Django==4.1.5
5+djangorestframework==3.14.0
6+h11==0.14.0
7+psycopg2-binary==2.9.5
8+pytz==2022.7
9+sqlparse==0.4.3
10+tzdata==2022.7
11+uvicorn==0.20.0
+10-0docker-compose.yml
@@ -0,0 +1,10 @@
1+services:
2+ nppn:
3+ build: .
4+ container_name: nppn
5+ volumes:
6+ - ./postgres:/var/lib/postgresql/data
7+ ports:
8+ - "80:80"
9+ - "3000:3000"
10+ - "8000:8000"
+16-0docker-entrypoint.sh
@@ -0,0 +1,16 @@
1+#!/bin/bash
2+
3+# postgres stuff
4+chown postgres:postgres /var/lib/postgresql/data
5+if [ ! "$(ls -A /var/lib/postgresql/data)" ]; then
6+ su postgres -c "initdb -D /var/lib/postgresql/data"
7+fi
8+sed -i 's/\/run\/postgresql/\/tmp/' /var/lib/postgresql/data/postgresql.conf
9+su postgres -c 'pg_ctl start -D /var/lib/postgresql/data'
10+
11+nginx
12+tail -f /var/lib/nginx/logs/access.log &
13+
14+cd frontend && npm start &
15+cd backend && python manage.py migrate
16+cd /backend && uvicorn backend.asgi:application --host 0.0.0.0
+36-0frontend/.gitignore
@@ -0,0 +1,36 @@
1+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+# dependencies
4+/node_modules
5+/.pnp
6+.pnp.js
7+
8+# testing
9+/coverage
10+
11+# next.js
12+/.next/
13+/out/
14+
15+# production
16+/build
17+
18+# misc
19+.DS_Store
20+*.pem
21+
22+# debug
23+npm-debug.log*
24+yarn-debug.log*
25+yarn-error.log*
26+.pnpm-debug.log*
27+
28+# local env files
29+.env*.local
30+
31+# vercel
32+.vercel
33+
34+# typescript
35+*.tsbuildinfo
36+next-env.d.ts
+36-0frontend/README.md
@@ -0,0 +1,36 @@
1+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2+
3+## Getting Started
4+
5+First, run the development server:
6+
7+```bash
8+npm run dev
9+# or
10+yarn dev
11+```
12+
13+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14+
15+You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
16+
17+[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
18+
19+The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20+
21+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22+
23+## Learn More
24+
25+To learn more about Next.js, take a look at the following resources:
26+
27+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31+
32+## Deploy on Vercel
33+
34+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+6-0frontend/next.config.js
@@ -0,0 +1,6 @@
1+/** @type {import('next').NextConfig} */
2+const nextConfig = {
3+ reactStrictMode: true,
4+}
5+
6+module.exports = nextConfig
+477-0frontend/package-lock.json
@@ -0,0 +1,477 @@
1+{
2+ "name": "frontend",
3+ "version": "0.1.0",
4+ "lockfileVersion": 3,
5+ "requires": true,
6+ "packages": {
7+ "": {
8+ "name": "frontend",
9+ "version": "0.1.0",
10+ "dependencies": {
11+ "@next/font": "13.1.1",
12+ "@types/node": "18.11.18",
13+ "@types/react": "18.0.26",
14+ "@types/react-dom": "18.0.10",
15+ "next": "13.1.1",
16+ "react": "18.2.0",
17+ "react-dom": "18.2.0",
18+ "typescript": "4.9.4"
19+ }
20+ },
21+ "node_modules/@next/env": {
22+ "version": "13.1.1",
23+ "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz",
24+ "integrity": "sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw=="
25+ },
26+ "node_modules/@next/font": {
27+ "version": "13.1.1",
28+ "resolved": "https://registry.npmjs.org/@next/font/-/font-13.1.1.tgz",
29+ "integrity": "sha512-amygRorS05hYK1/XQRZo5qBl7l2fpHnezeKU/cNveWU5QJg+sg8gMGkUXHtvesNKpiKIJshBRH1TzvO+2sKpvQ=="
30+ },
31+ "node_modules/@next/swc-android-arm-eabi": {
32+ "version": "13.1.1",
33+ "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.1.tgz",
34+ "integrity": "sha512-qnFCx1kT3JTWhWve4VkeWuZiyjG0b5T6J2iWuin74lORCupdrNukxkq9Pm+Z7PsatxuwVJMhjUoYz7H4cWzx2A==",
35+ "cpu": [
36+ "arm"
37+ ],
38+ "optional": true,
39+ "os": [
40+ "android"
41+ ],
42+ "engines": {
43+ "node": ">= 10"
44+ }
45+ },
46+ "node_modules/@next/swc-android-arm64": {
47+ "version": "13.1.1",
48+ "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.1.tgz",
49+ "integrity": "sha512-eCiZhTzjySubNqUnNkQCjU3Fh+ep3C6b5DCM5FKzsTH/3Gr/4Y7EiaPZKILbvnXmhWtKPIdcY6Zjx51t4VeTfA==",
50+ "cpu": [
51+ "arm64"
52+ ],
53+ "optional": true,
54+ "os": [
55+ "android"
56+ ],
57+ "engines": {
58+ "node": ">= 10"
59+ }
60+ },
61+ "node_modules/@next/swc-darwin-arm64": {
62+ "version": "13.1.1",
63+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.1.tgz",
64+ "integrity": "sha512-9zRJSSIwER5tu9ADDkPw5rIZ+Np44HTXpYMr0rkM656IvssowPxmhK0rTreC1gpUCYwFsRbxarUJnJsTWiutPg==",
65+ "cpu": [
66+ "arm64"
67+ ],
68+ "optional": true,
69+ "os": [
70+ "darwin"
71+ ],
72+ "engines": {
73+ "node": ">= 10"
74+ }
75+ },
76+ "node_modules/@next/swc-darwin-x64": {
77+ "version": "13.1.1",
78+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.1.tgz",
79+ "integrity": "sha512-qWr9qEn5nrnlhB0rtjSdR00RRZEtxg4EGvicIipqZWEyayPxhUu6NwKiG8wZiYZCLfJ5KWr66PGSNeDMGlNaiA==",
80+ "cpu": [
81+ "x64"
82+ ],
83+ "optional": true,
84+ "os": [
85+ "darwin"
86+ ],
87+ "engines": {
88+ "node": ">= 10"
89+ }
90+ },
91+ "node_modules/@next/swc-freebsd-x64": {
92+ "version": "13.1.1",
93+ "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.1.tgz",
94+ "integrity": "sha512-UwP4w/NcQ7V/VJEj3tGVszgb4pyUCt3lzJfUhjDMUmQbzG9LDvgiZgAGMYH6L21MoyAATJQPDGiAMWAPKsmumA==",
95+ "cpu": [
96+ "x64"
97+ ],
98+ "optional": true,
99+ "os": [
100+ "freebsd"
101+ ],
102+ "engines": {
103+ "node": ">= 10"
104+ }
105+ },
106+ "node_modules/@next/swc-linux-arm-gnueabihf": {
107+ "version": "13.1.1",
108+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.1.tgz",
109+ "integrity": "sha512-CnsxmKHco9sosBs1XcvCXP845Db+Wx1G0qouV5+Gr+HT/ZlDYEWKoHVDgnJXLVEQzq4FmHddBNGbXvgqM1Gfkg==",
110+ "cpu": [
111+ "arm"
112+ ],
113+ "optional": true,
114+ "os": [
115+ "linux"
116+ ],
117+ "engines": {
118+ "node": ">= 10"
119+ }
120+ },
121+ "node_modules/@next/swc-linux-arm64-gnu": {
122+ "version": "13.1.1",
123+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.1.tgz",
124+ "integrity": "sha512-JfDq1eri5Dif+VDpTkONRd083780nsMCOKoFG87wA0sa4xL8LGcXIBAkUGIC1uVy9SMsr2scA9CySLD/i+Oqiw==",
125+ "cpu": [
126+ "arm64"
127+ ],
128+ "optional": true,
129+ "os": [
130+ "linux"
131+ ],
132+ "engines": {
133+ "node": ">= 10"
134+ }
135+ },
136+ "node_modules/@next/swc-linux-arm64-musl": {
137+ "version": "13.1.1",
138+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.1.tgz",
139+ "integrity": "sha512-GA67ZbDq2AW0CY07zzGt07M5b5Yaq5qUpFIoW3UFfjOPgb0Sqf3DAW7GtFMK1sF4ROHsRDMGQ9rnT0VM2dVfKA==",
140+ "cpu": [
141+ "arm64"
142+ ],
143+ "optional": true,
144+ "os": [
145+ "linux"
146+ ],
147+ "engines": {
148+ "node": ">= 10"
149+ }
150+ },
151+ "node_modules/@next/swc-linux-x64-gnu": {
152+ "version": "13.1.1",
153+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.1.tgz",
154+ "integrity": "sha512-nnjuBrbzvqaOJaV+XgT8/+lmXrSCOt1YYZn/irbDb2fR2QprL6Q7WJNgwsZNxiLSfLdv+2RJGGegBx9sLBEzGA==",
155+ "cpu": [
156+ "x64"
157+ ],
158+ "optional": true,
159+ "os": [
160+ "linux"
161+ ],
162+ "engines": {
163+ "node": ">= 10"
164+ }
165+ },
166+ "node_modules/@next/swc-linux-x64-musl": {
167+ "version": "13.1.1",
168+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.1.tgz",
169+ "integrity": "sha512-CM9xnAQNIZ8zf/igbIT/i3xWbQZYaF397H+JroF5VMOCUleElaMdQLL5riJml8wUfPoN3dtfn2s4peSr3azz/g==",
170+ "cpu": [
171+ "x64"
172+ ],
173+ "optional": true,
174+ "os": [
175+ "linux"
176+ ],
177+ "engines": {
178+ "node": ">= 10"
179+ }
180+ },
181+ "node_modules/@next/swc-win32-arm64-msvc": {
182+ "version": "13.1.1",
183+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.1.tgz",
184+ "integrity": "sha512-pzUHOGrbgfGgPlOMx9xk3QdPJoRPU+om84hqVoe6u+E0RdwOG0Ho/2UxCgDqmvpUrMab1Deltlt6RqcXFpnigQ==",
185+ "cpu": [
186+ "arm64"
187+ ],
188+ "optional": true,
189+ "os": [
190+ "win32"
191+ ],
192+ "engines": {
193+ "node": ">= 10"
194+ }
195+ },
196+ "node_modules/@next/swc-win32-ia32-msvc": {
197+ "version": "13.1.1",
198+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.1.tgz",
199+ "integrity": "sha512-WeX8kVS46aobM9a7Xr/kEPcrTyiwJqQv/tbw6nhJ4fH9xNZ+cEcyPoQkwPo570dCOLz3Zo9S2q0E6lJ/EAUOBg==",
200+ "cpu": [
201+ "ia32"
202+ ],
203+ "optional": true,
204+ "os": [
205+ "win32"
206+ ],
207+ "engines": {
208+ "node": ">= 10"
209+ }
210+ },
211+ "node_modules/@next/swc-win32-x64-msvc": {
212+ "version": "13.1.1",
213+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.1.tgz",
214+ "integrity": "sha512-mVF0/3/5QAc5EGVnb8ll31nNvf3BWpPY4pBb84tk+BfQglWLqc5AC9q1Ht/YMWiEgs8ALNKEQ3GQnbY0bJF2Gg==",
215+ "cpu": [
216+ "x64"
217+ ],
218+ "optional": true,
219+ "os": [
220+ "win32"
221+ ],
222+ "engines": {
223+ "node": ">= 10"
224+ }
225+ },
226+ "node_modules/@swc/helpers": {
227+ "version": "0.4.14",
228+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
229+ "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
230+ "dependencies": {
231+ "tslib": "^2.4.0"
232+ }
233+ },
234+ "node_modules/@types/node": {
235+ "version": "18.11.18",
236+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
237+ "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
238+ },
239+ "node_modules/@types/prop-types": {
240+ "version": "15.7.5",
241+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
242+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
243+ },
244+ "node_modules/@types/react": {
245+ "version": "18.0.26",
246+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
247+ "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
248+ "dependencies": {
249+ "@types/prop-types": "*",
250+ "@types/scheduler": "*",
251+ "csstype": "^3.0.2"
252+ }
253+ },
254+ "node_modules/@types/react-dom": {
255+ "version": "18.0.10",
256+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
257+ "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
258+ "dependencies": {
259+ "@types/react": "*"
260+ }
261+ },
262+ "node_modules/@types/scheduler": {
263+ "version": "0.16.2",
264+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
265+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
266+ },
267+ "node_modules/caniuse-lite": {
268+ "version": "1.0.30001442",
269+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
270+ "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
271+ "funding": [
272+ {
273+ "type": "opencollective",
274+ "url": "https://opencollective.com/browserslist"
275+ },
276+ {
277+ "type": "tidelift",
278+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
279+ }
280+ ]
281+ },
282+ "node_modules/client-only": {
283+ "version": "0.0.1",
284+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
285+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
286+ },
287+ "node_modules/csstype": {
288+ "version": "3.1.1",
289+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
290+ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
291+ },
292+ "node_modules/js-tokens": {
293+ "version": "4.0.0",
294+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
295+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
296+ },
297+ "node_modules/loose-envify": {
298+ "version": "1.4.0",
299+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
300+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
301+ "dependencies": {
302+ "js-tokens": "^3.0.0 || ^4.0.0"
303+ },
304+ "bin": {
305+ "loose-envify": "cli.js"
306+ }
307+ },
308+ "node_modules/nanoid": {
309+ "version": "3.3.4",
310+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
311+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
312+ "bin": {
313+ "nanoid": "bin/nanoid.cjs"
314+ },
315+ "engines": {
316+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
317+ }
318+ },
319+ "node_modules/next": {
320+ "version": "13.1.1",
321+ "resolved": "https://registry.npmjs.org/next/-/next-13.1.1.tgz",
322+ "integrity": "sha512-R5eBAaIa3X7LJeYvv1bMdGnAVF4fVToEjim7MkflceFPuANY3YyvFxXee/A+acrSYwYPvOvf7f6v/BM/48ea5w==",
323+ "dependencies": {
324+ "@next/env": "13.1.1",
325+ "@swc/helpers": "0.4.14",
326+ "caniuse-lite": "^1.0.30001406",
327+ "postcss": "8.4.14",
328+ "styled-jsx": "5.1.1"
329+ },
330+ "bin": {
331+ "next": "dist/bin/next"
332+ },
333+ "engines": {
334+ "node": ">=14.6.0"
335+ },
336+ "optionalDependencies": {
337+ "@next/swc-android-arm-eabi": "13.1.1",
338+ "@next/swc-android-arm64": "13.1.1",
339+ "@next/swc-darwin-arm64": "13.1.1",
340+ "@next/swc-darwin-x64": "13.1.1",
341+ "@next/swc-freebsd-x64": "13.1.1",
342+ "@next/swc-linux-arm-gnueabihf": "13.1.1",
343+ "@next/swc-linux-arm64-gnu": "13.1.1",
344+ "@next/swc-linux-arm64-musl": "13.1.1",
345+ "@next/swc-linux-x64-gnu": "13.1.1",
346+ "@next/swc-linux-x64-musl": "13.1.1",
347+ "@next/swc-win32-arm64-msvc": "13.1.1",
348+ "@next/swc-win32-ia32-msvc": "13.1.1",
349+ "@next/swc-win32-x64-msvc": "13.1.1"
350+ },
351+ "peerDependencies": {
352+ "fibers": ">= 3.1.0",
353+ "node-sass": "^6.0.0 || ^7.0.0",
354+ "react": "^18.2.0",
355+ "react-dom": "^18.2.0",
356+ "sass": "^1.3.0"
357+ },
358+ "peerDependenciesMeta": {
359+ "fibers": {
360+ "optional": true
361+ },
362+ "node-sass": {
363+ "optional": true
364+ },
365+ "sass": {
366+ "optional": true
367+ }
368+ }
369+ },
370+ "node_modules/picocolors": {
371+ "version": "1.0.0",
372+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
373+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
374+ },
375+ "node_modules/postcss": {
376+ "version": "8.4.14",
377+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
378+ "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
379+ "funding": [
380+ {
381+ "type": "opencollective",
382+ "url": "https://opencollective.com/postcss/"
383+ },
384+ {
385+ "type": "tidelift",
386+ "url": "https://tidelift.com/funding/github/npm/postcss"
387+ }
388+ ],
389+ "dependencies": {
390+ "nanoid": "^3.3.4",
391+ "picocolors": "^1.0.0",
392+ "source-map-js": "^1.0.2"
393+ },
394+ "engines": {
395+ "node": "^10 || ^12 || >=14"
396+ }
397+ },
398+ "node_modules/react": {
399+ "version": "18.2.0",
400+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
401+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
402+ "dependencies": {
403+ "loose-envify": "^1.1.0"
404+ },
405+ "engines": {
406+ "node": ">=0.10.0"
407+ }
408+ },
409+ "node_modules/react-dom": {
410+ "version": "18.2.0",
411+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
412+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
413+ "dependencies": {
414+ "loose-envify": "^1.1.0",
415+ "scheduler": "^0.23.0"
416+ },
417+ "peerDependencies": {
418+ "react": "^18.2.0"
419+ }
420+ },
421+ "node_modules/scheduler": {
422+ "version": "0.23.0",
423+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
424+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
425+ "dependencies": {
426+ "loose-envify": "^1.1.0"
427+ }
428+ },
429+ "node_modules/source-map-js": {
430+ "version": "1.0.2",
431+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
432+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
433+ "engines": {
434+ "node": ">=0.10.0"
435+ }
436+ },
437+ "node_modules/styled-jsx": {
438+ "version": "5.1.1",
439+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
440+ "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
441+ "dependencies": {
442+ "client-only": "0.0.1"
443+ },
444+ "engines": {
445+ "node": ">= 12.0.0"
446+ },
447+ "peerDependencies": {
448+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
449+ },
450+ "peerDependenciesMeta": {
451+ "@babel/core": {
452+ "optional": true
453+ },
454+ "babel-plugin-macros": {
455+ "optional": true
456+ }
457+ }
458+ },
459+ "node_modules/tslib": {
460+ "version": "2.4.1",
461+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
462+ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
463+ },
464+ "node_modules/typescript": {
465+ "version": "4.9.4",
466+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
467+ "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
468+ "bin": {
469+ "tsc": "bin/tsc",
470+ "tsserver": "bin/tsserver"
471+ },
472+ "engines": {
473+ "node": ">=4.2.0"
474+ }
475+ }
476+ }
477+}
+21-0frontend/package.json
@@ -0,0 +1,21 @@
1+{
2+ "name": "frontend",
3+ "version": "0.1.0",
4+ "private": true,
5+ "scripts": {
6+ "dev": "next dev",
7+ "build": "next build",
8+ "start": "next start",
9+ "lint": "next lint"
10+ },
11+ "dependencies": {
12+ "@next/font": "13.1.1",
13+ "@types/node": "18.11.18",
14+ "@types/react": "18.0.26",
15+ "@types/react-dom": "18.0.10",
16+ "next": "13.1.1",
17+ "react": "18.2.0",
18+ "react-dom": "18.2.0",
19+ "typescript": "4.9.4"
20+ }
21+}
+6-0frontend/pages/_app.tsx
@@ -0,0 +1,6 @@
1+import '../styles/globals.css'
2+import type { AppProps } from 'next/app'
3+
4+export default function App({ Component, pageProps }: AppProps) {
5+ return <Component {...pageProps} />
6+}
+13-0frontend/pages/_document.tsx
@@ -0,0 +1,13 @@
1+import { Html, Head, Main, NextScript } from 'next/document'
2+
3+export default function Document() {
4+ return (
5+ <Html lang="en">
6+ <Head />
7+ <body>
8+ <Main />
9+ <NextScript />
10+ </body>
11+ </Html>
12+ )
13+}
+13-0frontend/pages/api/hello.ts
@@ -0,0 +1,13 @@
1+// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2+import type { NextApiRequest, NextApiResponse } from 'next'
3+
4+type Data = {
5+ name: string
6+}
7+
8+export default function handler(
9+ req: NextApiRequest,
10+ res: NextApiResponse<Data>
11+) {
12+ res.status(200).json({ name: 'John Doe' })
13+}
+123-0frontend/pages/index.tsx
@@ -0,0 +1,123 @@
1+import Head from 'next/head'
2+import Image from 'next/image'
3+import { Inter } from '@next/font/google'
4+import styles from '../styles/Home.module.css'
5+
6+const inter = Inter({ subsets: ['latin'] })
7+
8+export default function Home() {
9+ return (
10+ <>
11+ <Head>
12+ <title>Create Next App</title>
13+ <meta name="description" content="Generated by create next app" />
14+ <meta name="viewport" content="width=device-width, initial-scale=1" />
15+ <link rel="icon" href="/favicon.ico" />
16+ </Head>
17+ <main className={styles.main}>
18+ <div className={styles.description}>
19+ <p>
20+ Get started by editing&nbsp;
21+ <code className={styles.code}>pages/index.tsx</code>
22+ </p>
23+ <div>
24+ <a
25+ href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
26+ target="_blank"
27+ rel="noopener noreferrer"
28+ >
29+ By{' '}
30+ <Image
31+ src="/vercel.svg"
32+ alt="Vercel Logo"
33+ className={styles.vercelLogo}
34+ width={100}
35+ height={24}
36+ priority
37+ />
38+ </a>
39+ </div>
40+ </div>
41+
42+ <div className={styles.center}>
43+ <Image
44+ className={styles.logo}
45+ src="/next.svg"
46+ alt="Next.js Logo"
47+ width={180}
48+ height={37}
49+ priority
50+ />
51+ <div className={styles.thirteen}>
52+ <Image
53+ src="/thirteen.svg"
54+ alt="13"
55+ width={40}
56+ height={31}
57+ priority
58+ />
59+ </div>
60+ </div>
61+
62+ <div className={styles.grid}>
63+ <a
64+ href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
65+ className={styles.card}
66+ target="_blank"
67+ rel="noopener noreferrer"
68+ >
69+ <h2 className={inter.className}>
70+ Docs <span>-&gt;</span>
71+ </h2>
72+ <p className={inter.className}>
73+ Find in-depth information about Next.js features and&nbsp;API.
74+ </p>
75+ </a>
76+
77+ <a
78+ href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
79+ className={styles.card}
80+ target="_blank"
81+ rel="noopener noreferrer"
82+ >
83+ <h2 className={inter.className}>
84+ Learn <span>-&gt;</span>
85+ </h2>
86+ <p className={inter.className}>
87+ Learn about Next.js in an interactive course with&nbsp;quizzes!
88+ </p>
89+ </a>
90+
91+ <a
92+ href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
93+ className={styles.card}
94+ target="_blank"
95+ rel="noopener noreferrer"
96+ >
97+ <h2 className={inter.className}>
98+ Templates <span>-&gt;</span>
99+ </h2>
100+ <p className={inter.className}>
101+ Discover and deploy boilerplate example Next.js&nbsp;projects.
102+ </p>
103+ </a>
104+
105+ <a
106+ href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
107+ className={styles.card}
108+ target="_blank"
109+ rel="noopener noreferrer"
110+ >
111+ <h2 className={inter.className}>
112+ Deploy <span>-&gt;</span>
113+ </h2>
114+ <p className={inter.className}>
115+ Instantly deploy your Next.js site to a shareable URL
116+ with&nbsp;Vercel.
117+ </p>
118+ </a>
119+ </div>
120+ </main>
121+ </>
122+ )
123+}
+0-0frontend/public/favicon.ico

Binary file changed.

+1-0frontend/public/next.svg
@@ -0,0 +1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
+1-0frontend/public/thirteen.svg
@@ -0,0 +1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="31" fill="none"><g opacity=".9"><path fill="url(#a)" d="M13 .4v29.3H7V6.3h-.2L0 10.5V5L7.2.4H13Z"/><path fill="url(#b)" d="M28.8 30.1c-2.2 0-4-.3-5.7-1-1.7-.8-3-1.8-4-3.1a7.7 7.7 0 0 1-1.4-4.6h6.2c0 .8.3 1.4.7 2 .4.5 1 .9 1.7 1.2.7.3 1.6.4 2.5.4 1 0 1.7-.2 2.5-.5.7-.3 1.3-.8 1.7-1.4.4-.6.6-1.2.6-2s-.2-1.5-.7-2.1c-.4-.6-1-1-1.8-1.4-.8-.4-1.8-.5-2.9-.5h-2.7v-4.6h2.7a6 6 0 0 0 2.5-.5 4 4 0 0 0 1.7-1.3c.4-.6.6-1.3.6-2a3.5 3.5 0 0 0-2-3.3 5.6 5.6 0 0 0-4.5 0 4 4 0 0 0-1.7 1.2c-.4.6-.6 1.2-.6 2h-6c0-1.7.6-3.2 1.5-4.5 1-1.3 2.2-2.3 3.8-3C25 .4 26.8 0 28.8 0s3.8.4 5.3 1.1c1.5.7 2.7 1.7 3.6 3a7.2 7.2 0 0 1 1.2 4.2c0 1.6-.5 3-1.5 4a7 7 0 0 1-4 2.2v.2c2.2.3 3.8 1 5 2.2a6.4 6.4 0 0 1 1.6 4.6c0 1.7-.5 3.1-1.4 4.4a9.7 9.7 0 0 1-4 3.1c-1.7.8-3.7 1.1-5.8 1.1Z"/></g><defs><linearGradient id="a" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient><linearGradient id="b" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient></defs></svg>
+1-0frontend/public/vercel.svg
@@ -0,0 +1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
+278-0frontend/styles/Home.module.css
@@ -0,0 +1,278 @@
1+.main {
2+ display: flex;
3+ flex-direction: column;
4+ justify-content: space-between;
5+ align-items: center;
6+ padding: 6rem;
7+ min-height: 100vh;
8+}
9+
10+.description {
11+ display: inherit;
12+ justify-content: inherit;
13+ align-items: inherit;
14+ font-size: 0.85rem;
15+ max-width: var(--max-width);
16+ width: 100%;
17+ z-index: 2;
18+ font-family: var(--font-mono);
19+}
20+
21+.description a {
22+ display: flex;
23+ justify-content: center;
24+ align-items: center;
25+ gap: 0.5rem;
26+}
27+
28+.description p {
29+ position: relative;
30+ margin: 0;
31+ padding: 1rem;
32+ background-color: rgba(var(--callout-rgb), 0.5);
33+ border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34+ border-radius: var(--border-radius);
35+}
36+
37+.code {
38+ font-weight: 700;
39+ font-family: var(--font-mono);
40+}
41+
42+.grid {
43+ display: grid;
44+ grid-template-columns: repeat(4, minmax(25%, auto));
45+ width: var(--max-width);
46+ max-width: 100%;
47+}
48+
49+.card {
50+ padding: 1rem 1.2rem;
51+ border-radius: var(--border-radius);
52+ background: rgba(var(--card-rgb), 0);
53+ border: 1px solid rgba(var(--card-border-rgb), 0);
54+ transition: background 200ms, border 200ms;
55+}
56+
57+.card span {
58+ display: inline-block;
59+ transition: transform 200ms;
60+}
61+
62+.card h2 {
63+ font-weight: 600;
64+ margin-bottom: 0.7rem;
65+}
66+
67+.card p {
68+ margin: 0;
69+ opacity: 0.6;
70+ font-size: 0.9rem;
71+ line-height: 1.5;
72+ max-width: 30ch;
73+}
74+
75+.center {
76+ display: flex;
77+ justify-content: center;
78+ align-items: center;
79+ position: relative;
80+ padding: 4rem 0;
81+}
82+
83+.center::before {
84+ background: var(--secondary-glow);
85+ border-radius: 50%;
86+ width: 480px;
87+ height: 360px;
88+ margin-left: -400px;
89+}
90+
91+.center::after {
92+ background: var(--primary-glow);
93+ width: 240px;
94+ height: 180px;
95+ z-index: -1;
96+}
97+
98+.center::before,
99+.center::after {
100+ content: '';
101+ left: 50%;
102+ position: absolute;
103+ filter: blur(45px);
104+ transform: translateZ(0);
105+}
106+
107+.logo,
108+.thirteen {
109+ position: relative;
110+}
111+
112+.thirteen {
113+ display: flex;
114+ justify-content: center;
115+ align-items: center;
116+ width: 75px;
117+ height: 75px;
118+ padding: 25px 10px;
119+ margin-left: 16px;
120+ transform: translateZ(0);
121+ border-radius: var(--border-radius);
122+ overflow: hidden;
123+ box-shadow: 0px 2px 8px -1px #0000001a;
124+}
125+
126+.thirteen::before,
127+.thirteen::after {
128+ content: '';
129+ position: absolute;
130+ z-index: -1;
131+}
132+
133+/* Conic Gradient Animation */
134+.thirteen::before {
135+ animation: 6s rotate linear infinite;
136+ width: 200%;
137+ height: 200%;
138+ background: var(--tile-border);
139+}
140+
141+/* Inner Square */
142+.thirteen::after {
143+ inset: 0;
144+ padding: 1px;
145+ border-radius: var(--border-radius);
146+ background: linear-gradient(
147+ to bottom right,
148+ rgba(var(--tile-start-rgb), 1),
149+ rgba(var(--tile-end-rgb), 1)
150+ );
151+ background-clip: content-box;
152+}
153+
154+/* Enable hover only on non-touch devices */
155+@media (hover: hover) and (pointer: fine) {
156+ .card:hover {
157+ background: rgba(var(--card-rgb), 0.1);
158+ border: 1px solid rgba(var(--card-border-rgb), 0.15);
159+ }
160+
161+ .card:hover span {
162+ transform: translateX(4px);
163+ }
164+}
165+
166+@media (prefers-reduced-motion) {
167+ .thirteen::before {
168+ animation: none;
169+ }
170+
171+ .card:hover span {
172+ transform: none;
173+ }
174+}
175+
176+/* Mobile */
177+@media (max-width: 700px) {
178+ .content {
179+ padding: 4rem;
180+ }
181+
182+ .grid {
183+ grid-template-columns: 1fr;
184+ margin-bottom: 120px;
185+ max-width: 320px;
186+ text-align: center;
187+ }
188+
189+ .card {
190+ padding: 1rem 2.5rem;
191+ }
192+
193+ .card h2 {
194+ margin-bottom: 0.5rem;
195+ }
196+
197+ .center {
198+ padding: 8rem 0 6rem;
199+ }
200+
201+ .center::before {
202+ transform: none;
203+ height: 300px;
204+ }
205+
206+ .description {
207+ font-size: 0.8rem;
208+ }
209+
210+ .description a {
211+ padding: 1rem;
212+ }
213+
214+ .description p,
215+ .description div {
216+ display: flex;
217+ justify-content: center;
218+ position: fixed;
219+ width: 100%;
220+ }
221+
222+ .description p {
223+ align-items: center;
224+ inset: 0 0 auto;
225+ padding: 2rem 1rem 1.4rem;
226+ border-radius: 0;
227+ border: none;
228+ border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
229+ background: linear-gradient(
230+ to bottom,
231+ rgba(var(--background-start-rgb), 1),
232+ rgba(var(--callout-rgb), 0.5)
233+ );
234+ background-clip: padding-box;
235+ backdrop-filter: blur(24px);
236+ }
237+
238+ .description div {
239+ align-items: flex-end;
240+ pointer-events: none;
241+ inset: auto 0 0;
242+ padding: 2rem;
243+ height: 200px;
244+ background: linear-gradient(
245+ to bottom,
246+ transparent 0%,
247+ rgb(var(--background-end-rgb)) 40%
248+ );
249+ z-index: 1;
250+ }
251+}
252+
253+/* Tablet and Smaller Desktop */
254+@media (min-width: 701px) and (max-width: 1120px) {
255+ .grid {
256+ grid-template-columns: repeat(2, 50%);
257+ }
258+}
259+
260+@media (prefers-color-scheme: dark) {
261+ .vercelLogo {
262+ filter: invert(1);
263+ }
264+
265+ .logo,
266+ .thirteen img {
267+ filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
268+ }
269+}
270+
271+@keyframes rotate {
272+ from {
273+ transform: rotate(360deg);
274+ }
275+ to {
276+ transform: rotate(0deg);
277+ }
278+}
+107-0frontend/styles/globals.css
@@ -0,0 +1,107 @@
1+:root {
2+ --max-width: 1100px;
3+ --border-radius: 12px;
4+ --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
5+ 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
6+ 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
7+
8+ --foreground-rgb: 0, 0, 0;
9+ --background-start-rgb: 214, 219, 220;
10+ --background-end-rgb: 255, 255, 255;
11+
12+ --primary-glow: conic-gradient(
13+ from 180deg at 50% 50%,
14+ #16abff33 0deg,
15+ #0885ff33 55deg,
16+ #54d6ff33 120deg,
17+ #0071ff33 160deg,
18+ transparent 360deg
19+ );
20+ --secondary-glow: radial-gradient(
21+ rgba(255, 255, 255, 1),
22+ rgba(255, 255, 255, 0)
23+ );
24+
25+ --tile-start-rgb: 239, 245, 249;
26+ --tile-end-rgb: 228, 232, 233;
27+ --tile-border: conic-gradient(
28+ #00000080,
29+ #00000040,
30+ #00000030,
31+ #00000020,
32+ #00000010,
33+ #00000010,
34+ #00000080
35+ );
36+
37+ --callout-rgb: 238, 240, 241;
38+ --callout-border-rgb: 172, 175, 176;
39+ --card-rgb: 180, 185, 188;
40+ --card-border-rgb: 131, 134, 135;
41+}
42+
43+@media (prefers-color-scheme: dark) {
44+ :root {
45+ --foreground-rgb: 255, 255, 255;
46+ --background-start-rgb: 0, 0, 0;
47+ --background-end-rgb: 0, 0, 0;
48+
49+ --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
50+ --secondary-glow: linear-gradient(
51+ to bottom right,
52+ rgba(1, 65, 255, 0),
53+ rgba(1, 65, 255, 0),
54+ rgba(1, 65, 255, 0.3)
55+ );
56+
57+ --tile-start-rgb: 2, 13, 46;
58+ --tile-end-rgb: 2, 5, 19;
59+ --tile-border: conic-gradient(
60+ #ffffff80,
61+ #ffffff40,
62+ #ffffff30,
63+ #ffffff20,
64+ #ffffff10,
65+ #ffffff10,
66+ #ffffff80
67+ );
68+
69+ --callout-rgb: 20, 20, 20;
70+ --callout-border-rgb: 108, 108, 108;
71+ --card-rgb: 100, 100, 100;
72+ --card-border-rgb: 200, 200, 200;
73+ }
74+}
75+
76+* {
77+ box-sizing: border-box;
78+ padding: 0;
79+ margin: 0;
80+}
81+
82+html,
83+body {
84+ max-width: 100vw;
85+ overflow-x: hidden;
86+}
87+
88+body {
89+ color: rgb(var(--foreground-rgb));
90+ background: linear-gradient(
91+ to bottom,
92+ transparent,
93+ rgb(var(--background-end-rgb))
94+ )
95+ rgb(var(--background-start-rgb));
96+}
97+
98+a {
99+ color: inherit;
100+ text-decoration: none;
101+}
102+
103+@media (prefers-color-scheme: dark) {
104+ html {
105+ color-scheme: dark;
106+ }
107+}
+20-0frontend/tsconfig.json
@@ -0,0 +1,20 @@
1+{
2+ "compilerOptions": {
3+ "target": "es5",
4+ "lib": ["dom", "dom.iterable", "esnext"],
5+ "allowJs": true,
6+ "skipLibCheck": true,
7+ "strict": true,
8+ "forceConsistentCasingInFileNames": true,
9+ "noEmit": true,
10+ "esModuleInterop": true,
11+ "module": "esnext",
12+ "moduleResolution": "node",
13+ "resolveJsonModule": true,
14+ "isolatedModules": true,
15+ "jsx": "preserve",
16+ "incremental": true
17+ },
18+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19+ "exclude": ["node_modules"]
20+}