diff --git a/elections2021/constants.py b/elections2021/constants.py
index cc3e9c8abdc0c7013c2f27361823e551d69ec20e..9f424d7683b4deedf6fe06f825902cac99dbd954 100644
--- a/elections2021/constants.py
+++ b/elections2021/constants.py
@@ -111,25 +111,17 @@ NATURE = "nature"
 SPORT = "sport"
 HEALTH = "health"
 CULTURE = "culture"
-COMPUTERS = "computers"
+TECHNOLOGY = "technology"
 COUNTRYSIDE = "countryside"
 HOUSING = "housing"
 EDUCATION = "education"
 
-TARGET_CHOICES = (
-    (CHILDLESS, "bezdětní"),
-    (PARENTS, "rodiče"),
-    (MATURE, "zralí"),
-    (SENIORS, "senioři"),
-    (WORKING_SENIORS, "pracující senioři"),
-    (STUDENTS, "studenti"),
-    (SELF_EMPLOYED, "OSVČ"),
-    (SOCIALLY_WEAK, "sociálně slabí"),
+TOPIC_CHOICES = (
     (NATURE, "příroda"),
     (SPORT, "sport"),
     (HEALTH, "zdraví"),
     (CULTURE, "kultura"),
-    (COMPUTERS, "počítače"),
+    (TECHNOLOGY, "technologie"),
     (COUNTRYSIDE, "regiony"),
     (HOUSING, "bydlení"),
     (EDUCATION, "vzdělávání"),
@@ -237,3 +229,39 @@ BENEFITS = (
 )
 
 BENEFITS_CHOICES = [(num, name) for num, name, _ in BENEFITS]
+
+AGE_18_29 = "18-29"
+AGE_30_49 = "30-49"
+AGE_50_64 = "50-64"
+AGE_65_PLUS = "65-plus"
+
+AGE_CHOICES = (
+    (AGE_18_29, "18 - 29 let"),
+    (AGE_30_49, "30 - 49 let"),
+    (AGE_50_64, "50 - 64 let"),
+    (AGE_65_PLUS, "65+ let"),
+)
+
+WEALTH_BAD = "bad"
+WEALTH_AVERAGE = "average"
+WEALTH_GOOD = "good"
+
+WEALTH_CHOICES = (
+    (WEALTH_BAD, "nic moc"),
+    (WEALTH_AVERAGE, "průměrně"),
+    (WEALTH_GOOD, "skvěle"),
+)
+
+OCCUPATION_STUDENT = "student"
+OCCUPATION_WORKING = "working"
+OCCUPATION_BUSINESS = "business"
+OCCUPATION_PARENTING = "parenting"
+OCCUPATION_RETIRED = "retired"
+
+OCCUPATION_CHOICES = (
+    (OCCUPATION_STUDENT, "studuji"),
+    (OCCUPATION_WORKING, "pracuji"),
+    (OCCUPATION_BUSINESS, "podnikám"),
+    (OCCUPATION_PARENTING, "na rodičovské"),
+    (OCCUPATION_RETIRED, "v důchodu"),
+)
diff --git a/elections2021/forms.py b/elections2021/forms.py
index 096236346e0fa0a3457e018b5974407f521c68bf..2743ffd0a8e1b2d58295c307830ee886498b3599 100644
--- a/elections2021/forms.py
+++ b/elections2021/forms.py
@@ -4,6 +4,15 @@ from django import forms
 from django.utils.text import slugify
 from wagtail.admin.forms import WagtailAdminPageForm
 
+from .constants import (
+    AGE_30_49,
+    AGE_CHOICES,
+    OCCUPATION_CHOICES,
+    OCCUPATION_WORKING,
+    TOPIC_CHOICES,
+    WEALTH_AVERAGE,
+    WEALTH_CHOICES,
+)
 from .parser import (
     parse_program_html,
     prepare_benefit_for_all,
@@ -68,3 +77,13 @@ class ProgramPointPageForm(WagtailAdminPageForm):
             page.save()
 
         return page
+
+
+class ProgramAppForm(forms.Form):
+    age = forms.ChoiceField(choices=AGE_CHOICES, initial=AGE_30_49)
+    kids = forms.BooleanField(required=False, initial=True)
+    wealth = forms.ChoiceField(choices=WEALTH_CHOICES, initial=WEALTH_AVERAGE)
+    occupation = forms.ChoiceField(
+        choices=OCCUPATION_CHOICES, initial=OCCUPATION_WORKING
+    )
+    topics = forms.MultipleChoiceField(required=False, choices=TOPIC_CHOICES)
diff --git a/elections2021/management/commands/import_matrix.py b/elections2021/management/commands/import_matrix.py
index e069f3d958a2d6c8e5f6488cb1acd50a8bfc98af..f80f41df3df128967724b12726de3523a3c35ba5 100644
--- a/elections2021/management/commands/import_matrix.py
+++ b/elections2021/management/commands/import_matrix.py
@@ -5,7 +5,6 @@ from django.utils.text import slugify
 
 from ...constants import (
     CHILDLESS,
-    COMPUTERS,
     COUNTRYSIDE,
     CULTURE,
     EDUCATION,
@@ -41,6 +40,7 @@ from ...constants import (
     SOCIALLY_WEAK,
     SPORT,
     STUDENTS,
+    TECHNOLOGY,
     WORKING_SENIORS,
 )
 from ...models import Elections2021ProgramPointPage
@@ -65,7 +65,7 @@ FIELDNAMES = (
     SPORT,
     HEALTH,
     CULTURE,
-    COMPUTERS,
+    TECHNOLOGY,
     COUNTRYSIDE,
     HOUSING,
     EDUCATION,
@@ -127,6 +127,7 @@ class Command(BaseCommand):
             page.weight_sport = int(row[SPORT])
             page.weight_health = int(row[HEALTH])
             page.weight_culture = int(row[CULTURE])
+            page.weight_technology = int(row[TECHNOLOGY])
             page.weight_countryside = int(row[COUNTRYSIDE])
             page.weight_housing = int(row[HOUSING])
             page.weight_education = int(row[EDUCATION])
diff --git a/elections2021/migrations/0011_elections2021programapppage.py b/elections2021/migrations/0011_elections2021programapppage.py
new file mode 100644
index 0000000000000000000000000000000000000000..58c97d435d51882242478a62c6768a052869b1a6
--- /dev/null
+++ b/elections2021/migrations/0011_elections2021programapppage.py
@@ -0,0 +1,65 @@
+# Generated by Django 3.2.2 on 2021-05-15 10:51
+
+import django.db.models.deletion
+import wagtailmetadata.models
+from django.db import migrations, models
+
+import shared.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("wagtailimages", "0023_add_choose_permissions"),
+        ("wagtailcore", "0062_comment_models_and_pagesubscription"),
+        ("elections2021", "0010_auto_20210514_0115"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Elections2021ProgramAppPage",
+            fields=[
+                (
+                    "page_ptr",
+                    models.OneToOneField(
+                        auto_created=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        parent_link=True,
+                        primary_key=True,
+                        serialize=False,
+                        to="wagtailcore.page",
+                    ),
+                ),
+                (
+                    "cover_photo",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.PROTECT,
+                        to="wagtailimages.image",
+                        verbose_name="cover foto",
+                    ),
+                ),
+                (
+                    "search_image",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        related_name="+",
+                        to="wagtailimages.image",
+                        verbose_name="Search image",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Programová aplikace",
+            },
+            bases=(
+                shared.models.SubpageMixin,
+                wagtailmetadata.models.WagtailImageMetadataMixin,
+                "wagtailcore.page",
+                models.Model,
+            ),
+        ),
+    ]
diff --git a/elections2021/migrations/0012_auto_20210515_1257.py b/elections2021/migrations/0012_auto_20210515_1257.py
new file mode 100644
index 0000000000000000000000000000000000000000..5bc4b17d7b4574674ef7a8d8cefe9e551d9ec8c8
--- /dev/null
+++ b/elections2021/migrations/0012_auto_20210515_1257.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.2 on 2021-05-15 10:57
+
+import wagtail.core.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("elections2021", "0011_elections2021programapppage"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="elections2021programapppage",
+            name="cover_photo",
+        ),
+        migrations.AddField(
+            model_name="elections2021programapppage",
+            name="info_text",
+            field=wagtail.core.fields.RichTextField(
+                blank=True, null=True, verbose_name="základní informace"
+            ),
+        ),
+    ]
diff --git a/elections2021/migrations/0013_elections2021programpointpage_weight_technology.py b/elections2021/migrations/0013_elections2021programpointpage_weight_technology.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b06514fa8eb78324cce1a58c6ce1cf5c6616472
--- /dev/null
+++ b/elections2021/migrations/0013_elections2021programpointpage_weight_technology.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.2 on 2021-05-15 19:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("elections2021", "0012_auto_20210515_1257"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="elections2021programpointpage",
+            name="weight_technology",
+            field=models.IntegerField(default=0, verbose_name="váha technologie"),
+        ),
+    ]
diff --git a/elections2021/models.py b/elections2021/models.py
index a4f5dac27a232839c9a40d20009fe7b57f708a86..c194bd9c3218dbf214bccdd2037ef6d1893c195f 100644
--- a/elections2021/models.py
+++ b/elections2021/models.py
@@ -1,9 +1,11 @@
-from collections import defaultdict
+import json
 from functools import cached_property
 
 from django import forms
+from django.conf import settings
 from django.core.paginator import Paginator
 from django.db import models
+from django.db.models.functions import Greatest
 from django.http import HttpResponseRedirect
 from django.utils.translation import gettext_lazy
 from modelcluster.contrib.taggit import ClusterTaggableManager
@@ -35,7 +37,14 @@ from .constants import (
     ARTICLES_PER_PAGE,
     BENEFITS_CHOICES,
     CANDIDATE_RICH_TEXT_FEATURES,
+    CHILDLESS,
+    COUNTRYSIDE,
+    CULTURE,
+    EDUCATION,
     EXTRA_FEATURES,
+    HEALTH,
+    HOUSING,
+    MATURE,
     MINISTRY_AGRICULTURE,
     MINISTRY_ARCHETYPES,
     MINISTRY_BUSINESS,
@@ -53,6 +62,9 @@ from .constants import (
     MINISTRY_SCHOOLS,
     MINISTRY_SOCIAL,
     MINISTRY_TRANSPORT,
+    NATURE,
+    OCCUPATION_BUSINESS,
+    PARENTS,
     PARTY_CHOICES,
     PARTY_NAME,
     PIRATES,
@@ -71,16 +83,22 @@ from .constants import (
     REGION_CHOICES,
     REGION_NAME_VARIANT,
     REGION_OPTIONS,
-    REGION_PHA,
     REGION_SLUGS,
     RESTRICTED_FEATURES,
+    SENIORS,
+    SPORT,
     STANDARD_FEATURES,
+    STUDENTS,
     STYLE_CHOICES,
     STYLE_CSS,
+    TECHNOLOGY,
     TOP_CANDIDATES_NUM,
+    WEALTH_BAD,
     WHITE,
+    WORKING_SENIORS,
 )
-from .forms import ProgramPointPageForm
+from .forms import ProgramAppForm, ProgramPointPageForm
+from .utils import get_archetype
 
 NO_SEARCH_IMAGE_USE_PHOTO = (
     "Pokud není zadán <strong>Search image</strong>, použije se <strong>hlavní "
@@ -139,6 +157,7 @@ class Elections2021HomePage(Page, MetadataPageMixin):
         "elections2021.Elections2021CandidatesMapPage",
         "elections2021.Elections2021ProgramPage",
         "elections2021.Elections2021QuestionsPage",
+        "elections2021.Elections2021ProgramAppPage",
     ]
 
     ### OTHERS
@@ -176,6 +195,14 @@ class Elections2021HomePage(Page, MetadataPageMixin):
     def questions_page_url(self):
         return get_subpage_url(self, Elections2021QuestionsPage)
 
+    @cached_property
+    def program_page_url(self):
+        return get_subpage_url(self, Elections2021ProgramPage)
+
+    @cached_property
+    def program_app_page_url(self):
+        return get_subpage_url(self, Elections2021ProgramAppPage)
+
 
 class Elections2021ArticleTag(TaggedItemBase):
     content_object = ParentalKey(
@@ -624,17 +651,35 @@ class Elections2021ProgramPage(
         context["show_archetype_icon"] = True
         context["show_app_banner"] = True
         context["show_pagination"] = True
-
-        points = self.get_children().live().specific()
-        context["points"] = Paginator(points, PROGRAM_POINTS_PER_PAGE).get_page(
-            request.GET.get("page")
-        )
+        context["active_my_program"] = False
+        context["has_my_program"] = self.get_my_selection(request) is not None
         return context
 
     @staticmethod
     def get_archetype_image(archetype):
         return f"elections2021/images/archetype/{archetype}.svg"
 
+    def get_my_program_url(self):
+        return self.url + self.reverse_subpage("my_program")
+
+    def get_my_selection(self, request):
+        data = request.get_signed_cookie(settings.ELECTIONS2021_COOKIE_NAME, None)
+        if data:
+            try:
+                return json.loads(data)
+            except json.JSONDecodeError:
+                pass
+        return None
+
+    @route(r"^$")
+    def plan_all(self, request, param=None):
+        points = Elections2021ProgramPointPage.objects.live().specific()
+        points = Paginator(points, PROGRAM_POINTS_PER_PAGE).get_page(
+            request.GET.get("page")
+        )
+        context = {"points": points}
+        return self.render(request, context_overrides=context)
+
     @route(r"^plan/([a-z-]+)/$")
     def plan_detail(self, request, param=None):
         plan = param if param in PLAN_OPTIONS else None
@@ -646,58 +691,40 @@ class Elections2021ProgramPage(
             title = next(title for key, title in PLAN_CHOICES if key == plan)
             head_image = self.get_archetype_image(PLAN_ARCHETYPES[plan])
 
-        points = self.get_children().live().specific()
         if plan == PLAN_ECONOMICS:
             head_text = self.plan_economics_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_economics__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_economics")
+            weights = ["weight_plan_economics"]
         elif plan == PLAN_DIGITAL:
             head_text = self.plan_digital_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_digital__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_digital")
+            weights = ["weight_plan_digital"]
         elif plan == PLAN_CORRUPTION:
             head_text = self.plan_corruption_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_corruption__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_corruption")
+            weights = ["weight_plan_corruption"]
         elif plan == PLAN_CLIMATE:
             head_text = self.plan_climate_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_climate__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_climate")
+            weights = ["weight_plan_climate"]
         elif plan == PLAN_COUNTRYSIDE:
             head_text = self.plan_countryside_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_countryside__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_countryside")
+            weights = ["weight_plan_countryside"]
         elif plan == PLAN_MANAGEMENT:
             head_text = self.plan_management_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_management__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_management")
+            weights = ["weight_plan_management"]
         elif plan == PLAN_SAFE_COUNTRY:
             head_text = self.plan_safe_country_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_safe_country__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_safe_country")
+            weights = ["weight_plan_safe_country"]
         elif plan == PLAN_CARE:
             head_text = self.plan_care_text
-            points = points.filter(
-                elections2021programpointpage__weight_plan_care__gt=0
-            ).order_by("-elections2021programpointpage__weight_plan_care")
+            weights = ["weight_plan_care"]
 
         context = {
             "active_plan": plan,
             "title": title,
             "head_image": head_image,
             "head_text": head_text,
-            "points": points,
+            "points": Elections2021ProgramPointPage.filter_by_weights(weights),
             "show_app_banner": False,
             "show_pagination": False,
         }
-
         return self.render(request, context_overrides=context)
 
     @route(r"^resort/([a-z-]+)/$")
@@ -711,75 +738,106 @@ class Elections2021ProgramPage(
             title = next(title for key, title in MINISTRY_CHOICES if key == ministry)
             head_image = self.get_archetype_image(MINISTRY_ARCHETYPES[ministry])
 
-        points = self.get_children().live().specific()
         if ministry == MINISTRY_TRANSPORT:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_transport__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_transport")
+            weights = ["weight_ministry_transport"]
         elif ministry == MINISTRY_FINANCES:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_finances__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_finances")
+            weights = ["weight_ministry_finances"]
         elif ministry == MINISTRY_CULTURE:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_culture__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_culture")
+            weights = ["weight_ministry_culture"]
         elif ministry == MINISTRY_DEFENSE:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_defense__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_defense")
+            weights = ["weight_ministry_defense"]
         elif ministry == MINISTRY_SOCIAL:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_social__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_social")
+            weights = ["weight_ministry_social"]
         elif ministry == MINISTRY_COUNTRYSIDE:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_countryside__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_countryside")
+            weights = ["weight_ministry_countryside"]
         elif ministry == MINISTRY_BUSINESS:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_business__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_business")
+            weights = ["weight_ministry_business"]
         elif ministry == MINISTRY_JUSTICE:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_justice__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_justice")
+            weights = ["weight_ministry_justice"]
         elif ministry == MINISTRY_SCHOOLS:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_schools__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_schools")
+            weights = ["weight_ministry_schools"]
         elif ministry == MINISTRY_INTERIOR:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_interior__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_interior")
+            weights = ["weight_ministry_interior"]
         elif ministry == MINISTRY_FOREIGN:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_foreign__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_foreign")
+            weights = ["weight_ministry_foreign"]
         elif ministry == MINISTRY_HEALTH:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_health__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_health")
+            weights = ["weight_ministry_health"]
         elif ministry == MINISTRY_AGRICULTURE:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_agriculture__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_agriculture")
+            weights = ["weight_ministry_agriculture"]
         elif ministry == MINISTRY_ENVIRONMENT:
-            points = points.filter(
-                elections2021programpointpage__weight_ministry_environment__gt=0
-            ).order_by("-elections2021programpointpage__weight_ministry_environment")
+            weights = ["weight_ministry_environment"]
 
         context = {
             "active_ministry": ministry,
             "title": title,
             "head_image": head_image,
             "head_text": head_text,
-            "points": points,
+            "points": Elections2021ProgramPointPage.filter_by_weights(weights),
             "show_archetype_icon": False,
             "show_app_banner": False,
             "show_pagination": False,
         }
+        return self.render(request, context_overrides=context)
 
+    @route(r"^muj-program/$")
+    def my_program(self, request):
+        selection = self.get_my_selection(request)
+        if selection is None:
+            response = HttpResponseRedirect(self.url)
+            # očekávali jsme data z cookie, ale nepodařilo se je získat, tak pro
+            # jistotu cookie smažeme kdyby náhodou existovala s poškozenými daty
+            response.delete_cookie(settings.ELECTIONS2021_COOKIE_NAME)
+            return response
+
+        weights = []
+
+        archetype = get_archetype(selection)
+        if archetype == CHILDLESS:
+            weights.append("weight_childless")
+        elif archetype == PARENTS:
+            weights.append("weight_parents")
+        elif archetype == MATURE:
+            weights.append("weight_mature")
+        elif archetype == STUDENTS:
+            weights.append("weight_students")
+        elif archetype == SENIORS:
+            weights.append("weight_seniors")
+        elif archetype == WORKING_SENIORS:
+            weights.append("weight_working_seniors")
+
+        if selection["occupation"] == OCCUPATION_BUSINESS:
+            weights.append("weight_self_employed")
+
+        if selection["wealth"] == WEALTH_BAD:
+            weights.append("weight_socially_weak")
+
+        if NATURE in selection["topics"]:
+            weights.append("weight_nature")
+        if SPORT in selection["topics"]:
+            weights.append("weight_sport")
+        if HEALTH in selection["topics"]:
+            weights.append("weight_health")
+        if CULTURE in selection["topics"]:
+            weights.append("weight_culture")
+        if TECHNOLOGY in selection["topics"]:
+            weights.append("weight_technology")
+        if COUNTRYSIDE in selection["topics"]:
+            weights.append("weight_countryside")
+        if HOUSING in selection["topics"]:
+            weights.append("weight_housing")
+        if EDUCATION in selection["topics"]:
+            weights.append("weight_education")
+
+        points = Elections2021ProgramPointPage.filter_by_weights(weights)
+        points = Paginator(points, PROGRAM_POINTS_PER_PAGE).get_page(
+            request.GET.get("page")
+        )
+        context = {
+            "title": "Program pro mě",
+            "points": points,
+            "show_app_banner": False,
+            "active_my_program": True,
+        }
         return self.render(request, context_overrides=context)
 
 
@@ -896,6 +954,7 @@ class Elections2021ProgramPointPage(SubpageMixin, MetadataPageMixin, Page):
     weight_sport = models.IntegerField("váha sport", default=0)
     weight_health = models.IntegerField("váha zdraví", default=0)
     weight_culture = models.IntegerField("váha kultura", default=0)
+    weight_technology = models.IntegerField("váha technologie", default=0)
     weight_countryside = models.IntegerField("váha regiony", default=0)
     weight_housing = models.IntegerField("váha bydlení", default=0)
     weight_education = models.IntegerField("váha vzdělávání", default=0)
@@ -1021,6 +1080,7 @@ class Elections2021ProgramPointPage(SubpageMixin, MetadataPageMixin, Page):
                 FieldPanel("weight_sport"),
                 FieldPanel("weight_health"),
                 FieldPanel("weight_culture"),
+                FieldPanel("weight_technology"),
                 FieldPanel("weight_countryside"),
                 FieldPanel("weight_housing"),
                 FieldPanel("weight_education"),
@@ -1130,6 +1190,20 @@ class Elections2021ProgramPointPage(SubpageMixin, MetadataPageMixin, Page):
             return MINISTRY_ARCHETYPES[MINISTRY_ENVIRONMENT]
         return ""
 
+    @classmethod
+    def filter_by_weights(cls, weights):
+        points = cls.objects.live().specific()
+        if len(weights) > 1:
+            return (
+                points.annotate(max_weight=Greatest(*weights))
+                .filter(max_weight__gt=0)
+                .order_by("-max_weight")
+            )
+        else:
+            condition = {f"{weights[0]}__gt": 0}
+            order = f"-{weights[0]}"
+            return points.filter(**condition).order_by(order)
+
 
 class RichQuestionBlock(blocks.StructBlock):
     question = blocks.CharBlock(label="otázka")
@@ -1211,3 +1285,55 @@ class Elections2021QuestionsPage(SubpageMixin, MetadataPageMixin, Page):
 
     def get_meta_image(self):
         return self.search_image or self.photo
+
+
+class Elections2021ProgramAppPage(SubpageMixin, MetadataPageMixin, Page):
+    ### FIELDS
+
+    info_text = RichTextField(
+        "základní informace", blank=True, null=True, features=RESTRICTED_FEATURES
+    )
+
+    ### PANELS
+
+    content_panels = Page.content_panels + [FieldPanel("info_text")]
+
+    promote_panels = [
+        MultiFieldPanel(
+            [
+                FieldPanel("slug"),
+                FieldPanel("seo_title"),
+                FieldPanel("search_description"),
+                ImageChooserPanel("search_image"),
+                HelpPanel(help.build(help.NO_SEO_TITLE)),
+            ],
+            gettext_lazy("Common page configuration"),
+        ),
+    ]
+
+    settings_panels = [CommentPanel()]
+
+    ### RELATIONS
+
+    parent_page_types = ["elections2021.Elections2021HomePage"]
+    subpage_types = []
+
+    ### OTHERS
+
+    class Meta:
+        verbose_name = "Programová aplikace"
+
+    def serve(self, request):
+        if request.method == "POST":
+            form = ProgramAppForm(request.POST)
+            if form.is_valid():
+                program_page = Elections2021ProgramPage.objects.live().first()
+                response = HttpResponseRedirect(program_page.get_my_program_url())
+                response.set_signed_cookie(
+                    settings.ELECTIONS2021_COOKIE_NAME,
+                    json.dumps(form.cleaned_data),
+                    secure=settings.SESSION_COOKIE_SECURE,
+                    httponly=settings.SESSION_COOKIE_HTTPONLY,
+                )
+                return response
+        return super().serve(request)
diff --git a/elections2021/templates/elections2021/elections2021_program_app_page.html b/elections2021/templates/elections2021/elections2021_program_app_page.html
new file mode 100644
index 0000000000000000000000000000000000000000..5688eadcb2c46e17d80e88076d33deccedc0a43b
--- /dev/null
+++ b/elections2021/templates/elections2021/elections2021_program_app_page.html
@@ -0,0 +1,208 @@
+{% extends "elections2021/base.html" %}
+{% load wagtailcore_tags wagtailimages_tags %}
+
+{% block content %}
+
+<main class="election-program-app">
+  <div class="container container--default py-24">
+    <header class="grid grid-cols-3">
+      <h1 class="head-alt-xl text-white">{{ page.title }}</h1>
+      <i class="col-start-3 ico--app text-white text-right"></i>
+    </header>
+    <section class="election-program-app__body px-8 py-16 md:py-20 md:px-16 mt-16 elevation-6">
+      <form method="post">
+        {% csrf_token %}
+
+        {% if page.info_text %}
+          <div class="grid grid-cols-4 mb-20 md:mb-40">
+            <div class="col-span-4 md:col-span-1">
+              <p class="head-xs font-bold">Základní informace</p>
+            </div>
+            <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0">
+              <p class="para">{{ page.info_text|richtext }}</p>
+            </div>
+          </div>
+        {% endif %}
+
+        <div class="grid grid-cols-4">
+          <div class="col-span-4 md:col-span-1">
+            <p class="head-xs font-bold mt-5">Kolik vám je let?</p>
+          </div>
+          <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap">
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="age" id="18-29" value="18-29">
+              <label for="18-29">18 - 29 LET</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="age" id="30-49" value="30-49" checked="">
+              <label for="30-49">30 - 49 LET</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="age" id="50-64" value="50-64">
+              <label for="50-64">50 - 64 LET</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="age" id="65-plus" value="65-plus">
+              <label for="65-plus">65+ LET</label>
+            </div>
+
+            <hr class="w-full">
+          </div>
+        </div>
+
+        <div class="grid grid-cols-4">
+          <div class="col-span-4 md:col-span-1">
+            <p class="head-xs font-bold">Vychováváte děti?</p>
+          </div>
+          <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap">
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="kids" id="ano" value="true" checked="">
+              <label for="ano">ANO</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="kids" id="ne" value="false">
+              <label for="ne">NE</label>
+            </div>
+
+            <hr class="w-full">
+          </div>
+        </div>
+
+        <div class="grid grid-cols-4">
+          <div class="col-span-4 md:col-span-1">
+            <p class="head-xs font-bold">Jak jste na tom s penězi?</p>
+          </div>
+          <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap">
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="wealth" id="bad" value="bad">
+              <label for="bad">NIC MOC</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="wealth" id="average" value="average" checked="">
+              <label for="average">PRŮMĚRNĚ</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="wealth" id="good" value="good">
+              <label for="good">SKVĚLE</label>
+            </div>
+
+            <hr class="w-full">
+          </div>
+        </div>
+
+        <div class="grid grid-cols-4">
+          <div class="col-span-4 md:col-span-1">
+            <p class="head-xs font-bold">Máte nějaké povolání?</p>
+          </div>
+          <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap">
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="occupation" id="student" value="student">
+              <label for="student">STUDUJI</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="occupation" id="working" value="working" checked="">
+              <label for="working">PRACUJI</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="occupation" id="business" value="business">
+              <label for="business">PODNIKÁM</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="occupation" id="parenting" value="parenting">
+              <label for="parenting">NA RODIČOVSKÉ</label>
+            </div>
+
+            <div class="radio program-radio form-field__control mb-2 mr-2">
+              <input type="radio" name="occupation" id="retired" value="retired">
+              <label for="retired">V DŮCHODU</label>
+            </div>
+
+            <hr class="w-full">
+          </div>
+        </div>
+
+        <div class="grid grid-cols-4">
+          <div class="col-span-4 md:col-span-1">
+            <p class="head-xs font-bold">Jaké téma je vám blízké?</p>
+          </div>
+          <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap">
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="nature" value="nature" name="topics"> <span>PŘÍRODA</span>
+              </label>
+            </div>
+
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="sport" value="sport" name="topics"> <span>SPORT</span>
+              </label>
+            </div>
+
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="health" value="health" name="topics"> <span>ZDRAVÍ</span>
+              </label>
+            </div>
+
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="culture" value="culture" name="topics"> <span>KULTURA</span>
+              </label>
+            </div>
+
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="technology" value="technology" name="topics"> <span>TECHNOLOGIE</span>
+              </label>
+            </div>
+
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="countryside" value="countryside" name="topics"> <span>REGIONY</span>
+              </label>
+            </div>
+
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="housing" value="housing" name="topics"> <span>BYDLENÍ</span>
+              </label>
+            </div>
+
+            <div class="checkbox program-checkbox form-field__control mr-2 mb-2 ">
+              <label>
+                <input type="checkbox" id="education" value="education" name="topics"> <span>VZDĚLÁVÁNÍ</span>
+              </label>
+            </div>
+
+            <hr class="w-full">
+          </div>
+        </div>
+
+        <div class="grid grid-cols-4">
+          <div class="col-span-4 md:col-span-1">
+          </div>
+          <div class="col-span-4 md:col-span-3 md:pl-16 mt-5 md:mt-0 inline-flex flex-wrap">
+            <button class="btn max-w-max ">
+              <div class="btn__body bg-acidgreen py-3 px-5 md:py-5 md:px-16 text-black font-bold head-xs max-w-max w-max">Ukázat program na míru</div>
+            </button>
+
+            <a href="{{ page.root_page.program_page_url }}" class="sm:ml-auto w-auto text-left md:text-right mb-4 md:mb-0 mt-6 ">
+              Přeskočit formulář <i class="ico--chevron-right"></i>
+            </a>
+          </div>
+        </div>
+      </form>
+    </section>
+  </div>
+</main>
+
+{% endblock %}
diff --git a/elections2021/templates/elections2021/elections2021_program_page.html b/elections2021/templates/elections2021/elections2021_program_page.html
index e866279820446a853ac6c9325f0a867526d37749..8b54f54864df585bac33febee312e0830e7dc258 100644
--- a/elections2021/templates/elections2021/elections2021_program_page.html
+++ b/elections2021/templates/elections2021/elections2021_program_page.html
@@ -1,5 +1,5 @@
 {% extends "elections2021/base.html" %}
-{% load static wagtailcore_tags wagtailimages_tags %}
+{% load static wagtailcore_tags wagtailimages_tags wagtailroutablepage_tags %}
 
 {% block content %}
 
@@ -21,6 +21,8 @@
   </article>
 {% elif active_ministry %}
   {% include "elections2021/_page_header.html" with title=title %}
+{% elif active_my_program %}
+  {% include "elections2021/_page_header.html" with title=title %}
 {% else %}
   {% include "elections2021/_page_header.html" with title=page.title photo=page.photo %}
 {% endif %}
@@ -30,57 +32,64 @@
     <div class="container container--default pt-8 pb-16 lg:py-24">
       <div class="text-center">
 
-        {% comment %}
-        <!-- set data-chosen of #progamswitch container by serverside script based on current url-->
-        <div id="progamswitch" class="switch mb-4" data-chosen="mujprogram">
-          <a class="switch__item switch__item--active" data-chosen="mujprogram">Program pro mě</a>
-          <a class="switch__item" data-chosen="uplnyprogram">Úplný program</a>
-        </div>
-        {% endcomment %}
-
-        <div class="filters">
-          <div class="select inline-flex w-auto {% if active_ministry %}text-black{% else %}text-white{% endif %}">
-            <select id="select_ministry" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_ministry }}">
-              <option value="x" {% if not active_ministry %}selected="selected"{% endif %}>Podle oblasti</option>
-              {% for val, name in ministry_choices %}
-                <option value="{{ val }}" {% if active_ministry == val %}selected="selected"{% endif %}>{{ name }}</option>
-              {% endfor %}
-            </select>
+        {% if has_my_program %}
+          <div id="progamswitch" class="switch mb-4">
+            {% if active_my_program %}
+              <span class="switch__item switch__item--active">Program pro mě</span>
+              <a href="{% pageurl page %}" class="switch__item">Úplný program</a>
+            {% else %}
+              <a href="{% routablepageurl page "my_program" %}" class="switch__item">Program pro mě</a>
+              <cpan class="switch__item switch__item--active">Úplný program</span>
+            {% endif %}
           </div>
-          <div class="select inline-flex w-auto {% if active_plan %}text-black{% else %}text-white{% endif %}">
-            <select id="select_plan" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_plan }}">
-              <option value="x">Podle plánu</option>
-              {% for val, name in plan_choices %}
-                <option value="{{ val }}" {% if active_plan == val %}selected=""{% endif %}>{{ name }}</option>
-              {% endfor %}
-            </select>
+        {% endif %}
+
+        {% if not active_my_program %}
+          <div class="filters">
+            <div class="select inline-flex w-auto {% if active_ministry %}text-black{% else %}text-white{% endif %}">
+              <select id="select_ministry" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_ministry }}">
+                <option value="x" {% if not active_ministry %}selected="selected"{% endif %}>Podle oblasti</option>
+                {% for val, name in ministry_choices %}
+                  <option value="{{ val }}" {% if active_ministry == val %}selected="selected"{% endif %}>{{ name }}</option>
+                {% endfor %}
+              </select>
+            </div>
+            <div class="select inline-flex w-auto {% if active_plan %}text-black{% else %}text-white{% endif %}">
+              <select id="select_plan" class="select__control form-field__control bg-black block filter-pirati-stan" data-chosen="{{ active_plan }}">
+                <option value="x">Podle plánu</option>
+                {% for val, name in plan_choices %}
+                  <option value="{{ val }}" {% if active_plan == val %}selected=""{% endif %}>{{ name }}</option>
+                {% endfor %}
+              </select>
+            </div>
           </div>
-        </div>
-      </div>
 
-      <script>
-        var baseUrl = "{% pageurl page %}";
-        var selectMinistry = document.getElementById("select_ministry");
-        var selectPlan = document.getElementById("select_plan");
-
-        selectMinistry.addEventListener("change", function(e) {
-          if (this.value == "x") {
-            location.href = baseUrl;
-          }
-          else {
-            location.href = baseUrl + "resort/" + this.value + "/";
-          }
-        });
-
-        selectPlan.addEventListener("change", function(e) {
-          if (this.value == "x") {
-            location.href = baseUrl;
-          }
-          else {
-            location.href = baseUrl + "plan/" + this.value + "/";
-          }
-        });
-      </script>
+          <script>
+            var baseUrl = "{% pageurl page %}";
+            var selectMinistry = document.getElementById("select_ministry");
+            var selectPlan = document.getElementById("select_plan");
+
+            selectMinistry.addEventListener("change", function(e) {
+              if (this.value == "x") {
+                location.href = baseUrl;
+              }
+              else {
+                location.href = baseUrl + "resort/" + this.value + "/";
+              }
+            });
+
+            selectPlan.addEventListener("change", function(e) {
+              if (this.value == "x") {
+                location.href = baseUrl;
+              }
+              else {
+                location.href = baseUrl + "plan/" + this.value + "/";
+              }
+            });
+          </script>
+        {% endif %}
+
+      </div>
 
       <h1 class="head-alt-md text-center py-8 lg:pt-24 lg:pb-8">Volební program</h1>
 
diff --git a/elections2021/templatetags/elections2021_extras.py b/elections2021/templatetags/elections2021_extras.py
index 12eebd468bd12ed1c7932831a9170b31af22e1f5..5c257f2e6e6fe1b15f1c0a255e160f495ae14049 100644
--- a/elections2021/templatetags/elections2021_extras.py
+++ b/elections2021/templatetags/elections2021_extras.py
@@ -13,8 +13,6 @@ def format_sources_block(value):
     soup = bs4.BeautifulSoup(value, "html.parser")
     out = []
 
-    print(soup)
-
     for item in soup.children:
         for a in item.find_all("a"):
             a["class"] = "text-fxactivecolor underline"
diff --git a/elections2021/tests/__init__.py b/elections2021/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/elections2021/tests/test_utils.py b/elections2021/tests/test_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c540aa7035ceebebc27b1e0b9fdc86f7e33dd89f
--- /dev/null
+++ b/elections2021/tests/test_utils.py
@@ -0,0 +1,140 @@
+import pytest
+
+from ..constants import (
+    AGE_18_29,
+    AGE_30_49,
+    AGE_50_64,
+    AGE_65_PLUS,
+    CHILDLESS,
+    MATURE,
+    OCCUPATION_BUSINESS,
+    OCCUPATION_PARENTING,
+    OCCUPATION_RETIRED,
+    OCCUPATION_STUDENT,
+    OCCUPATION_WORKING,
+    PARENTS,
+    SENIORS,
+    STUDENTS,
+    WEALTH_AVERAGE,
+    WEALTH_BAD,
+    WEALTH_GOOD,
+    WORKING_SENIORS,
+)
+from ..utils import get_archetype
+
+ALL_AGES = (AGE_18_29, AGE_30_49, AGE_50_64, AGE_65_PLUS)
+ALL_OCCUPATIONS = (
+    OCCUPATION_WORKING,
+    OCCUPATION_BUSINESS,
+    OCCUPATION_PARENTING,
+    OCCUPATION_RETIRED,
+    OCCUPATION_STUDENT,
+)
+ALL_BUT_STUDENT_OCCUPATIONS = (
+    OCCUPATION_WORKING,
+    OCCUPATION_BUSINESS,
+    OCCUPATION_PARENTING,
+    OCCUPATION_RETIRED,
+)
+ALL_WEALTHS = (WEALTH_BAD, WEALTH_AVERAGE, WEALTH_GOOD)
+ALL_KIDS = (True, False)
+
+
+@pytest.mark.parametrize("age", (AGE_18_29, AGE_30_49))
+@pytest.mark.parametrize("wealth", ALL_WEALTHS)
+@pytest.mark.parametrize("occupation", ALL_BUT_STUDENT_OCCUPATIONS)
+def test_get_archetype__childless(age, wealth, occupation):
+    selection = {
+        "kids": False,
+        "age": age,
+        "wealth": wealth,
+        "occupation": occupation,
+        "topics": [],
+    }
+    assert get_archetype(selection) == CHILDLESS
+
+
+@pytest.mark.parametrize("age", (AGE_18_29, AGE_30_49))
+@pytest.mark.parametrize("wealth", ALL_WEALTHS)
+@pytest.mark.parametrize("occupation", ALL_BUT_STUDENT_OCCUPATIONS)
+def test_get_archetype__parents(age, wealth, occupation):
+    selection = {
+        "kids": True,
+        "age": age,
+        "wealth": wealth,
+        "occupation": occupation,
+        "topics": [],
+    }
+    assert get_archetype(selection) == PARENTS
+
+
+@pytest.mark.parametrize("kids", ALL_KIDS)
+@pytest.mark.parametrize("wealth", ALL_WEALTHS)
+@pytest.mark.parametrize("occupation", ALL_BUT_STUDENT_OCCUPATIONS)
+def test_get_archetype__mature(kids, wealth, occupation):
+    selection = {
+        "age": AGE_50_64,
+        "kids": kids,
+        "wealth": wealth,
+        "occupation": occupation,
+        "topics": [],
+    }
+    assert get_archetype(selection) == MATURE
+
+
+@pytest.mark.parametrize("kids", ALL_KIDS)
+@pytest.mark.parametrize("wealth", ALL_WEALTHS)
+@pytest.mark.parametrize(
+    "occupation", (OCCUPATION_BUSINESS, OCCUPATION_PARENTING, OCCUPATION_RETIRED)
+)
+def test_get_archetype__seniors(kids, wealth, occupation):
+    selection = {
+        "age": AGE_65_PLUS,
+        "kids": kids,
+        "wealth": wealth,
+        "occupation": occupation,
+        "topics": [],
+    }
+    assert get_archetype(selection) == SENIORS
+
+
+@pytest.mark.parametrize("kids", ALL_KIDS)
+@pytest.mark.parametrize("wealth", ALL_WEALTHS)
+def test_get_archetype__working_seniors(kids, wealth):
+    selection = {
+        "age": AGE_65_PLUS,
+        "kids": kids,
+        "wealth": wealth,
+        "occupation": OCCUPATION_WORKING,
+        "topics": [],
+    }
+    assert get_archetype(selection) == WORKING_SENIORS
+
+
+@pytest.mark.parametrize("age", ALL_AGES)
+@pytest.mark.parametrize("kids", ALL_KIDS)
+@pytest.mark.parametrize("wealth", ALL_WEALTHS)
+def test_get_archetype__students(age, kids, wealth):
+    selection = {
+        "age": age,
+        "kids": kids,
+        "wealth": wealth,
+        "occupation": OCCUPATION_STUDENT,
+        "topics": [],
+    }
+    assert get_archetype(selection) == STUDENTS
+
+
+@pytest.mark.parametrize("age", ALL_AGES)
+@pytest.mark.parametrize("kids", ALL_KIDS)
+@pytest.mark.parametrize("wealth", ALL_WEALTHS)
+@pytest.mark.parametrize("occupation", ALL_OCCUPATIONS)
+def test_get_archetype__not_missing_combination(age, kids, wealth, occupation):
+    selection = {
+        "age": age,
+        "kids": kids,
+        "wealth": wealth,
+        "occupation": occupation,
+        "topics": [],
+    }
+    assert get_archetype(selection) is not None
diff --git a/elections2021/utils.py b/elections2021/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fc1d4964dcd51fb9b9be53fa557d63670c6b2ee
--- /dev/null
+++ b/elections2021/utils.py
@@ -0,0 +1,30 @@
+from .constants import (
+    AGE_18_29,
+    AGE_30_49,
+    AGE_50_64,
+    AGE_65_PLUS,
+    CHILDLESS,
+    MATURE,
+    OCCUPATION_STUDENT,
+    OCCUPATION_WORKING,
+    PARENTS,
+    SENIORS,
+    STUDENTS,
+    WORKING_SENIORS,
+)
+
+
+def get_archetype(selection):
+    if selection["occupation"] == OCCUPATION_STUDENT:
+        return STUDENTS
+    if selection["age"] == AGE_50_64:
+        return MATURE
+    if selection["age"] == AGE_65_PLUS:
+        if selection["occupation"] == OCCUPATION_WORKING:
+            return WORKING_SENIORS
+        return SENIORS
+    if selection["age"] in [AGE_18_29, AGE_30_49]:
+        if selection["kids"]:
+            return PARENTS
+        return CHILDLESS
+    return None
diff --git a/majak/settings/base.py b/majak/settings/base.py
index 1c338096dddbce5b922631b54dd780eaf7512c84..9b3653b198436fdcebd664b1d0627ca872293673 100644
--- a/majak/settings/base.py
+++ b/majak/settings/base.py
@@ -257,3 +257,5 @@ CZECH_INSPIRATIONAL_NEWSLETTER_ID = env.int(
 )
 NALODENI_API_NEWS_SUBSCRIBE_URL = env.str("NALODENI_API_NEWS_SUBSCRIBE_URL", default="")
 NALODENI_API_CREDENTIALS = env.str("NALODENI_API_CREDENTIALS", default="")
+
+ELECTIONS2021_COOKIE_NAME = "program"
diff --git a/requirements/base.in b/requirements/base.in
index ce93dbec40607f2d97103a454f5ab3c3265b0e59..a90545dd621a5dbae407096cbdbfc96ef85eda6d 100644
--- a/requirements/base.in
+++ b/requirements/base.in
@@ -17,3 +17,4 @@ sentry-sdk
 Markdown
 beautifulsoup4
 bleach
+ipython
diff --git a/requirements/base.txt b/requirements/base.txt
index d517b603cb9d23459c015c3c52288138ad0b86f8..79713326181c8082f739dd1d322130d15aaa8d57 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -6,12 +6,16 @@
 #
 anyascii==0.2.0
     # via wagtail
+appnope==0.1.2
+    # via ipython
 arrow==0.14.7
     # via
     #   -r base.in
     #   ics
 asgiref==3.3.4
     # via django
+backcall==0.2.0
+    # via ipython
 beautifulsoup4==4.9.3
     # via
     #   -r base.in
@@ -31,6 +35,8 @@ cryptography==3.4.7
     #   josepy
     #   mozilla-django-oidc
     #   pyopenssl
+decorator==5.0.8
+    # via ipython
 django-environ==0.4.5
     # via -r base.in
 django-extensions==3.1.3
@@ -78,12 +84,20 @@ ics==0.7
     # via -r base.in
 idna==2.10
     # via requests
+ipython-genutils==0.2.0
+    # via traitlets
+ipython==7.23.1
+    # via -r base.in
+jedi==0.18.0
+    # via ipython
 josepy==1.8.0
     # via mozilla-django-oidc
 l18n==2020.6.1
     # via wagtail
 markdown==3.3.4
     # via -r base.in
+matplotlib-inline==0.1.2
+    # via ipython
 mozilla-django-oidc==1.2.4
     # via pirates
 numpy==1.20.3
@@ -94,16 +108,28 @@ openpyxl==3.0.7
     # via tablib
 packaging==20.9
     # via bleach
+parso==0.8.2
+    # via jedi
+pexpect==4.8.0
+    # via ipython
+pickleshare==0.7.5
+    # via ipython
 pillow==8.2.0
     # via
     #   django-simple-captcha
     #   wagtail
 pirates==0.5.0
     # via -r base.in
+prompt-toolkit==3.0.18
+    # via ipython
 psycopg2-binary==2.8.6
     # via -r base.in
+ptyprocess==0.7.0
+    # via pexpect
 pycparser==2.20
     # via cffi
+pygments==2.9.0
+    # via ipython
 pyopenssl==20.0.1
     # via josepy
 pyparsing==2.4.7
@@ -146,6 +172,10 @@ tatsu==5.6.1
     # via ics
 telepath==0.1.1
     # via wagtail
+traitlets==5.0.5
+    # via
+    #   ipython
+    #   matplotlib-inline
 urllib3==1.26.4
     # via
     #   requests
@@ -156,6 +186,8 @@ wagtail==2.13
     # via
     #   -r base.in
     #   wagtail-metadata
+wcwidth==0.2.5
+    # via prompt-toolkit
 webencodings==0.5.1
     # via
     #   bleach