Compare commits

...

25 Commits
v1.0.0 ... main

Author SHA1 Message Date
gpkvt 6810cc315c Added link to demo 2023-07-03 11:53:14 +02:00
gpkvt c60f596816 fixed help position 2023-06-08 07:27:32 +02:00
gpkvt b343af8dfe improved display on small screens 2023-06-08 00:54:26 +02:00
gpkvt bff08ceddf z-index fixes 2023-06-07 22:29:30 +02:00
gpkvt 84371968e5 added "or" voting option 2023-06-07 22:27:34 +02:00
gpkvt 3a4602be36 improved question 2023-06-07 18:38:07 +02:00
gpkvt 8eab8ada15 Added nginx config 2023-06-06 23:02:29 +02:00
gpkvt 8b4ff7df58 pylinted 2023-06-06 22:47:34 +02:00
gpkvt 203777512a fixed initial theme 2023-06-06 22:31:29 +02:00
gpkvt 70ff903457 fixed seperator text 2023-06-06 21:22:45 +02:00
gpkvt 33f4054d63 fixed questions 2023-06-06 18:10:00 +02:00
gpkvt dcf67c5a7b adjusted help 2023-06-06 17:44:18 +02:00
gpkvt d6f88dacf8 fixed theme switcher 2023-06-06 17:37:43 +02:00
gpkvt 1353351c0e use url_for for css and js 2023-06-06 17:23:23 +02:00
gpkvt 7d5c522e6a fixed title 2023-06-06 17:17:15 +02:00
gpkvt c19ded10b1 added form to submit questions via mail 2023-06-06 14:36:24 +02:00
gpkvt cf59ff986f made bg image less opaque 2023-06-06 11:44:51 +02:00
gpkvt 321731f49e Added background image 2023-06-06 11:33:48 +02:00
gpkvt 973cce5509 simplified template variables 2023-06-06 10:42:00 +02:00
gpkvt 35ee1b06e5 removed unused imports 2023-06-06 00:37:22 +02:00
gpkvt da1aca9e69 Removed logging 2023-06-06 00:33:14 +02:00
gpkvt b58c6dca64 fixed animation jump 2023-06-06 00:06:18 +02:00
gpkvt 2e74a5ba3d Improved animations 2023-06-05 17:05:27 +02:00
gpkvt 7c435efbdb Improved help 2023-06-05 16:47:32 +02:00
gpkvt 1986be119b Simplyfied helptext config 2023-06-05 16:43:21 +02:00
13 changed files with 396 additions and 165 deletions

View File

@ -2,6 +2,10 @@
Simple choice game.
## Demo
https://ab.21x9.org
## Installation
0. Install `python3` (and `pip` if needed)
@ -28,3 +32,35 @@ Or just run `python3 ./game.py`
## Use
Open your browser and navigate to `http://localhost:5000` or whatever you configured as `bind` and `port` in your `config.ini`.
## Reverse Proxy
To run this game in an production environment it's strongly recommended to set up an reverse proxy upfront.
### nginx
Make sure you understand the config and replace the `location` and/or `proxy_pass` value if necessary.
```nginx
location / {
proxy_pass http://127.0.0.1:5000/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 32M;
proxy_redirect off;
proxy_intercept_errors off;
proxy_read_timeout 86400s;
proxy_ignore_client_abort on;
proxy_connect_timeout 120s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_headers_hash_max_size 512;
proxy_buffering on;
proxy_cache_bypass $http_pragma $http_authorization $cookie_nocache;
}
```

6
ab.txt
View File

@ -769,7 +769,7 @@ Zucker;Salz
Crushed Eis;Eiswürfel
Locken;glatte Haare
im Laden kaufen;Online Shopping
Nutella mit;ohne Butter
Frühling;Herbst
Beziehung;Single-Life
Löffel;Gabel
Meer;Berge
@ -792,7 +792,7 @@ Jogginghose;Jeans
Samstag;Sonntag
Schwitzen;Frieren
Die;das Nutella
Android;iPhone
Frühling;Winter
Stadt;Dorf
McDonald's;Burger King
Schokolade;Chips
@ -1035,7 +1035,7 @@ Multiplayer;Einzelspieler
Pizza Hawaii;keine Pizza
Pacman;Tetris
Tischfußball;Tischtennis
Computer;Spielkonsolen
PC;Spielkonsolen
Live-Action-Rollenspiele;Online-Spiele
Wrestling;Mix Martial Arts
Bowling;Tennis

