From 198ed18cee2b977814e1eb7b0ebbfb80b9edb061 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1?= <git@imaniti.org>
Date: Tue, 3 Jan 2023 11:28:21 +0100
Subject: [PATCH] project base

---
 .gitignore                     |   7 +
 Makefile                       |  48 +++
 env.example                    |   3 +
 manage.py                      |  22 ++
 package.json                   |  23 ++
 requirements/base.txt          |   6 +
 rybicka/__init__.py            |   0
 rybicka/asgi.py                |  16 +
 rybicka/settings/base.py       | 136 +++++++++
 rybicka/settings/dev.py        |  10 +
 rybicka/urls.py                |  21 ++
 rybicka/wsgi.py                |  16 +
 shared/__init__.py             |   0
 shared/admin.py                |   1 +
 shared/apps.py                 |   6 +
 shared/models.py               |   3 +
 shared/static/shared/style.css | 524 +++++++++++++++++++++++++++++++++
 shared/tests.py                |   3 +
 shared/urls.py                 |   5 +
 shared/views.py                |   3 +
 static_src/base.css            |  19 ++
 static_src/base.js             |   0
 tailwind.config.js             |  18 ++
 webpack.config.js              |  23 ++
 24 files changed, 913 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Makefile
 create mode 100644 env.example
 create mode 100755 manage.py
 create mode 100644 package.json
 create mode 100644 requirements/base.txt
 create mode 100644 rybicka/__init__.py
 create mode 100644 rybicka/asgi.py
 create mode 100644 rybicka/settings/base.py
 create mode 100644 rybicka/settings/dev.py
 create mode 100644 rybicka/urls.py
 create mode 100644 rybicka/wsgi.py
 create mode 100644 shared/__init__.py
 create mode 100644 shared/admin.py
 create mode 100644 shared/apps.py
 create mode 100644 shared/models.py
 create mode 100644 shared/static/shared/style.css
 create mode 100644 shared/tests.py
 create mode 100644 shared/urls.py
 create mode 100644 shared/views.py
 create mode 100644 static_src/base.css
 create mode 100644 static_src/base.js
 create mode 100644 tailwind.config.js
 create mode 100644 webpack.config.js

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9e27f6e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+__pycache__/
+node_modules/*
+staticfiles/*
+package-lock.json
+webpack-stats.json
+.env
+shared/static/shared/main-*.js
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..71f8ea7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,48 @@
+#!/usr/bin/make -f
+
+PYTHON   = python
+VENV     = .venv
+PORT     = 8009
+SETTINGS = rybicka.settings.dev
+
+.PHONY: help venv install build run shell migrations migrate
+
+help:
+	@echo "Setup:"
+	@echo "  venv           Setup virtual environment"
+	@echo "  install        Install dependencies to venv"
+	@echo "  build          Build CSS and JS files"
+	@echo ""
+	@echo "Application:"
+	@echo "  run            Run the application on port ${PORT}"
+	@echo "  shell          Access the Django shell"
+	@echo ""
+	@echo "Database:"
+	@echo "  migrations     Generate migrations"
+	@echo "  migrate        Run migrations"
+
+venv: .venv/bin/python
+.venv/bin/python:
+	${PYTHON} -m venv ${VENV}
+
+install: venv
+	${VENV}/bin/pip install -r requirements/base.txt
+	${VENV}/bin/nodeenv --python-virtualenv --node=19.3.0
+	${VENV}/bin/npm install
+
+
+build: venv
+	${VENV}/bin/npm run build
+	${VENV}/bin/python manage.py collectstatic --noinput --settings=${SETTINGS}
+
+run: venv
+	${VENV}/bin/python manage.py runserver ${PORT} --settings=${SETTINGS}
+
+shell: venv
+	${VENV}/bin/python manage.py shell --settings=${SETTINGS}
+
+migrations: venv
+	${VENV}/bin/python manage.py makemigrations --settings=${SETTINGS}
+
+migrate: venv
+	${VENV}/bin/python manage.py migrate --settings=${SETTINGS}
diff --git a/env.example b/env.example
new file mode 100644
index 0000000..f1dd3ea
--- /dev/null
+++ b/env.example
@@ -0,0 +1,3 @@
+DATABASE_URL="postgresql://rybicka:rybicka@localhost:5432/rybicka"
+
+SECRET_KEY="%@=^sip3=tqn6d_-xvvidc1@-t0t3&*kab@vr4c4"
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..3580e5f
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rybicka.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..25d4280
--- /dev/null
+++ b/package.json
@@ -0,0 +1,23 @@
+{
+  "name": "rybicka",
+  "version": "0.0.1",
+  "description": "",
+  "private": true,
+  "dependencies": {
+    "css-loader": "^6.7.3",
+    "style-loader": "^3.3.1",
+    "tailwindcss": "^3.2.4",
+    "webpack": "^5.75.0",
+    "webpack-bundle-tracker": "^1.8.0",
+    "webpack-cli": "^5.0.1"
+  },
+  "scripts": {
+    "build": "npx tailwindcss -i ./static_src/base.css -o ./shared/static/shared/style.css && npx webpack build"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@git.imaniti.org:Tomas/Rybicka.git"
+  },
+  "author": "Tomáš Valenta",
+  "license": "AGPL-3.0-or-later"
+}
diff --git a/requirements/base.txt b/requirements/base.txt
new file mode 100644
index 0000000..ca71420
--- /dev/null
+++ b/requirements/base.txt
@@ -0,0 +1,6 @@
+Django==4.1.5
+django-database-url==1.0.3
+python-dotenv==0.21.0
+psycopg2-binary==2.9.5
+django-webpack-loader==1.8.0
+nodeenv==1.7.0
diff --git a/rybicka/__init__.py b/rybicka/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/rybicka/asgi.py b/rybicka/asgi.py
new file mode 100644
index 0000000..d7176f0
--- /dev/null
+++ b/rybicka/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for rybicka project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rybicka.settings')
+
+application = get_asgi_application()
diff --git a/rybicka/settings/base.py b/rybicka/settings/base.py
new file mode 100644
index 0000000..254c26b
--- /dev/null
+++ b/rybicka/settings/base.py
@@ -0,0 +1,136 @@
+"""
+Django settings for rybicka project.
+
+Generated by 'django-admin startproject' using Django 4.1.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.1/ref/settings/
+"""
+
+import os
+import pathlib
+
+import dj_database_url
+import dotenv
+
+dotenv.load_dotenv()
+
+# Build paths inside the project like this: BASE_DIR / "subdir".
+BASE_DIR = pathlib.Path(__file__).parents[2]
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
+
+DEBUG = False
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = os.environ["SECRET_KEY"]
+
+ALLOWED_HOSTS = []
+
+STATIC_ROOT = os.environ.get(
+    "STATIC_ROOT",
+    "staticfiles"
+)
+
+# Application definition
+
+INSTALLED_APPS = [
+    "django.contrib.admin",
+    "django.contrib.auth",
+    "django.contrib.contenttypes",
+    "django.contrib.sessions",
+    "django.contrib.messages",
+    "django.contrib.staticfiles",
+
+	"shared"
+]
+
+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 = "rybicka.urls"
+
+TEMPLATES = [
+    {
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
+        "DIRS": [],
+        "APP_DIRS": True,
+        "OPTIONS": {
+            "context_processors": [
+                "django.template.context_processors.debug",
+                "django.template.context_processors.request",
+                "django.contrib.auth.context_processors.auth",
+                "django.contrib.messages.context_processors.messages",
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = "rybicka.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
+
+DATABASES = {
+    "default": dj_database_url.config(conn_max_age=600)
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/4.1/topics/i18n/
+
+LANGUAGE_CODE = "cs-cz"
+TIME_ZONE = "Europe/Prague"
+USE_I18N = True
+USE_TZ = True
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.1/howto/static-files/
+
+STATIC_URL = "static/"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+
+WEBPACK_LOADER = {
+    "DEFAULT": {
+        "CACHE": not DEBUG,
+        "BUNDLE_DIR_NAME": "shared",
+        "STATS_FILE": os.path.join(BASE_DIR, "webpack-stats.json"),
+        "POLL_INTERVAL": 0.1,
+        "IGNORE": [r".+\.hot-update.js", r".+\.map"],
+    }
+}
diff --git a/rybicka/settings/dev.py b/rybicka/settings/dev.py
new file mode 100644
index 0000000..edd6357
--- /dev/null
+++ b/rybicka/settings/dev.py
@@ -0,0 +1,10 @@
+"""
+Development settings.
+
+SECURITY WARNING: These should absolutely never be used in production no matter
+what!
+"""
+
+from .base import *
+
+DEBUG = True 
diff --git a/rybicka/urls.py b/rybicka/urls.py
new file mode 100644
index 0000000..8702dbc
--- /dev/null
+++ b/rybicka/urls.py
@@ -0,0 +1,21 @@
+"""rybicka URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/4.1/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+]
diff --git a/rybicka/wsgi.py b/rybicka/wsgi.py
new file mode 100644
index 0000000..8cfda5e
--- /dev/null
+++ b/rybicka/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for rybicka project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rybicka.settings')
+
+application = get_wsgi_application()
diff --git a/shared/__init__.py b/shared/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/shared/admin.py b/shared/admin.py
new file mode 100644
index 0000000..694323f
--- /dev/null
+++ b/shared/admin.py
@@ -0,0 +1 @@
+from django.contrib import admin
diff --git a/shared/apps.py b/shared/apps.py
new file mode 100644
index 0000000..5bfd568
--- /dev/null
+++ b/shared/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class SharedConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "shared"
diff --git a/shared/models.py b/shared/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/shared/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/shared/static/shared/style.css b/shared/static/shared/style.css
new file mode 100644
index 0000000..d51d612
--- /dev/null
+++ b/shared/static/shared/style.css
@@ -0,0 +1,524 @@
+/*
+! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+  box-sizing: border-box;
+  /* 1 */
+  border-width: 0;
+  /* 2 */
+  border-style: solid;
+  /* 2 */
+  border-color: #e5e7eb;
+  /* 2 */
+}
+
+::before,
+::after {
+  --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+5. Use the user's configured `sans` font-feature-settings by default.
+*/
+
+html {
+  line-height: 1.5;
+  /* 1 */
+  -webkit-text-size-adjust: 100%;
+  /* 2 */
+  -moz-tab-size: 4;
+  /* 3 */
+  -o-tab-size: 4;
+     tab-size: 4;
+  /* 3 */
+  font-family: Roboto Condensed, ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+  /* 4 */
+  font-feature-settings: normal;
+  /* 5 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+  margin: 0;
+  /* 1 */
+  line-height: inherit;
+  /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+  height: 0;
+  /* 1 */
+  color: inherit;
+  /* 2 */
+  border-top-width: 1px;
+  /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+  -webkit-text-decoration: underline dotted;
+          text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-size: inherit;
+  font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+  color: inherit;
+  text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font family by default.
+2. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  /* 1 */
+  font-size: 1em;
+  /* 2 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+  font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+  text-indent: 0;
+  /* 1 */
+  border-color: inherit;
+  /* 2 */
+  border-collapse: collapse;
+  /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit;
+  /* 1 */
+  font-size: 100%;
+  /* 1 */
+  font-weight: inherit;
+  /* 1 */
+  line-height: inherit;
+  /* 1 */
+  color: inherit;
+  /* 1 */
+  margin: 0;
+  /* 2 */
+  padding: 0;
+  /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+  text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+[type='button'],
+[type='reset'],
+[type='submit'] {
+  -webkit-appearance: button;
+  /* 1 */
+  background-color: transparent;
+  /* 2 */
+  background-image: none;
+  /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+  outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+  box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+  vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+  -webkit-appearance: textfield;
+  /* 1 */
+  outline-offset: -2px;
+  /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button;
+  /* 1 */
+  font: inherit;
+  /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+  display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+  margin: 0;
+}
+
+fieldset {
+  margin: 0;
+  padding: 0;
+}
+
+legend {
+  padding: 0;
+}
+
+ol,
+ul,
+menu {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+  resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+  opacity: 1;
+  /* 1 */
+  color: #9ca3af;
+  /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+  opacity: 1;
+  /* 1 */
+  color: #9ca3af;
+  /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+  cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+  cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+   This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+  display: block;
+  /* 1 */
+  vertical-align: middle;
+  /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+  max-width: 100%;
+  height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+
+[hidden] {
+  display: none;
+}
+
+html {
+  font-family: "Roboto Condensed", system-ui, sans-serif;
+}
+
+*, ::before, ::after {
+  --tw-border-spacing-x: 0;
+  --tw-border-spacing-y: 0;
+  --tw-translate-x: 0;
+  --tw-translate-y: 0;
+  --tw-rotate: 0;
+  --tw-skew-x: 0;
+  --tw-skew-y: 0;
+  --tw-scale-x: 1;
+  --tw-scale-y: 1;
+  --tw-pan-x:  ;
+  --tw-pan-y:  ;
+  --tw-pinch-zoom:  ;
+  --tw-scroll-snap-strictness: proximity;
+  --tw-ordinal:  ;
+  --tw-slashed-zero:  ;
+  --tw-numeric-figure:  ;
+  --tw-numeric-spacing:  ;
+  --tw-numeric-fraction:  ;
+  --tw-ring-inset:  ;
+  --tw-ring-offset-width: 0px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: rgb(59 130 246 / 0.5);
+  --tw-ring-offset-shadow: 0 0 #0000;
+  --tw-ring-shadow: 0 0 #0000;
+  --tw-shadow: 0 0 #0000;
+  --tw-shadow-colored: 0 0 #0000;
+  --tw-blur:  ;
+  --tw-brightness:  ;
+  --tw-contrast:  ;
+  --tw-grayscale:  ;
+  --tw-hue-rotate:  ;
+  --tw-invert:  ;
+  --tw-saturate:  ;
+  --tw-sepia:  ;
+  --tw-drop-shadow:  ;
+  --tw-backdrop-blur:  ;
+  --tw-backdrop-brightness:  ;
+  --tw-backdrop-contrast:  ;
+  --tw-backdrop-grayscale:  ;
+  --tw-backdrop-hue-rotate:  ;
+  --tw-backdrop-invert:  ;
+  --tw-backdrop-opacity:  ;
+  --tw-backdrop-saturate:  ;
+  --tw-backdrop-sepia:  ;
+}
+
+::backdrop {
+  --tw-border-spacing-x: 0;
+  --tw-border-spacing-y: 0;
+  --tw-translate-x: 0;
+  --tw-translate-y: 0;
+  --tw-rotate: 0;
+  --tw-skew-x: 0;
+  --tw-skew-y: 0;
+  --tw-scale-x: 1;
+  --tw-scale-y: 1;
+  --tw-pan-x:  ;
+  --tw-pan-y:  ;
+  --tw-pinch-zoom:  ;
+  --tw-scroll-snap-strictness: proximity;
+  --tw-ordinal:  ;
+  --tw-slashed-zero:  ;
+  --tw-numeric-figure:  ;
+  --tw-numeric-spacing:  ;
+  --tw-numeric-fraction:  ;
+  --tw-ring-inset:  ;
+  --tw-ring-offset-width: 0px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: rgb(59 130 246 / 0.5);
+  --tw-ring-offset-shadow: 0 0 #0000;
+  --tw-ring-shadow: 0 0 #0000;
+  --tw-shadow: 0 0 #0000;
+  --tw-shadow-colored: 0 0 #0000;
+  --tw-blur:  ;
+  --tw-brightness:  ;
+  --tw-contrast:  ;
+  --tw-grayscale:  ;
+  --tw-hue-rotate:  ;
+  --tw-invert:  ;
+  --tw-saturate:  ;
+  --tw-sepia:  ;
+  --tw-drop-shadow:  ;
+  --tw-backdrop-blur:  ;
+  --tw-backdrop-brightness:  ;
+  --tw-backdrop-contrast:  ;
+  --tw-backdrop-grayscale:  ;
+  --tw-backdrop-hue-rotate:  ;
+  --tw-backdrop-invert:  ;
+  --tw-backdrop-opacity:  ;
+  --tw-backdrop-saturate:  ;
+  --tw-backdrop-sepia:  ;
+}
+
+@layer typography {
+  .font-bebas {
+    font-family: "Bebas Neue";
+  }
+}
diff --git a/shared/tests.py b/shared/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/shared/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/shared/urls.py b/shared/urls.py
new file mode 100644
index 0000000..f5e3311
--- /dev/null
+++ b/shared/urls.py
@@ -0,0 +1,5 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = []
diff --git a/shared/views.py b/shared/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/shared/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/static_src/base.css b/static_src/base.css
new file mode 100644
index 0000000..0462d9f
--- /dev/null
+++ b/static_src/base.css
@@ -0,0 +1,19 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+
+@layer base {
+  html {
+    font-family: "Roboto Condensed", system-ui, sans-serif;
+  }
+}
+
+
+@layer typography {
+    .font-bebas {
+        font-family: "Bebas Neue";
+    }
+}
+
+@import url("https://gfonts.pirati.cz/css2?family=Bebas+Neue&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap");
diff --git a/static_src/base.js b/static_src/base.js
new file mode 100644
index 0000000..e69de29
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..6d9541c
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,18 @@
+const defaultTheme = require('tailwindcss/defaultTheme')
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+  content: [
+    "*/templates/*/*.html",
+    "*/templates/*/*/*.html",
+  ],
+  theme: {
+    extend: {
+      fontFamily: {
+        "bebas": ["Bebas Neue", defaultTheme.fontFamily.sans],
+        "sans": ["Roboto Condensed", defaultTheme.fontFamily.sans],
+      },
+    },
+  },
+  plugins: [],
+}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..43801f4
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,23 @@
+const path = require('path');
+const BundleTracker = require('webpack-bundle-tracker');
+
+module.exports = {
+  mode: "production",
+  context: __dirname,
+  entry: path.resolve("static_src", "base.js"),
+  output: {
+    path: path.resolve(__dirname, "shared", "static", "shared"),
+    filename: "[name]-[fullhash].js",
+  },
+  module: {
+    rules: [
+      {
+        test: /\.css$/i,
+        use: ["style-loader", "css-loader"],
+      },
+    ],
+  },
+  plugins: [
+    new BundleTracker({filename: './webpack-stats.json'})
+  ],
+};
-- 
GitLab