diff --git a/mo/web/templates/user_contest.html b/mo/web/templates/user_contest.html
index b46abb3f60fc70cb99746e235d38f4010fdeb5e7..b1412201ac75014769adfdeaab87b45b2d5b5309 100644
--- a/mo/web/templates/user_contest.html
+++ b/mo/web/templates/user_contest.html
@@ -6,28 +6,52 @@
 
 <h2>{{ round.name }} {{ round.year }}. ročníku kategorie {{ round.category }}: {{ contest.place.name }}</h2>
 
+{% if state == db.RoundState.preparing %}
+<p>
+	Soutěžní kolo se <b>připravuje</b>{% if round.ct_tasks_start and round.ct_tasks_start > g.now %},
+	začne <b>{{ round.ct_tasks_start|timeformat }} ({{ human_timedelta(round.ct_tasks_start, g.now) }})</b>{% endif %}.
+	Až začne, budete mít na této stránce {% if round.has_tasks() %}k dispozici text zadání,{% endif %}
+	přehled úloh a budete zde moci odevzdat svá řešení k jednotlivým úlohám.
+	Do té doby zde nenajdete nic jiného.
+</p>
+{% elif state == db.RoundState.running and not round.can_submit() %}
+<p>
+	Soutěžní kolo <b>je připraveno</b>, ale zatím nelze odevzdávat. Odevzdávání začne
+	<b>{{ round.ct_tasks_start|timeformat }} ({{ human_timedelta(round.ct_tasks_start, g.now) }})</b>.
+	Až začne, budete mít na této stránce {% if round.has_tasks() %}k dispozici text zadání,{% endif %}
+	přehled úloh a budete zde moci odevzdat svá řešení k jednotlivým úlohám.
+	Do té doby zde nenajdete nic jiného.
+</p>
+{% else %}
 {% if state == db.RoundState.running %}
-<p>Soutěž běží.
-	{% if round.ct_submit_end == None %}
-		Můžete odevzdávat svá řešení.
-	{% else %}
-		Svá řešení odevzdávejte do {{ round.ct_submit_end|timeformat }}.
-		V případě technických problémů můžete odevzdat i později,
-		ale není zaručeno, že řešení budou hodnocena.
-	{% endif %}
-
+<p>
+{% if round.ct_submit_end == None %}
+	Soutěžní kolo běží, <b>můžete odevzdávat svá řešení.</b>
+{% elif round.ct_submit_end > g.now %}
+	Soutěžní kolo běží, <b>svá řešení odevzdávejte do {{ round.ct_submit_end|timeformat }} ({{ human_timedelta(round.ct_submit_end, g.now) }})</b>.
+	V případě technických problémů můžete odevzdat i později, ale není zaručeno, že řešení budou hodnocena.
+{% else %}
+	Řádný termín soutěžního kola <b>již skončil</b> (v {{ round.ct_submit_end|timeformat }}, {{ human_timedelta(round.ct_submit_end, g.now) }}),
+	ale stále můžete odevzdat svá řešení, která se vám nepovedla odevzdat kvůli
+	technickým problémům. Není však zaručeno, že řešení budou hodnocena.
+{% endif %}
+</p>
+{% if round.can_submit() %}
 <p>Řešení odevzdávejte ve formátu PDF jako soubor o velikosti maximálně
 {{ max_submit_size // 1048576 }} MB.
+{% endif %}
 
 {% elif state == db.RoundState.grading %}
 <p>Odevzdávání bylo ukončeno. Vyčkejte prosím, až úlohy opravíme.
 {% elif state == db.RoundState.closed %}
-<p>FIXME
+<p>Soutěžní kolo bylo ukončeno, níže si můžete prohlédnout svá ohodnocená a okomentovaná řešení.
+
+<p>FIXME výsledkovka
 {% else %}
 <p>Soutěž se nachází v neznámém stavu. To by se nemělo stát :)
 {% endif %}
 
-{% if statement_visible %}
+{% if round.task_statement_available() %}
 <p>Můžete si stáhnout <a href='{{ url_for('user_task_statement', id=contest.contest_id) }}'>zadání úloh</a>.
 {% endif %}
 
@@ -41,9 +65,8 @@
 		{% if round.state == db.RoundState.closed %}
 			<th>Opraveno
 			<th>Body
-		{% else %}
-			<th>Akce
 		{% endif %}
+			<th>Akce
 	<tbody>
 {% for task, sol in task_sols %}
 		<tr>
@@ -60,13 +83,13 @@
 			<td>
 			{% endif %}
 			<td>{{ sol.points if sol.points != None else '–' }}
-		{% else %}
-			<td>
-			{% if round.state == db.RoundState.running %}
-				<a class='btn btn-xs btn-primary' href='{{ url_for('user_contest_task', contest_id=contest.contest_id, task_id=task.task_id) }}'>Odevzdat</a>
-			{% endif %}
 		{% endif %}
+			<td>
+				<a class='btn btn-xs btn-primary' href='{{ url_for('user_contest_task', contest_id=contest.contest_id, task_id=task.task_id) }}'>
+					{% if round.can_submit() %}Odevzdat{% else %}Detail úlohy{% endif %}
+				</a>
 {% endfor %}
 </table>
 
+{% endif %}
 {% endblock %}
diff --git a/mo/web/templates/user_contest_task.html b/mo/web/templates/user_contest_task.html
index 819f7a11854239e13a8036c561bd57a77a236e0d..400ee0e335f2d06d154b2c6db1348001093c9308 100644
--- a/mo/web/templates/user_contest_task.html
+++ b/mo/web/templates/user_contest_task.html
@@ -7,14 +7,18 @@
 
 <p><a href='{{ url_for('user_contest', id=contest.contest_id) }}'>Zpět na seznam úloh</a>
 
+{% if round.can_submit() %}
 <h3>Odevzdat řešení</h3>
 
-{% if round.ct_submit_end != None and g.now > round.ct_submit_end %}
-<p class="alert alert-danger">Pozor, odevzdáváte po termínu. Vaše řešení nemusí být hodnoceno.
-Doporučujeme využít políčko pro komentář a vysvětlit situaci.
+{% if round.ct_submit_end and g.now > round.ct_submit_end %}
+<p class="alert alert-danger">Pozor, odevzdáváte po termínu (uplynul {{ round.ct_submit_end|timeformat }},
+{{ human_timedelta(round.ct_submit_end) }}). Vaše řešení nemusí být hodnoceno. Doporučujeme využít políčko pro komentář a vysvětlit situaci.
 {% endif %}
 
 {{ wtf.quick_form(form, form_type='basic', button_map={'submit': 'primary'}) }}
+{% else %}
+<p><i>Soutěžní kolo neběží, již není možné odevzdat nové řešení.</i></p>
+{% endif %}
 
 <h3>Historie vašich řešení</h3>
 
diff --git a/mo/web/templates/user_index.html b/mo/web/templates/user_index.html
index c55c8fa42446bb53944a903cd66055a3a4cfcaf4..be8b2d4be21f4884e5d88dab3e78505d30babfd7 100644
--- a/mo/web/templates/user_index.html
+++ b/mo/web/templates/user_index.html
@@ -5,7 +5,7 @@
 {% if pions %}
 	<p>Účastníte se následujících kol MO:
 
-	<table class=data>
+	<table class="table">
 		<thead>
 			<tr>
 				<th title='ročník MO'>Roč.
@@ -21,11 +21,9 @@
 				<td>{{ round.category }}
 				<td>{{ round.name }}
 				<td>{{ contest.place.name }}
-				<td>{{ round.state.friendly_name() }}
+				<td>{{ round.long_state() }}
 				<td><div class="btn-group">
-						{% if round.state == db.RoundState.running %}
-						<a class='btn btn-xs btn-primary' href='{{ url_for('user_contest', id=contest.contest_id) }}'>Odevzdávat</a>
-						{% endif %}
+					<a class='btn btn-xs btn-primary' href='{{ url_for('user_contest', id=contest.contest_id) }}'>Detail kola</a>
 				    </div>
 			{% endfor %}
 	</table>
diff --git a/mo/web/user.py b/mo/web/user.py
index e1cc7cdae85cc409416231d0599e067d0725b101..761d1114d68c4179bacc9ab233541b2db7518cdf 100644
--- a/mo/web/user.py
+++ b/mo/web/user.py
@@ -29,7 +29,6 @@ def user_index():
              .join(db.Contest)
              .join(db.Round)
              .filter(db.Participation.user == g.user)
-             .filter(db.Round.state != db.RoundState.preparing)
              .options(joinedload(db.Contest.place))
              .order_by(db.Round.year.desc(), db.Round.category, db.Round.seq)
              .all())