View File

@ -3,7 +3,11 @@ port = 5000
bind = 0.0.0.0
base_url = https://ab.21x9.org
timezone = Europe/Berlin
mail = abgame@21x9.org
mailform = false
mailserver = localhost:25
mailto = abgame@21x9.org
mailfrom = abgame@21x9.org
mailsubject = A/B Game Questions
separator_char = ;
theme = dark
animations = 1
@ -12,15 +16,15 @@ animations = 1
lang = de
more = Weiter »»»
title = A oder B
mailtext = Einreichen!
help_title = Hilfe
help_1 = Wähle die Antworten durch Klicken oder mit der Tastatur (a/b) aus. Sobald alle Antworten ausgewählt wurden, werden nach einer kurzen Wartezeit automatisch neue Fragen geladen.
help_2 = Möchtest du nicht alle Fragen beantworten, klicke einfach auf die Schaltfläche "Weiter" unten rechts (das klappt auch, während der Countdown läuft).
help_3 = Klicke/tippe außerhalb dieses Fensters oder bewege die Maus, um fortzufahren.
help_4 = Diese Webseite zeichnet keine Daten auf und verwendet keine Cookies.
help_5 =
mail_link = Einreichen!
help_link = Hilfe?
help = <strong>Hilfe</strong><p>Wähle die Antworten durch Klicken/Tippen oder mit der Tastatur durch Drücken der Tasten "a" bzw. "b" aus (beantwortet die jeweils nächste unbeantwortete Frage), eine Frage kann mit "o" übersprungen werden. Sobald alle Antworten ausgewählt wurden, werden nach einer kurzen Wartezeit automatisch neue Fragen geladen.</p><p>Möchtest Du nicht alle Fragen beantworten, klicke einfach auf die Schaltfläche "Weiter" unten rechts oder drücke "F5" (beides klappt auch, während der Countdown läuft) bzw. "s" für Skip (mit kurzer Wartezeit).</p><p>Klicke/tippe außerhalb dieses Fensters oder bewege die Maus, um fortzufahren.</p><p>Diese Webseite zeichnet keine Daten auf und verwendet keine Cookies.</p>
separator = oder
questions_prefix = Es gibt derzeit
questions_suffix = Fragen.
send_questions = Einreichen!
desc = Ein simples Entweder/Oder-Entscheidungsspiel
submit = Absenden
submit_questions = <p>Bitte gib deine Fragenvorschläge in das untenstehende Feld ein.</p><p>Wenn du mir einen Gefallen tun möchtest, gib die Vorschläge im Format "A;B" an und schreibe immer nur ein Fragenpaar in eine Zeile.</p><p>Danke!</p>
submit_thanks_title = Danke!
submit_thanks = <p>Vielen Dank für die Einsendung.</p>

236
game.py
View File

