diff --git a/package.json b/package.json
index 509de41c2abe10c1ad71fcb4dfc239894f13de21..4a07c0ecbe08e9ffec6d3ceb929877ce237b3726 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
   "dependencies": {
     "css-loader": "^6.7.3",
     "jquery": "^3.6.3",
+    "js-cookie": "^3.0.1",
     "select2": "^4.1.0-rc.0",
     "style-loader": "^3.3.1",
     "tailwindcss": "^3.2.4",
diff --git a/requirements/base.txt b/requirements/base.txt
index 9efe2998543d7d21e6b74c9e33931f53e22db887..fe6b002554d53683818bd6e20d728659bead4bb7 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,8 +1,9 @@
 Django==4.1.5
 django-database-url==1.0.3
 django-environ==0.9.0
-psycopg2-binary==2.9.5
 django-webpack-loader==1.8.0
-nodeenv==1.7.0
+django-http-exceptions==1.4.0
 gql[requests]==3.4.0
+nodeenv==1.7.0
+psycopg2-binary==2.9.5
 requests==2.28.2
diff --git a/rv_voting_calc/templates/rv_voting_calc/includes/option_list.html b/rv_voting_calc/templates/rv_voting_calc/includes/option_list.html
index 642694ecd7aefc1e0f46716d9be6e5ab7c7b2b87..1e77bca301e571b13d271aa0f3572d5381b7a8ff 100644
--- a/rv_voting_calc/templates/rv_voting_calc/includes/option_list.html
+++ b/rv_voting_calc/templates/rv_voting_calc/includes/option_list.html
@@ -2,32 +2,36 @@
 
 <ul class="flex flex-col gap-2">
     {% for option_key, option in options.items %}
-        {% if option.has_support or show_has_support %}
-            <h3 class="flex gap-2 items-end text-xl font-semibold">
-                {{ option_key }}
-                <span class="text-sm text-gray-600">
-                    {{ option.ticket_count }} lístků, {{ option.vote_count }} hlasů
-                </span>
-                {% if show_has_support and not option.has_support %}
-                    <span class="text-sm text-red-600">(Nemá nadpoloviční podporu)</span>
-                {% endif %}
-            </h3>
+        {% if not option.marked_for_deletion or show_marked_for_deletion %}
+            {% if option.has_support or show_has_support %}
+                <h3 class="flex gap-2 items-end text-xl font-semibold {% if option.marked_for_deletion or not option.has_support %}text-red-600{% endif %}">
+                    {{ option_key }}
+                    <span class="text-sm {% if option.marked_for_deletion or not option.has_support %}text-red-600{% else %}text-gray-600{% endif %}">
+                        {{ option.ticket_count }} lístků, {{ option.vote_count }} hlasů
+                    </span>
+                    {% if show_has_support and not option.has_support %}
+                        <span class="text-sm text-red-600">(Nemá nadpoloviční podporu)</span>
+                    {% endif %}
+                </h3>
 
-            {% if option.votes|length != 0 %}
-                <ul class="flex gap-3 mt-1 flex-wrap">
-                    {% for vote in option.votes %}
-                        <li class="flex">
-                            <div class="px-4 py-2 bg-gray-300">
-                                {{ rv_members|index:vote.member|index:"displayName" }}
-                            </div>
-                            <ul class="px-4 py-2 flex gap-1 bg-gray-200">
-                                {% for option in options_by_member|index:vote.member %}
-                                    <li>{{ option }}{% if not forloop.last %}, {% endif %}</li>
-                                {% endfor %}
-                            </ul>
-                        </li>
-                    {% endfor %}
-                </ul>
+                {% if option.ticket_votes|length != 0 %}
+                    <ul class="flex gap-3 mt-1 flex-wrap">
+                        {% for vote in option.ticket_votes %}
+                            {% if not vote.hidden %}
+                                <li class="flex">
+                                    <div class="px-4 py-2 {% if not option.marked_for_deletion and option.has_support %}bg-gray-300{% else %}bg-red-300{% endif %}">
+                                        {{ rv_members|index:vote.member|index:"displayName" }}
+                                    </div>
+                                    <ul class="px-4 py-2 flex gap-1 {% if not option.marked_for_deletion and option.has_support %}bg-gray-200{% else %}bg-red-200{% endif %}">
+                                        {% for option in options_by_member|index:vote.member %}
+                                            <li>{{ option }}{% if not forloop.last %}, {% endif %}</li>
+                                        {% endfor %}
+                                    </ul>
+                                </li>
+                            {% endif %}
+                        {% endfor %}
+                    </ul>
+                {% endif %}
             {% endif %}
         {% endif %}
     {% endfor %}
diff --git a/rv_voting_calc/templates/rv_voting_calc/index.html b/rv_voting_calc/templates/rv_voting_calc/index.html
index 7ae060f41213f3b1a02e9e7d7b61843a91d337e3..effaff14d1819a90e24e6d8483f3959476cd719e 100644
--- a/rv_voting_calc/templates/rv_voting_calc/index.html
+++ b/rv_voting_calc/templates/rv_voting_calc/index.html
@@ -43,12 +43,21 @@
                 {% endfor %}
             </ul>
 
-            <ul
-                class="flex flex-col gap-5"
-                id="result"
-            >
-                
-            </ul>
+            <div>
+                <ul
+                    class="flex flex-col gap-5"
+                    id="result"
+                >
+                    
+                </ul>
+                <div class="mt-3">
+                    <a
+                        class="hidden underline"
+                        id="permalink"
+                        href=""
+                    >Permalink</a>
+                </div>
+            </div>
         </div>
     </main>
 
@@ -56,5 +65,21 @@
     
     <script>
         const VOTE_CALCULATION_ENDPOINT = "{% url "rv_voting_calc:get_calculated_votes" %}"
+
+        {% if options_by_member %}
+            {% for member, selected_options in options_by_member.items %}
+                {% for selected_option in selected_options %}
+                    $(`#${$.escapeSelector("{{ member}}")}-selection`).append(
+                        new Option(
+                            "{{ selected_option }}",
+                            "{{ selected_option }}",
+                            true,
+                            true,
+                        )
+                    );
+                {% endfor %}
+                $(`#${$.escapeSelector("{{ member}}")}-selection`).trigger("change");
+            {% endfor %}
+        {% endif %}
     </script>
 {% endblock %}
diff --git a/rv_voting_calc/templates/rv_voting_calc/steps/removed_option.html b/rv_voting_calc/templates/rv_voting_calc/steps/removed_option.html
index 8bd68a2ff1d86d021dfcda961c692d00b0fe9a59..062e07c61a4e3f49d8e3c68e80fe29b8b8d97acb 100644
--- a/rv_voting_calc/templates/rv_voting_calc/steps/removed_option.html
+++ b/rv_voting_calc/templates/rv_voting_calc/steps/removed_option.html
@@ -1,6 +1,11 @@
+{% load index %}
+
 <li class="bg-gray-100 drop-shadow-md p-4">
-    <div class="pb-2 mb-2 border-b border-gray-300">
+    <div class="pb-2 mb-3 border-b border-gray-300">
         <h2 class="text-2xl font-semibold font-bebas">{{ iteration|add:1 }}. vyřazovací kolo</h2>
     </div>
-    <p>Odstraněna {% if randomly %}(losováním){% else %}nepopulární{% endif %} možnost {{ option_key }}</p>
+    <p class="mb-2 text-red-600 font-semibold">
+        Možnost {{ option_key }} bude {% if randomly %}losováním{% else %}kvůli nepopularitě{% endif %} odstraněna
+    </p>
+    {% include "rv_voting_calc/includes/option_list.html" with options=options options_by_member=options_by_member show_proposers=True show_marked_for_deletion=True %}
 </li>
diff --git a/rv_voting_calc/views.py b/rv_voting_calc/views.py
index 9143bedf6a58d5e606fe6114fa281115cff3b06d..ddb24299826766842b80bfb43ea0cff95805ed86 100644
--- a/rv_voting_calc/views.py
+++ b/rv_voting_calc/views.py
@@ -1,30 +1,104 @@
+import base64
+import binascii
 import json
 import math
 import random
+import secrets
 
 import gql
 import requests
 
+from gql.transport.exceptions import TransportQueryError
 from gql.transport.requests import RequestsHTTPTransport
 
 from django.conf import settings
-from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
+from django.http import HttpResponse, JsonResponse
 from django.shortcuts import render
 from django.template.loader import render_to_string
+from django_http_exceptions import HTTPExceptions
 
 
-def get_rv_members() -> list:
+def get_options_by_member(source, rv_members) -> dict:
+    try:
+        options_by_member = json.loads(source)
+
+        if not isinstance(options_by_member, dict):
+            raise ValueError
+    except ValueError:
+        raise HTTPExceptions.BAD_REQUEST
+
+    rv_members = convert_rv_members_to_dict(rv_members)
+
+    for member, options in options_by_member.items():
+        # `member` is not a string
+        if not isinstance(member, str):
+            raise HTTPExceptions.BAD_REQUEST
+
+        # `member` is not in the list of known members
+        if member not in options_by_member:
+            raise HTTPExceptions.BAD_REQUEST
+
+        # `options` is not a list of 1+ length
+        if not isinstance(options, list) and len(options) != 0:
+            raise HTTPExceptions.BAD_REQUEST
+
+        # all items in `options` are strings
+        for item in options:
+            if not isinstance(item, str):
+                raise HTTPExceptions.BAD_REQUEST
+
+    return options_by_member
+
+
+def index(request):
+    rv_members = get_rv_members(request)
+    keyed_rv_members = convert_rv_members_to_dict(rv_members)
+
+    # Get votes from a possible permalink
+    options_by_member = request.GET.get("votes")
+
+    # BEGIN Input validation
+    if options_by_member is not None:
+        options_by_member = get_options_by_member(
+            options_by_member,
+            rv_members=rv_members,
+        )
+
+    return render(
+        request,
+        "rv_voting_calc/index.html",
+        {
+            "rv_members": rv_members,
+            "keyed_rv_members": keyed_rv_members,
+            "options_by_member": options_by_member,
+        }
+    )
+
+
+def get_rv_members(request, get_rv_gid: bool = False):
     transport = RequestsHTTPTransport(url=settings.CHOBOTNICE_API_URL)
     client = gql.Client(
         transport=transport,
         fetch_schema_from_transport=True,
     )
 
+    rv_gid = None
+
+    if "rv_gid" in request.GET:
+        try:
+            base64.b64decode(request.GET["rv_gid"], validate=True)
+        except binascii.Error:
+            raise HTTPExceptions.BAD_REQUEST
+
+        rv_gid = request.GET["rv_gid"]
+    else:
+        rv_gid = settings.CHOBOTNICE_RV_GID
+
     # Get members from query
     query = gql.gql(
         f"""
             {{
-                group(id: "{settings.CHOBOTNICE_RV_GID}") {{
+                group(id: "{rv_gid}") {{
                     memberships {{
                         person {{
                             username
@@ -37,7 +111,12 @@ def get_rv_members() -> list:
         """
     )
 
-    result = client.execute(query)
+    try:
+        result = client.execute(query)
+    except TransportQueryError:
+        # rv_gid was not found
+        raise HTTPExceptions.BAD_REQUEST
+
     rv_members = []
 
     # Convert to a nicer format
@@ -47,7 +126,10 @@ def get_rv_members() -> list:
     # Sort alphabetically
     rv_members.sort(key=lambda value: value["displayName"])
 
-    return rv_members
+    if get_rv_gid:
+        return rv_members, rv_gid
+    else:
+        return rv_members
 
 
 def convert_rv_members_to_dict(source: list) -> dict:
@@ -63,78 +145,80 @@ def convert_rv_members_to_dict(source: list) -> dict:
     return rv_members
 
 
-def index(request):
-    rv_members = get_rv_members()
-    keyed_rv_members = convert_rv_members_to_dict(rv_members)
-
-    return render(
-        request,
-        "rv_voting_calc/index.html",
-        {
-            "rv_members": rv_members,
-            "keyed_rv_members": keyed_rv_members,
-        }
-    )
-
-
 def do_step_b_through_d(
     request,
     iteration: int,
     steps: list,
     options: dict,
-    options_by_member: dict,
     options_without_support: list,
+    options_by_member: dict,
     rv_members: dict,
-    winner_treshold: int,
 ) -> tuple:
     ## BEGIN Step B
 
-    #print("start", options)
+    options_marked_for_deletion = []
 
-    for option in options.values():
+    # Move ticket votes to the next most popular option, if necessary
+    for option_key, option in options.items():
         votes_to_remove = []
 
-        for vote_list_position, vote in enumerate(option["votes"]):
-            if vote.get("was_moved", False):
-                continue
+        if option["marked_for_deletion"]:
+            options_marked_for_deletion.append(option_key)
 
-            for other_acceptable_option_position, other_acceptable_option in (
-                enumerate(options_by_member[vote["member"]])
-            ):
-                if not options[other_acceptable_option]["has_support"]:
+        # If the option has support and isn't marked for deletion, we don't need
+        # to do anything.
+        if option["has_support"] and not option["marked_for_deletion"]:
+            continue
+
+        # For all of the option's votes...
+        for vote_list_position, vote in enumerate(option["ticket_votes"]):
+            # Find the other acceptable options listed within...
+            for other_acceptable_option in vote["other_acceptable"]:
+                # ... if the other acceptable option no longer exists, do nothing.
+                if other_acceptable_option not in options:
                     continue
 
+                # ... if the other acceptable option has support and isn't marked
+                # for deletion, move this ticket vote there. We don't need to check
+                # positions, since the list we are looping through is ordered
+                # by popularity.
                 if (
-                    other_acceptable_option_position >= vote["position"]
-                    and option["has_support"]
+                    options[other_acceptable_option]["has_support"]
+                    and not options[other_acceptable_option]["marked_for_deletion"]
                 ):
-                    break
-
-                already_voted_for_acceptable_option = False
-
-                for other_acceptable_option_vote in options[other_acceptable_option]["votes"]:
-                    if other_acceptable_option_vote["member"] == vote["member"]:
-                        already_voted_for_acceptable_option = True
-
-                if already_voted_for_acceptable_option:
                     votes_to_remove.append(vote_list_position)
+                    options[other_acceptable_option]["ticket_votes"].append(vote)
+                    options[other_acceptable_option]["ticket_count"] += 1
                     break
 
-                vote["was_moved"] = True
-                options[other_acceptable_option]["votes"].append(vote)
-
-                break
-
+        # Remove votes that have been moved from this option.
         index_offset = 0
 
         for vote_list_position in votes_to_remove:
-            option["votes"].pop(vote_list_position - index_offset)
+            option["ticket_votes"].pop(vote_list_position - index_offset)
 
             index_offset += 1
 
-    for option_key in options_without_support:
-        options.pop(option_key)
+    # If we are on the first iteration, show the list of options with those that
+    # don't have support removed.
+    if iteration == 0:
+        steps.append(
+            render_to_string(
+                "rv_voting_calc/steps/with_support.html",
+                {
+                    "options": options,
+                    "options_by_member": options_by_member,
+                    "rv_members": rv_members
+                },
+                request=request,
+            )
+        )
 
+    # Delete options without support and those marked for deletion.
+    for option_key in (options_without_support + options_marked_for_deletion):
+        options.pop(option_key, None)
+
+    # If no options remain, show that there are no winners and end everything.
     if len(options) == 0:
         steps.append(
             render_to_string(
@@ -150,22 +234,7 @@ def do_step_b_through_d(
 
     ## BEGIN Step C
 
-    total_vote_count = 0
-    lowest_vote_count = math.inf
-    highest_vote_count = 0
-
-    for option in options.values():
-        if option["vote_count"] < lowest_vote_count:
-            lowest_vote_count = option["vote_count"]
-
-        if option["vote_count"] > highest_vote_count:
-            highest_vote_count = option["vote_count"]
-
-        total_vote_count += option["vote_count"]
-
-        # Remove was_moved for next iteration
-        option.pop("was_moved", None)
-
+    # Sort options by their vote count.
     options = {
         key: value
         for key, value
@@ -176,140 +245,136 @@ def do_step_b_through_d(
         )
     }
 
-    found_winner = False
-    winners = []
+    # Find winners.
+    winner_vote_count = options[next(iter(options))]["vote_count"]
+    winner_count = 0
+    winning_option_keys = []
 
-    # Try to find the most popular option
     for option_key, option in options.items():
-        option["is_popularity_winner"] = (option["vote_count"] >= winner_treshold)
-
-        if option["is_popularity_winner"]:
-            found_winner = True
-            winners.append(option_key)
-
-    # If more than 1 winners, delete one randomly
-    if found_winner:
-        if len(winners) == 1:
-            steps.append(
-                render_to_string(
-                    "rv_voting_calc/steps/found_popularity_winner.html",
-                    {
-                        "option_key": winners[0],
-                        "option": options[winners[0]],
-                    },
-                    request=request,
-                )
-            )
+        if option["vote_count"] == winner_vote_count:
+            winning_option_keys.append(option_key)
+            winner_count += 1
 
-            return options, True
-        else:
-            winners_to_eliminate = random.choices(winners, k=len(winners) - 1)
-
-            for eliminated_winner in winners_to_eliminate:
-                steps.append(
-                    render_to_string(
-                        "rv_voting_calc/steps/removed_option.html",
-                        {
-                            "iteration": iteration,
-                            "option_key": eliminated_winner,
-                            "option": options[eliminated_winner],
-                            "randomly": True
-                        },
-                        request=request,
-                    )
-                )
-
-                del options[eliminated_winner]
-
-            steps.append(
-                render_to_string(
-                    "rv_voting_calc/steps/found_popularity_winner.html",
-                    {
-                        "option_key": winners[0],
-                        "option": options[winners[0]]
-                    },
-                    request=request,
-                )
+    # If there is exactly 1 winner, show the winner and end everything.
+    if winner_count == 1:
+        steps.append(
+            render_to_string(
+                "rv_voting_calc/steps/found_popularity_winner.html",
+                {
+                    "option_key": winning_option_keys[0],
+                    "option": options[winning_option_keys[0]],
+                },
+                request=request,
             )
+        )
 
-            return options, True
-
-    ## END Step C
-
-    ## BEGIN Step D
-
-    # If no most popular option was found, remove the least popular one
-    most_unpopular_options = []
-
-    for option_key, option in options.items():
-        if (
-            option["vote_count"] < highest_vote_count
-            and option["vote_count"] == lowest_vote_count
-        ):
-            most_unpopular_options.append(option_key)
+        return options, True
+    # If all options are winners, randomly eliminate one and continue
+    # to the next iteration.
+    elif winner_count == len(options):
+        eliminated_winner = random.choice(winning_option_keys)
+        
+        options[eliminated_winner]["marked_for_deletion"] = True
 
-    if len(most_unpopular_options) == 1:
         steps.append(
             render_to_string(
                 "rv_voting_calc/steps/removed_option.html",
                 {
                     "iteration": iteration,
-                    "option_key": option_key,
-                    "option": option
+                    "option_key": eliminated_winner,
+                    "option": options[eliminated_winner],
+                    "options": options,
+                    "options_by_member": options_by_member,
+                    "rv_members": rv_members,
+                    "randomly": True
                 },
                 request=request,
             )
         )
 
-        del options[most_unpopular_options[0]]
+        return options, False
 
-        if len(options) > 1:
-            # Continue through to next loop
-            return options, False
+    ## END Step C
+
+    ## BEGIN Step D
+
+    # Sort options by their vote count, with lowest ranking ones first.
+    options = {
+        key: value
+        for key, value
+        in sorted(
+            options.items(),
+            key=lambda option: option[1]["vote_count"],
+        )
+    }
+
+    # Find losing options.
+    loser_vote_count = options[next(iter(options))]["vote_count"]
+    loser_count = 0
+    losing_option_keys = []
+
+    for option_key, option in options.items():
+        if option["vote_count"] == loser_vote_count:
+            losing_option_keys.append(option_key)
+            loser_count += 1
+
+    # If there is exactly one losing option, delete it and move on to the next
+    # iteration.
+    if loser_count == 1:
+        loser_key = next(iter(options))
+
+        options[loser_key]["marked_for_deletion"] = True
 
         steps.append(
             render_to_string(
-                "rv_voting_calc/steps/found_popularity_winner.html",
+                "rv_voting_calc/steps/removed_option.html",
                 {
-                    "option_key": option_key,
-                    "option": option
+                    "iteration": iteration,
+                    "option_key": loser_key,
+                    "option": options[loser_key],
+                    "options": options,
+                    "options_by_member": options_by_member,
+                    "rv_members": rv_members,
                 },
                 request=request,
             )
         )
 
-        return options, True
-
-    # Remove options with least tickets
-    min_unpopular_option_ticket_count = math.inf
-
-    for unpopular_option in most_unpopular_options:
-        if options[unpopular_option]["ticket_count"] < min_unpopular_option_ticket_count:
-            min_unpopular_option_ticket_count = options[unpopular_option]["ticket_count"]
-
-    unpopular_options_to_remove = []
+        return options, False
+    else:
+        # If there are multiple losing options, order them by the amount of
+        # ticket votes they have.
+        losing_option_keys.sort(
+            key=lambda key: options[key]["ticket_count"]
+        )
 
-    for unpopular_option in most_unpopular_options:
-        if options[unpopular_option]["ticket_count"] == min_unpopular_option_ticket_count:
-            unpopular_options_to_remove.append(unpopular_option)
+        options[losing_option_keys[0]]["marked_for_deletion"] = True
 
-    for option in unpopular_options_to_remove:
+        # Delete the vote with the least ticket votes. There maybe more, but
+        # those should be taken care of in the next iteration.
         steps.append(
             render_to_string(
                 "rv_voting_calc/steps/removed_option.html",
                 {
                     "iteration": iteration,
-                    "option_key": option,
-                    "option": options[option]
+                    "option_key": losing_option_keys[0],
+                    "option": options[losing_option_keys[0]],
+                    "options": options,
+                    "options_by_member": options_by_member,
+                    "rv_members": rv_members,
                 },
                 request=request,
             )
         )
 
-        del options[option]
+        # End this iteration and move to the next one.
+        return options, False
 
     ## END Step D
 
+    # (TODO: Will this function ever actually reach the code below?)
+
+    # If there are no winners, show that and end everything.
     if len(options) == 0:
         steps.append(
             render_to_string(
@@ -321,10 +386,7 @@ def do_step_b_through_d(
 
         return options, True
 
-    #print(options)
-    #import time
-    #time.sleep(1)
-
+    # Continue on to the next iteration.
     return options, False
 
 
@@ -334,39 +396,14 @@ def get_calculated_votes(request):
     # BEGIN Input validation
 
     if options_by_member is None:
-        return HttpResponseBadRequest("Nebyly zadány žádné hlasy")
-
-    try:
-        options_by_member = json.loads(options_by_member)
+        raise HTTPExceptions.BAD_REQUEST
 
-        if not isinstance(options_by_member, dict):
-            raise ValueError
-    except ValueError:
-        return HttpResponseBadRequest("Formát hlasů není validní")
-
-    rv_members = convert_rv_members_to_dict(get_rv_members())
-
-    integrity_check_failed = False
-
-    for member, options in options_by_member.items():
-        # `member` is not in the list of known members
-        if member not in options_by_member:
-            integrity_check_failed = True
-            break
-
-        # `options` is a list
-        if not isinstance(options, list):
-            integrity_check_failed = True
-            break
-
-        # all items in `options` are strings
-        for item in options:
-            if not isinstance(item, str):
-                integrity_check_failed = True
-                break
-
-    if integrity_check_failed:
-        return HttpResponseBadRequest("Formát hlasů není validní")
+    rv_members, rv_gid = get_rv_members(request, get_rv_gid=True)
+    options_by_member = get_options_by_member(
+        options_by_member,
+        rv_members=rv_members,
+    )
+    rv_members = convert_rv_members_to_dict(rv_members)
 
     # END Input validation
 
@@ -383,40 +420,48 @@ def get_calculated_votes(request):
     if total_ticket_count % 2 == 0:
         has_support_treshold += 1
 
-    max_vote_position = 0
-
+    # For each member's selected options...
     for member, selected_options in options_by_member.items():
-        option_is_ticket = True
-
         for position, option in enumerate(selected_options):
-            if position > max_vote_position:
-                max_vote_position = position
-
+            # Add the option to the options list if it isn't there already.
             if option not in options:
                 options[option] = {
                     "ticket_count": 0,
                     "vote_count": 0,
-                    "points": 0,
-                    "is_popularity_winner": False,
-                    "votes": [],
-                    "has_support": False
+                    "marked_for_deletion": False,
+                    "has_support": False,
+                    "ticket_votes": [],
                 }
 
-            options[option]["votes"].append({
-                "position": position,
+            # If this vote isn't a ticket vote, don't do anything else.
+            if position != 0:
+                continue
+
+            # Add this vote to the option's ticket votes.
+            options[option]["ticket_votes"].append({
                 "member": member,
+                "other_acceptable": selected_options[1:]
             })
-
-            if option_is_ticket:
-                options[option]["ticket_count"] += 1
-
+            # Increase the vote and ticket vote counters.
+            options[option]["ticket_count"] += 1
             options[option]["vote_count"] += 1
 
-            option_is_ticket = False
-
-    # Filtering out options without enough support and figuring out
-    # the most popular options
-
+            # For all other acceptable options the member selected...
+            for other_acceptable_option in selected_options[1:]:
+                # If the option isn't among in the options list, add it.
+                if other_acceptable_option not in options:
+                    options[other_acceptable_option] = {
+                        "ticket_count": 0,
+                        "vote_count": 0,
+                        "marked_for_deletion": False,
+                        "has_support": False,
+                        "ticket_votes": [],
+                    }
+
+                # Increase the option's vote counter.
+                options[other_acceptable_option]["vote_count"] += 1
+
+    # Mark options that don't meet the >half acceptability treshold as such.
     options_without_support = []
 
     for option_key, option in options.items():
@@ -427,45 +472,23 @@ def get_calculated_votes(request):
             options_without_support.append(option_key)
             continue
 
-        option["points"] += option["ticket_count"] * (max_vote_position + 1)
-
-        for vote in option["votes"]:
-            option["points"] += (
-                max_vote_position
-                - vote["position"]
-                + 1
-            )
-
+    # Order the options based on whether they have support or not.
+    # This is purely for aesthetic purpose.
     options = {
         key: value
         for key, value
         in sorted(
             options.items(),
             reverse=True,
-            key=lambda option: option[1]["points"],
+            key=lambda option: option[1]["has_support"],
         )
     }
 
-    first_step_options = options.copy()
-
-    for option in first_step_options.values():
-        votes_to_remove = []
-
-        for vote_list_position, vote in enumerate(option["votes"]):
-            if vote["position"] != 0:
-                votes_to_remove.append(vote_list_position)
-
-        index_offset = 0
-
-        for vote_list_position in votes_to_remove:
-            option["votes"].pop(vote_list_position - index_offset)
-            index_offset += 1
-
     steps.append(
         render_to_string(
             "rv_voting_calc/steps/initial_sort.html",
             {
-                "options": first_step_options,
+                "options": options,
                 "options_by_member": options_by_member,
                 "rv_members": rv_members,
                 "total_ticket_count": total_ticket_count,
@@ -475,22 +498,25 @@ def get_calculated_votes(request):
         )
     )
 
-    del first_step_options
+    ## END Step A
 
-    steps.append(
-        render_to_string(
-            "rv_voting_calc/steps/with_support.html",
-            {
-                "options": options,
-                "options_by_member": options_by_member,
-                "rv_members": rv_members
-            },
-            request=request,
+    # Make sure we can always repeat a previously made calculation. This is also
+    # useful for permalinks.
+
+    # First, try to find the seed in the URL parameters
+    seed = request.GET.get(
+        "seed",
+        # Then, try to find it in the cookies
+        request.COOKIES.get(
+            "seed",
+            # If neither are set, generate a default.
+            base64.b64encode(secrets.token_bytes()).decode("utf-8")
         )
     )
+    random.seed(seed)
 
-    ## END Step A
-
+    # Continue until steps B - D have reached a final conclusion and count
+    # the amount of times the function ran to achieve this.
     iteration = 0
 
     while True:
@@ -499,14 +525,19 @@ def get_calculated_votes(request):
             iteration,
             steps,
             options,
-            options_by_member,
             options_without_support,
+            options_by_member,
             rv_members,
-            has_support_treshold,
         )
 
         if ended:
-            return HttpResponse("\n".join(steps))
+            response = HttpResponse("\n".join(steps))
+            # Set the random seed cookie to the one used in the calculation.
+            response.set_cookie("seed", seed)
+            # Do the same for RV's GID.
+            response.set_cookie("rv_gid", rv_gid)
+
+            return response
 
         # Step E
         iteration += 1    
diff --git a/rybicka/settings/base.py b/rybicka/settings/base.py
index 2ee813f7e68ac15cbf213be0d3ed8c374f2654c5..9d59ae05d40982c7e2da515a79e225197e7ba1de 100644
--- a/rybicka/settings/base.py
+++ b/rybicka/settings/base.py
@@ -57,6 +57,9 @@ MIDDLEWARE = [
     "django.middleware.csrf.CsrfViewMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+
+    "django_http_exceptions.middleware.ExceptionHandlerMiddleware",
+    "django_http_exceptions.middleware.ThreadLocalRequestMiddleware",
 ]
 
 ROOT_URLCONF = "rybicka.urls"
diff --git a/static_src/rv_voting_calc.js b/static_src/rv_voting_calc.js
index 881051eff0c78afa54200be6e2f0bc0ef630bbe3..58d6e09305478f3e9f0d1195aa234f061dfb62f1 100644
--- a/static_src/rv_voting_calc.js
+++ b/static_src/rv_voting_calc.js
@@ -2,6 +2,8 @@ import jQuery from "jquery";
 
 Object.assign(window, { $: jQuery, jQuery });
 
+import Cookies from "js-cookie";
+
 require("select2/dist/js/i18n/cs");
 import "select2/dist/js/select2.full";
 import "select2/dist/css/select2.min.css";
@@ -134,8 +136,21 @@ $(window).ready(
                     }
                 }
 
+                const urlParams = new URL(window.location).searchParams;
+                const rvGid = urlParams.get("rv_gid");
+                const seed = urlParams.get("seed");
+                
                 let response = await fetch(
-                    `${VOTE_CALCULATION_ENDPOINT}?votes=${encodeURIComponent(JSON.stringify(votes))}`
+                    VOTE_CALCULATION_ENDPOINT
+                    + `?votes=${encodeURIComponent(JSON.stringify(votes))}`
+                    + (
+                        (rvGid !== null) ?
+                        `&rv_gid=${rvGid}` : ""
+                    )
+                    + (
+                        (seed !== null) ?
+                        `&seed=${seed}` : ""
+                    )
                 );
 
                 if (!response.ok) {
@@ -144,6 +159,18 @@ $(window).ready(
                 }
 
                 $("#result").html(await response.text());
+
+                $("#permalink").show();
+                $("#permalink").attr(
+                    "href",
+                    (
+                        // Base URL
+                        window.location.href.split('?')[0]
+                        + `?votes=${encodeURIComponent(JSON.stringify(votes))}`
+                        + `&rv_gid=${Cookies.get("rv_gid")}`
+                        + `&seed=${Cookies.get("seed")}`
+                    )
+                );
             }
         );
     }