@@ -50,12 +49,6 @@ def get_contest(id: int) -> db.Contest:
     if not contest:
         raise werkzeug.exceptions.NotFound()
 
-    # FIXME: Časem chceme účastníky pustit i v jiných stavech
-    # FIXME: A také místo generického Forbidden říci něco konkrétnějšího
-    #        (je možné, že se sem účastník dostal reloadem stránky po konci contestu)
-    if contest.round.state != db.RoundState.running:
-        raise werkzeug.exceptions.Forbidden()
-
     # FIXME: Kontrolovat nějak pion.state?
     pion = (db.get_session().query(db.Participation)
             .filter_by(user=g.user, contest=contest)
@@ -94,7 +87,6 @@ def user_contest(id: int):
         'user_contest.html',
         contest=contest,
         task_sols=task_sols,
-        statement_visible=contest.round.task_statement_available(),
         max_submit_size=config.MAX_CONTENT_LENGTH,
         db=db,  # kvůli hodnotám enumů
     )
@@ -104,7 +96,7 @@ def user_contest(id: int):
 def user_task_statement(id: int):
     contest = get_contest(id)
 
-    if not contest.round.task_statement_available():
+    if not contest.round.task_statement_available(mo.now):
         logger.warn(f'Účastník #{g.user.user_id} chce zadání, na které nemá právo')
         raise werkzeug.exceptions.Forbidden()
 
@@ -123,8 +115,14 @@ def user_contest_task(contest_id: int, task_id: int):
     task = get_task(contest, task_id)
     sess = db.get_session()
 
+    round = contest.round
+    if round.state == db.RoundState.preparing or (round.state == db.RoundState.running and not round.can_submit(mo.now)):
+        # Dokud se kolo připravuje nebo čeká na zveřejnění zadání, tak ani nezobrazujeme
+        # stránku, abychom něco neprozradili jménem úlohy
+        raise werkzeug.exceptions.Forbidden()
+
     form = SubmitForm()
-    if form.validate_on_submit():
+    if round.can_submit(now) and form.validate_on_submit():
         # FIXME: Tohle je pomalé, dělá se tu zbytečná další kopie dat.
         # Nicméně werkzeugu by měla jít podstrčit stream factory,
         # která bude vyrábět streamy rovnou uložené v našem tmp.