@ -1,12 +1,16 @@
#! /bin/python3
from random import choice
from flask import Flask, render_template, request
"""
Simple A/B choice game
"""
from configparser import ConfigParser
from datetime import datetime
from email.message import EmailMessage
import smtplib
import random
import logging
import pytz
from flask import Flask, render_template, request
app = Flask(__name__,
static_url_path='',
@ -15,116 +19,150 @@ app = Flask(__name__,
config = ConfigParser()
config.read('config.ini')
logging.basicConfig(encoding='utf-8', level=logging.INFO, format='%(message)s')
logger = logging.getLogger('waitress')
tz = pytz.timezone(config.get('main', 'timezone'))
@app.after_request
def log_the_request(response):
now = datetime.now(tz=tz)
i18n = {
'lang': config.get('i18n', 'lang'),
'title': config.get('i18n', 'title'),
'more': config.get('i18n', 'more'),
'desc': config.get('i18n', 'desc'),
'questions_prefix': config.get('i18n', 'questions_prefix'),
'questions_suffix': config.get('i18n', 'questions_suffix'),
'separator': config.get('i18n', 'separator'),
'mailtext': config.get('i18n', 'mail_link'),
'helptext': config.get('i18n', 'help_link'),
'help': config.get('i18n', 'help'),
'submit': config.get('i18n', 'submit'),
'submit_questions': config.get('i18n', 'submit_questions'),
'submit_thanks_title': config.get('i18n', 'submit_thanks_title'),
'submit_thanks': config.get('i18n', 'submit_thanks')
}
conf = {
'separator_char': config.get('main', 'separator_char'),
'mailform': config.get('main', 'mailform').lower(),
'mailserver': config.get('main', 'mailserver'),
'mailto': config.get('main', 'mailto'),
'mailfrom': config.get('main', 'mailfrom'),
'mailsubject': config.get('main', 'mailsubject'),
'url': config.get('main', 'base_url'),
'theme': config.get('main', 'theme'),
'animations': config.get('main', 'animations')
}
if 'X-Forwarded-For' in request.headers:
remote_addr = request.headers['X-Forwarded-For']
else:
remote_addr = request.remote_addr
with open("ab.txt", "r", encoding="utf-8") as f:
num_lines = sum(1 for _ in f)
if not request.remote_user:
remote_user = "-"
else:
remote_user = request.remote_user
if not request.referrer:
referrer = "-"
else:
referrer = request.referrer
if request.full_path[-1] == '?':
full_path = request.full_path[:-1]
else:
full_path = request.full_path
log = {
'remote_addr': remote_addr,
'remote_user': remote_user,
'url': full_path,
'date': now.strftime("%d/%b/%Y:%H:%M:%S %z"),
'referrer': referrer,
'user_agent': request.user_agent.string,
'method': request.method,
'content_length': response.content_length,
'status_code': response.status_code
}
logfile = "{} - {} [{}] \"{} {}\" {} {} \"{}\" \"{}\"".format(log['remote_addr'], log['remote_user'], log['date'], log['method'], log['url'], log['status_code'], log['content_length'], log['referrer'], log['user_agent'])
logger.info(logfile)
return response
@app.errorhandler(404)
def page_not_found(e):
lang = config.get('i18n', 'lang')
title = config.get('i18n', 'title')
url = config.get('main', 'base_url')
theme = config.get('main', 'theme')
desc = config.get('i18n', 'desc')
return render_template('404.html', title=title, lang=lang, desc=desc, theme=theme, url=url), 404
@app.errorhandler(500)
def internal_server_error(e):
lang = config.get('i18n', 'lang')
title = config.get('i18n', 'title')
url = config.get('main', 'base_url')
theme = config.get('main', 'theme')
desc = config.get('i18n', 'desc')
return render_template('500.html', title=title, lang=lang, desc=desc, theme=theme, url=url), 500
@app.route("/")
def hello():
lang = config.get('i18n', 'lang')
title = config.get('i18n', 'title')
more = config.get('i18n', 'more')
desc = config.get('i18n', 'desc')
questions_prefix = config.get('i18n', 'questions_prefix')
questions_suffix = config.get('i18n', 'questions_suffix')
send_questions = config.get('i18n', 'send_questions')
separator = config.get('i18n', 'separator')
separator_char = config.get('main', 'separator_char')
mail = config.get('main', 'mail')
mailtext = config.get('i18n', 'mailtext')
help = config.get('i18n', 'help_title')
help1 = config.get('i18n', 'help_1')
help2 = config.get('i18n', 'help_2')
help3 = config.get('i18n', 'help_3')
help4 = config.get('i18n', 'help_4')
help5 = config.get('i18n', 'help_5')
url = config.get('main', 'base_url')
theme = config.get('main', 'theme')
animations = config.get('main', 'animations')
ablines = []
def get_epoch():
"""
Get current time as epoch timestamp
"""
now = datetime.now(tz=tz)
epoch = now.timestamp()
epoch = int(epoch)
lines = getContent()
while len(lines) < 2:
logger.error('Error reading content')
print(lines)
lines = getContent()
return epoch
for line in lines:
ab = line.split(separator_char)
ablines.append(
{'A': str(ab[0]), 'B': str(ab[1])}
@app.errorhandler(404)
def page_not_found():
"""
404 Error Page
"""
epoch = get_epoch()
return render_template('404.html', config=conf, i18n=i18n, epoch=epoch), 404
@app.errorhandler(500)
def internal_server_error():
"""
500 Error Page
"""
epoch = get_epoch()
return render_template(
'500.html',
config=conf,
i18n=i18n,
epoch=epoch
), 500
if conf['mailform'] == "true":
@app.route("/form")
def mailform():
"""
Mail form
"""
epoch = get_epoch()
return render_template(
'mailform.html',
config=conf,
i18n=i18n,
epoch=epoch,
num_lines=num_lines
)
with open("ab.txt", "r") as f:
num_lines = sum(1 for _ in f)
@app.route("/send", methods=['POST', 'GET'])
def sendmail():
"""
Send form data via E-Mail
"""
epoch = get_epoch()
return render_template('index.html', title=title, separator=separator, content=ablines, num_lines=num_lines, epoch=epoch, mailto=mail, more=more, questions_prefix=questions_prefix, questions_suffix=questions_suffix, send_questions=send_questions, lang=lang, url=url, desc=desc, theme=theme, mailtext=mailtext, help=help, help_1=help1, help_2=help2, help_3=help3, help_4=help4, help_5=help5, animations=animations)
if request.method == 'POST':
mailcontent = request.form['questions']
else:
mailcontent = request.args.get('questions')
def getContent():
lines = [a.strip() for a in open("ab.txt", "r").readlines()]
message = EmailMessage()
message.set_content(mailcontent)
message['Subject'] = conf['mailsubject']
message['From'] = conf['mailfrom']
message['To'] = conf['mailto']
smtp_server = smtplib.SMTP(conf['mailserver'])
smtp_server.send_message(message)
smtp_server.quit()
return render_template(
'thanks.html',
config=conf,
i18n=i18n,
epoch=epoch,
questions=mailcontent
)
@app.route("/")
def hello():
"""
Default/Main page
"""
ablines = []
epoch = get_epoch()
lines = get_content()
while len(lines) < 2:
print('Error reading content')
print(lines)
lines = get_content()
for line in lines:
abq = line.split(conf['separator_char'])
ablines.append(
{'A': str(abq[0]), 'B': str(abq[1])}
)
return render_template(
'index.html',
content=ablines,
config=conf,
i18n=i18n,
num_lines=num_lines,
epoch=epoch
)
def get_content():
"""
Read content from file
"""
lines = [a.strip() for a in open("ab.txt", "r", encoding="utf-8").readlines()]
result = random.sample(lines, 5)
return result

View File

@ -1,10 +1,34 @@
label.a.slide {
label.a.slide1 {
animation-duration: 1s;
animation-name: slideinfromleft;
position: relative;
}
label.a.slide2 {
animation-duration: 1.25s;
animation-name: slideinfromleft;
position: relative;
}
label.a.slide3 {
animation-duration: 1.5s;
animation-name: slideinfromleft;
position: relative;
}
label.a.slide4 {
animation-duration: 1.75s;
animation-name: slideinfromleft;
position: relative;
}
label.a.slide5 {
animation-duration: 2s;
animation-name: slideinfromleft;
position: relative;
}
@keyframes slideinfromleft {
from {
left: -640px;
@ -15,12 +39,36 @@ label.a.slide {
}
}
label.b.slide {
label.b.slide1 {
animation-duration: 1s;
animation-name: slideinfromright;
position: relative;
}
label.b.slide2 {
animation-duration: 1.25s;
animation-name: slideinfromright;
position: relative;
}
label.b.slide3 {
animation-duration: 1.5s;
animation-name: slideinfromright;
position: relative;
}
label.b.slide4 {
animation-duration: 1.75s;
animation-name: slideinfromright;
position: relative;
}
label.b.slide5 {
animation-duration: 2s;
animation-name: slideinfromright;
position: relative;
}
@keyframes slideinfromright {
from {
left: 640px;

View File

@ -30,12 +30,13 @@ a #help {
a:hover #help {
position: absolute;
top: 65px;
top: 105px;
left: 50%;
height: auto;
width: 50%;
margin: auto;
padding: 20px;
padding-bottom: 5px;
overflow: visible;
text-decoration: none;
background-color: var(--bg-a);
@ -45,6 +46,39 @@ a:hover #help {
display: inline-block;
text-align: center;
transform: translate(-50%);
z-index: 10;
}
textarea {
height: 250px;
width: 100%;
background-color: var(--bg-b);
color: var(--fg-b);
border: 1px solid var(--bg-a);
}
#background {
overflow: hidden;
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
}
#background::before {
content: ' ';
display: block;
background: url('/ab.jpg');
background-repeat: no-repeat;
background-position: 50% 0;
background-size: cover;
left: 0;
top: 0;
width: 100%;
height: 100%;
position: absolute;
opacity: 0.1;
}
#header {
@ -64,15 +98,19 @@ a:hover #help {
min-width: 400px;
max-width: 900px;
max-height: 100%;
overflow: auto;
overflow: hidden;
width: 65%;
margin: 0;
position: absolute;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.form {
padding: 20px;
}
.ab {
float: none;
border-radius: 10px;
@ -151,6 +189,9 @@ input[type="radio"] {
justify-content: center;
align-items: center;
color: var(--fg-sep);
border-radius: 10px;
z-index: 5;
cursor: pointer;
}
#footer {
@ -204,6 +245,33 @@ input[type="radio"] {
}
@media only screen and (max-width:640px) {
#header {
padding-top: 15px;
padding-bottom: 15px;
}
a:hover #help {
top: 5px;
width: 100%;
padding: 5px;
}
.a {
padding: 15px;
font-size: small;
text-indent: 0;
}
.b {
padding: 15px;
font-size: small;
text-indent: 0;
}
.o {
padding: 15px;
}
#content {
min-width: 95%;
transform: none;
@ -215,7 +283,8 @@ input[type="radio"] {
#next {
min-width: 100%;
margin-top: 20px;
margin-top: 10px;
height: 50px;
}
#questions {

View File

@ -9,7 +9,7 @@ function countdown() {
document.getElementById("next").innerHTML = timeleft;
}
timeleft -= 1;
}, 1000);
}, 500);
}
function autoReload() {

View File

@ -53,6 +53,12 @@ window.addEventListener("keydown", function (event) {
case "b":
check("b");
break;
case "o":
check("o");
break;
case "s":
countdown();
break;
default:
checkCount();
return;

View File

@ -4,6 +4,6 @@ directory=/opt/ab/
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile_maxbytes=32MB
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
user=www-data

View File

@ -1,19 +1,13 @@
<!DOCTYPE html>
{%- if theme == "light" %}
<html lang="{{ lang }}" data-theme="light">
{%- else %}
<html lang="{{ lang }}" data-theme="dark">
{%- endif %}
<html lang="{{ i18n.lang }}" data-theme="{{ config.theme }}">
<head>
<title>{{ title }}</title>
<script type="text/javascript" src="/js/toggle.js?t={{ epoch }}"></script>
<script type="text/javascript" src="/js/autoReload.js?t={{ epoch }}"></script>
<script type="text/javascript" src="/js/shortcut.js?t={{ epoch }}"></script>
<link rel="stylesheet" type="text/css" href="/css/base.css?t={{ epoch }}">
<link rel="stylesheet" type="text/css" href="/css/colors.css?t={{ epoch }}">
{%- if animations == "1" or animations == "true" %}
<link rel="stylesheet" type="text/css" href="/css/animations.css?t={{ epoch }}">
<title>{{ i18n.title }}</title>
<script type="text/javascript" src="{{ url_for('static', filename='/js/toggle.js') }}?t={{ epoch }}"></script>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='/css/base.css') }}?t={{ epoch }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='/css/colors.css') }}?t={{ epoch }}">
{%- if config.animations == "1" or config.animations == "true" %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='/css/animations.css') }}?t={{ epoch }}">
{%- endif %}
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon-32x32.png') }}">
@ -24,25 +18,25 @@
<meta name="theme-color" content="#ffffff">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<meta property="og:title" content="{{ title }}">
<meta property="og:site_name" content="{{ title }}">
<meta property="og:url" content="{{ url }}">
<meta property="og:description" content="{{ desc }}">
<meta property="og:title" content="{{ i18n.title }}">
<meta property="og:site_name" content="{{ i18n.title }}">
<meta property="og:url" content="{{ config.url }}">
<meta property="og:description" content="{{ i18n.desc }}">
<meta property="og:type" content="website">
<meta property="og:image" content="{{ url_for('static', filename='ab.jpg') }}">
</head>
<body>
<div id="content">
<div id="header">{{ title }}</div>
<hr>
{%- block main %}
{%- endblock %}
<div id="background">
<div id="content">
<div id="header">{{ i18n.title }}</div>
<hr>
{%- block main %}
{%- endblock %}
</div>
</div>
<script type="text/javascript">
initToggle();
autoReload();
monitorRadio();
</script>
</body>

