diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d6613ebc0169817a4a61bd626f64d8a0a6867a6e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,48 @@
+#!/usr/bin/make -f
+
+PYTHON   = python
+VENV     = .venv
+PORT     = 8012
+SETTINGS = registry.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 -r requirements/production.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
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f3ee301e15c0806c42e59a42f774f1ae9ee082db 100644
--- a/env.example
+++ b/env.example
@@ -0,0 +1 @@
+DEFAULT_SIGNING_PARTY_REPRESENTATIVE="Česká pirátská strana\nNa Moráni 360/3\n128 00 Praha 2\nIČ: 71339698\nDIČ: CZ71339698"
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..5675a2e01fbf86888a2003cc1a1bebc8438336c6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+  "name": "contract-registry",
+  "version": "0.0.1",
+  "description": "",
+  "scripts": {
+    "build": "npx tailwindcss -i ./static_src/base.css -o ./shared/static/shared/style.css && npx webpack build"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@gitlab.pirati.cz:to/contract-registry.git"
+  },
+  "author": "Tomáš Valenta",
+  "license": "AGPL-3.0-or-later",
+  "dependencies": {
+    "css-loader": "^6.7.3",
+    "jquery": "^3.6.3",
+    "tailwindcss": "^3.2.4",
+    "webpack": "^5.75.0",
+    "webpack-bundle-tracker": "^1.8.0",
+    "webpack-cli": "^5.0.1"
+  }
+}
diff --git a/registry/manage.py b/registry/manage.py
new file mode 100755
index 0000000000000000000000000000000000000000..0e1442be86443cb560d236a8317bc4f0f1416563
--- /dev/null
+++ b/registry/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', 'registry.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/registry/registry/__init__.py b/registry/registry/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/registry/registry/asgi.py b/registry/registry/asgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1d1af068c04c244397d345195498fa0d029ccb0
--- /dev/null
+++ b/registry/registry/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for registry 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.0/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'registry.settings')
+
+application = get_asgi_application()
diff --git a/registry/registry/settings/base.py b/registry/registry/settings/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..6173ee261fe0ea98d32e70e4c32aeeaf32456ed8
--- /dev/null
+++ b/registry/registry/settings/base.py
@@ -0,0 +1,126 @@
+"""
+Django settings for registry project.
+
+Generated by 'django-admin startproject' using Django 4.0.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.0/ref/settings/
+"""
+
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
+
+DEBUG = False
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = env.str("SECRET_KEY")
+
+ALLOWED_HOSTS = []
+
+STATIC_ROOT = os.path.join(BASE_DIR, "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 = "registry.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 = "registry.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
+
+DATABASES = {
+    "default": {
+        "ENGINE": "django.db.backends.sqlite3",
+        "NAME": BASE_DIR / "db.sqlite3",
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/4.0/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.0/topics/i18n/
+
+LANGUAGE_CODE = "en-us"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.0/howto/static-files/
+
+STATIC_URL = "static/"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
diff --git a/registry/registry/settings/production.py b/registry/registry/settings/production.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8e79df9e8af96e1f8610c856e60ada1189c5c34
--- /dev/null
+++ b/registry/registry/settings/production.py
@@ -0,0 +1 @@
+from .base import * 
diff --git a/registry/registry/urls.py b/registry/registry/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..13c37f72e62a07f75bfabf6da7f9aba104c04723
--- /dev/null
+++ b/registry/registry/urls.py
@@ -0,0 +1,21 @@
+"""registry URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/4.0/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/registry/registry/wsgi.py b/registry/registry/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..06fb2293640440935875f76745d3cc20fed4487e
--- /dev/null
+++ b/registry/registry/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for registry 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.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'registry.settings')
+
+application = get_wsgi_application()
diff --git a/requirements/base.txt b/requirements/base.txt
new file mode 100644
index 0000000000000000000000000000000000000000..51af94fe4596c5d69ea0b778a705e673e3e31f30
--- /dev/null
+++ b/requirements/base.txt
@@ -0,0 +1,11 @@
+django==4.1.4
+django-database-url==1.0.3
+psycopg2-binary==2.9.5
+django-webpack-loader==1.8.0
+nodeenv==1.7.0
+pirates==0.6.0
+django-markdownx==4.0.0b1
+django-environ==0.9.0
+django-http-exceptions==1.4.0 
+django-guardian==2.4.0
+django-countries==7.5.1
diff --git a/requirements/production.txt b/requirements/production.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/shared/__init__.py b/shared/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/shared/admin.py b/shared/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/shared/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/shared/apps.py b/shared/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..87efe0081f84bbf7e0a233e36ae7cdb836755f9e
--- /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/migrations/__init__.py b/shared/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/shared/models.py b/shared/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..8349f55a6685a465eff990b4c37309dd67b6bb08
--- /dev/null
+++ b/shared/models.py
@@ -0,0 +1,388 @@
+from django.conf import settings
+from django.db import models
+
+from django_countries.fields import CountryField
+from markdownx.models import MarkdownxField
+from pirates import models as pirates_models
+
+# Create your models here.
+
+
+class ContractExternalSigner(models.Model):
+    name = models.CharField(
+        max_length=256,
+        verbose_name="Jméno",
+    )
+
+
+    is_legal_entity = models.BooleanField(
+        verbose_name="Je právnická osoba",
+    )
+
+
+    address_street_with_number = models.CharField(
+        max_length=256,
+        verbose_name="Ulice, č.p.",
+    )  # WARNING: Legal entity status dependent!
+
+    address_district = models.CharField(
+        max_length=256,
+        verbose_name="Obec",
+    )
+
+    address_zip = models.CharField(
+        max_length=16,
+        verbose_name="PSČ",
+    )  # WARNING: Legal entity status dependent!
+
+    address_country = CountryField(
+        verbose_name="Země",
+    )
+
+
+    ico_number = models.CharField(
+        max_length=16,
+        blank=True,
+        null=True,
+        verbose_name="IČO",
+    )  # WARNING: Legal entity status dependent!
+
+    date_of_birth = models.DateField(
+        blank=True,
+        null=True,
+        verbose_name="Datum narození",
+    )  # WARNING: Legal entity status dependent!
+
+
+    representative_name = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Zástupce",
+    )
+
+    representative_role = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Funkce zástupce",
+    )
+
+
+    department = models.CharField(
+        max_length=128,
+        blank=True,
+        null=True,
+        verbose_name="Organizační složka",
+    )
+
+
+    class Meta:
+        verbose_name = "Druhá smluvní strana"
+        verbose_name_plural = "Druhé smluvní strany"
+
+
+class ContractLocalSigner(models.Model):
+    name = models.CharField(
+        max_length=256,
+        verbose_name="Jméno",
+    )
+
+
+    address_street_with_number = models.CharField(
+        max_length=256,
+        verbose_name="Ulice, č.p.",
+    )
+
+    address_district = models.CharField(
+        max_length=256,
+        verbose_name="Obec",
+    )
+
+    address_zip = models.CharField(
+        max_length=16,
+        verbose_name="PSČ",
+    )
+
+    address_country = CountryField(
+        verbose_name="Země",
+    )
+
+
+    ico_number = models.CharField(
+        max_length=16,
+        blank=True,
+        null=True,
+        verbose_name="IČO",
+    )
+
+
+    # TODO: Input validation
+    color = models.CharField(
+        max_length=6, # e.g. "ffffff"
+        verbose_name="Barva",
+    )
+
+
+    class Meta:
+        verbose_name = "Naše smluvní strana"
+        verbose_name_plural = "Naše smlouvní strany"
+
+
+class ContractSubtypes(models.Model):
+    name = models.CharField(
+        max_length=32,
+        verbose_name="Jméno",
+    )
+
+
+    class Meta:
+        verbose_name = "Podtyp smlouvy"
+        verbose_name_plural = "Podtypy smlouvy"
+
+
+class ContractIssue(models.Model):
+    name = models.CharField(
+        max_length=32,
+        verbose_name="Jméno",
+    )
+
+
+    class Meta:
+        verbose_name = "Problém se smlouvou"
+        verbose_name_plural = "Problémy se smlouvou"
+
+
+class ContractFilingArea(models.Model):
+    name = models.CharField(
+        max_length=32,
+        verbose_name="Jméno",
+    )
+
+    person_responsible = models.CharField(
+        max_length=256,
+        verbose_name="Odpovědná osoba",
+    )
+
+
+    class Meta:
+        verbose_name = "Spisovna"
+        verbose_name_plural = "Spisovny"
+
+
+class Contract(models.Model):
+    class ContractTypes(models.TextChoices):
+        PRIMARY = "Hlavní"
+        AMENDMENT = "Dodatek"
+        FRAMEWORK_ORDER = "Objednávka u rámcové smlouvy"
+
+    type_ = models.CharField(
+        max_length=len(ContractTypes.FRAMEWORK_ORDER),
+        default=ContractTypes.PRIMARY,
+        verbose_name="Typ",
+    )
+
+    subtype = models.ForeignKey(
+        ContractSubtype,
+        on_delete=models.CASCADE,
+        verbose_name="Podtyp",
+    )
+
+
+    contains_nda = models.BooleanField(
+        default=False,
+        verbose_name="Obsahuje NDA",
+    )
+
+    is_anonymized = models.BooleanField(
+        default=False,
+        verbose_name="Je anonymizovaná",
+    )  # WARNING: Seems to only be used for amendments
+
+
+    external_signer = models.ForeignKey(ContractExternalSigner)
+
+    # NOTE: Should we allow these to be null, if a contract is logged before it is signed?
+    external_signer_signature_date = models.DateField(
+        verbose_name="Datum podpisu druhé strany",
+    )
+
+    local_signer = models.ForeignKey(ContractLocalSigner)
+
+    local_signer_signature_date = models.DateField(
+        verbose_name="Datum podpisu naší strany",
+    )
+
+    all_parties_sign_date = models.DateField(
+        verbose_name="Datum podpisu všech stran",
+    )  # WARNING: Exclude in admin, autofill
+
+
+    valid_start_date = models.DateField(
+        verbose_name="Začátek účinnosti"
+    )
+    valid_end_date = models.DateField(
+        verbose_name="Začátek platnosti",
+    )
+
+
+    uploaded_by = models.ForeignKey(
+        pirates_models.User,
+        on_delete=models.CASCADE,
+        verbose_name="Nahráno uživatelem",
+    )
+
+
+    class LegalStates(models.TextChoices):
+        VALID = "Platná"
+        EFFECTIVE = "Účinná"
+        NOT_EFFECTIVE = "Neúčinná"
+        INVALID = "Neplatná"
+
+    class PublicStates(models.TextChoices):
+        WAITING = "Nová"
+        YES = "Zveřejněná"
+        NO = "Neveřejná"
+
+    class PaperFormStates(models.TextChoices):
+        SENT = "Odeslaná"
+        STORED = "Uložená"
+        TO_SHRED = "Ke skartaci"
+        SHREDDED = "Skartovaná"
+
+    legal_state = models.CharField(
+        max_length=len(LegalStates.NOT_EFFECTIVE),  # longest choice
+        choices=LegalStates,
+        verbose_name="Stav právního ujednání",
+    )
+
+    public_state = models.CharField(
+        max_length=len(PublicStates.YES),  # longest choice
+        choices=PublicStates,
+        verbose_name="Veřejnost smlouvy",
+    )
+
+    paper_form_state = models.CharField(
+        max_length=len(PaperFormStates.TO_SHRED),  # longest choice
+        choices=PaperFormStates,
+        verbose_name="Stav papírové formy",
+    )
+
+
+    public_status_set_by = models.ForeignKey(
+        pirates_models.User,
+        on_delete=models.CASCADE,
+        verbose_name="Zveřejněno / nezveřejněno uživatelem",
+    )
+
+
+    publishing_rejection_comment = models.CharField(
+        max_length=65536,
+        blank=True,
+        null=True,
+        verbose_name="Důvod nezveřejnění",
+    )
+
+
+    tender_url = models.URLField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Odkaz na výběrové řízení",
+    )
+
+
+    identifier = models.CharField(
+        max_length=128,
+        verbose_name="Identifikační číslo",
+    )
+
+
+    issues = modles.ManyToManyField(ContractIssue)
+
+
+    summary = models.CharField(
+        max_length=65536,
+        blank=True,
+        null=True,
+        verbose_name="Rekapitulace",
+    )
+
+
+    contract_file = models.FileField(
+        verbose_name="Smlouva (PDF)",
+    )
+
+    primary_contract = models.ForeignKey(
+        Contract,
+        blank=True,
+        null=True,
+        verbose_name="Hlavní smlouva",
+    )  # WARNING: Dependent on the type!
+
+
+    expected_cost_total = models.IntegerField(
+        verbose_name="Očekáváná celková cena"
+    )
+
+    expected_cost_year = models.IntegerField(
+        verbose_name="Očekáváná cena za rok"
+    )
+
+    expected_cost_month = models.IntegerField(
+        verbose_name="Očekáváná cena za měsíc"
+    )
+
+    expected_cost_hour = models.IntegerField(
+        verbose_name="Očekáváná cena za hodinu"
+    )
+
+
+    intent_url = models.URLField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Odkaz na záměr",
+    )
+
+    agreement_url = models.URLField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Odkaz na schválení",
+    )  # WARNING: Dependent on the type!
+
+
+    filing_area = models.ForeignKey(
+        ContractFilingArea,
+        blank=True,
+        null=True,
+    )  # WARNING: Dependent on the type!
+
+
+    class Meta:
+        verbose_name = "Smlouva"
+        verbose_name_plural = "Smlouvy"
+
+
+class ContractNote(models.Model):
+    contract = models.ForeignKey(Contract)
+
+
+    author = models.ForeignKey(
+        pirates_models.User,
+        verbose_name="Autor",
+    )
+
+    created_date = models.DateTimeField(
+        verbose_name="Datum vytvoření",
+    )
+
+    content = models.MarkdownxField(
+        verbose_name="Obsah",
+    )
+
+
+    class Meta:
+        verbose_name = "Poznámka ke smlouvě"
+        verbose_name_plural = "Poznámky ke smlouvě"
diff --git a/shared/tests.py b/shared/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/shared/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/shared/views.py b/shared/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..91ea44a218fbd2f408430959283f0419c921093e
--- /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 0000000000000000000000000000000000000000..44f1cb603a7646e785005d3627740563ef4fb9e4
--- /dev/null
+++ b/static_src/base.css
@@ -0,0 +1,17 @@
+@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";
+    }
+}
diff --git a/static_src/base.js b/static_src/base.js
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..882ff8fc6f21218aac61604077d8e0cdd47ff91e
--- /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-stats.json b/webpack-stats.json
new file mode 100644
index 0000000000000000000000000000000000000000..3e296a93a0be9d13148309d286fffa4781fc9b02
--- /dev/null
+++ b/webpack-stats.json
@@ -0,0 +1,30 @@
+{
+  "status": "done",
+  "assets": {
+    "base-1150289fada5f20f5bd0.js": {
+      "name": "base-1150289fada5f20f5bd0.js",
+      "path": "/home/user/Projects/contract-registry/shared/static/shared/base-1150289fada5f20f5bd0.js"
+    },
+    "runtime-1150289fada5f20f5bd0.js": {
+      "name": "runtime-1150289fada5f20f5bd0.js",
+      "path": "/home/user/Projects/contract-registry/shared/static/shared/runtime-1150289fada5f20f5bd0.js"
+    },
+    "shared-1150289fada5f20f5bd0.js": {
+      "name": "shared-1150289fada5f20f5bd0.js",
+      "path": "/home/user/Projects/contract-registry/shared/static/shared/shared-1150289fada5f20f5bd0.js"
+    },
+    "shared-1150289fada5f20f5bd0.js.LICENSE.txt": {
+      "name": "shared-1150289fada5f20f5bd0.js.LICENSE.txt",
+      "path": "/home/user/Projects/contract-registry/shared/static/shared/shared-1150289fada5f20f5bd0.js.LICENSE.txt"
+    }
+  },
+  "chunks": {
+    "base": [
+      "base-1150289fada5f20f5bd0.js"
+    ],
+    "shared": [
+      "runtime-1150289fada5f20f5bd0.js",
+      "shared-1150289fada5f20f5bd0.js"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..5fd34684fb877b918b136bc58fd1e1fe949db07d
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,32 @@
+const path = require('path');
+const BundleTracker = require('webpack-bundle-tracker');
+
+module.exports = {
+  mode: "production",
+  context: __dirname,
+  entry: {
+    base: {
+      import: path.resolve("static_src", "base.js"),
+      dependOn: "shared",
+    },
+    shared: ["jquery"],
+  },
+  output: {
+    path: path.resolve(__dirname, "shared", "static", "shared"),
+    filename: "[name]-[fullhash].js",
+  },
+  module: {
+    rules: [
+      {
+        test: /\.css$/i,
+        use: ["style-loader", "css-loader"],
+      },
+    ],
+  },
+  optimization: {
+    runtimeChunk: "single",
+  },
+  plugins: [
+    new BundleTracker({filename: './webpack-stats.json'})
+  ],
+};