View File

@ -1,46 +1,44 @@
{% extends 'base.html' %}
{% block main %}
<script type="text/javascript" src="{{ url_for('static', filename='/js/autoReload.js') }}?t={{ epoch }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='/js/shortcut.js') }}?t={{ epoch }}"></script>
{%- set id = namespace(value=0) %}
{%- for question in content %}
{%- set id.value = id.value + 1 %}
<div class="ab">
<input type="radio" id="a{{ id.value }}" class="a" name="group-{{ id.value }}">
<label for="a{{ id.value }}" class="a slide" title="{{ question['A'] }}"> A)&nbsp;{{ question['A'] }}</label>
<div class="separator">{{ separator }}</div>
<label for="a{{ id.value }}" class="a slide{{ id.value }}" title="{{ question['A'] }}"> A)&nbsp;{{ question['A'] }}</label>
<input type="radio" id="o{{ id.value }}" class="separator" name="group-{{ id.value }}">
<label for="o{{ id.value }}" class="separator">{{ i18n.separator }}</label>
<input type="radio" id="b{{ id.value }}" class="b" name="group-{{ id.value }}">
<label for="b{{ id.value }}" class="b slide" title="{{ question['B'] }}">B)&nbsp;{{ question['B'] }}</label>
<label for="b{{ id.value }}" class="b slide{{ id.value }}" title="{{ question['B'] }}">B)&nbsp;{{ question['B'] }}</label>
</div>
<hr>
{%- endfor %}
<div id="footer">
<div id="questions">
{{ questions_prefix }} {{ num_lines }} {{ questions_suffix }}
{{ i18n.questions_prefix }} {{ num_lines }} {{ i18n.questions_suffix }}
<br>
<a href="mailto:{{ mailto }}">{{ mailtext }}</a> -
<a href="#">{{ help }}?
{%- if config.mailform == "true" %}
<a href="/form">{{ i18n.mailtext }}</a> -
{%- endif %}
<a href="#">{{ i18n.helptext }}
<div id="help">
<strong>{{ help }}</strong>
<p>{{ help_1 }}</p>
{%- if help_2 %}
<p>{{ help_2 }}</p>
{%- endif %}
{%- if help_3 %}
<p>{{ help_3 }}</p>
{%- endif %}
{%- if help_4 %}
<p>{{ help_4 }}</p>
{%- endif %}
{%- if help_5 %}
<p>{{ help_5 }}</p>
{%- endif %}
{{ i18n.help|safe }}
</div>
</a>
</div>
{% include 'toggle.html' %}
<form action="/" method="get">
<input type="hidden" name="t" value="{{ epoch }}">
<button id="next" type="submit">{{ more }}</button>
<button id="next" type="submit">{{ i18n.more }}</button>
</form>
</div>
{% endblock %}
<script type="text/javascript">
autoReload();
monitorRadio();
</script>
{% endblock %}

22
templates/mailform.html Normal file
View File

@ -0,0 +1,22 @@
{% extends 'base.html' %}
{% block main %}
<form action="/send" method="post">
<input type="hidden" name="t" value="{{ epoch }}">
<div class="ab">
<div class="form">
<div><label for="questions">{{ i18n.submit_questions | safe }}</label></div>
<div><textarea name="questions" required></textarea></div>
</div>
</div>
<div id="footer">
<div id="questions">
{{ i18n.questions_prefix }} {{ num_lines }} {{ i18n.questions_suffix }}
<br>
<a href="/">Home</a>
</div>
{% include 'toggle.html' %}
<button id="next" type="submit">{{ i18n.submit }}</button>
</div>
</form>
{% endblock %}

16
templates/thanks.html Normal file
View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block main %}
<div class="ab">
<div class="a">
<h1>{{ i18n.submit_thanks_title }}</h1>
{{ i18n.submit_thanks | safe }}
<p>[<a href="/">Home</a>]</p>
</div>
</div>
<div id="footer">
<div class="box">&nbsp;</div>
{% include 'toggle.html' %}
<div class="box">&nbsp;</div>
</div>
{% endblock %}