Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

13 changed files with 162 additions and 393 deletions

View file

@ -2,10 +2,6 @@
Simple choice game. Simple choice game.
## Demo
https://ab.21x9.org
## Installation ## Installation
0. Install `python3` (and `pip` if needed) 0. Install `python3` (and `pip` if needed)
@ -32,35 +28,3 @@ Or just run `python3 ./game.py`
## Use ## Use
Open your browser and navigate to `http://localhost:5000` or whatever you configured as `bind` and `port` in your `config.ini`. 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 Crushed Eis;Eiswürfel
Locken;glatte Haare Locken;glatte Haare
im Laden kaufen;Online Shopping im Laden kaufen;Online Shopping
Frühling;Herbst Nutella mit;ohne Butter
Beziehung;Single-Life Beziehung;Single-Life
Löffel;Gabel Löffel;Gabel
Meer;Berge Meer;Berge
@ -792,7 +792,7 @@ Jogginghose;Jeans
Samstag;Sonntag Samstag;Sonntag
Schwitzen;Frieren Schwitzen;Frieren
Die;das Nutella Die;das Nutella
Frühling;Winter Android;iPhone
Stadt;Dorf Stadt;Dorf
McDonald's;Burger King McDonald's;Burger King
Schokolade;Chips Schokolade;Chips
@ -1035,7 +1035,7 @@ Multiplayer;Einzelspieler
Pizza Hawaii;keine Pizza Pizza Hawaii;keine Pizza
Pacman;Tetris Pacman;Tetris
Tischfußball;Tischtennis Tischfußball;Tischtennis
PC;Spielkonsolen Computer;Spielkonsolen
Live-Action-Rollenspiele;Online-Spiele Live-Action-Rollenspiele;Online-Spiele
Wrestling;Mix Martial Arts Wrestling;Mix Martial Arts
Bowling;Tennis Bowling;Tennis

View file

@ -3,11 +3,7 @@ port = 5000
bind = 0.0.0.0 bind = 0.0.0.0
base_url = https://ab.21x9.org base_url = https://ab.21x9.org
timezone = Europe/Berlin timezone = Europe/Berlin
mailform = false mail = abgame@21x9.org
mailserver = localhost:25
mailto = abgame@21x9.org
mailfrom = abgame@21x9.org
mailsubject = A/B Game Questions
separator_char = ; separator_char = ;
theme = dark theme = dark
animations = 1 animations = 1
@ -16,15 +12,15 @@ animations = 1
lang = de lang = de
more = Weiter »»» more = Weiter »»»
title = A oder B title = A oder B
mail_link = Einreichen! mailtext = Einreichen!
help_link = Hilfe? help_title = 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> 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 =
separator = oder separator = oder
questions_prefix = Es gibt derzeit questions_prefix = Es gibt derzeit
questions_suffix = Fragen. questions_suffix = Fragen.
send_questions = Einreichen! send_questions = Einreichen!
desc = Ein simples Entweder/Oder-Entscheidungsspiel 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>

230
game.py
View file

@ -1,16 +1,12 @@
#! /bin/python3 #! /bin/python3
""" from random import choice
Simple A/B choice game from flask import Flask, render_template, request
"""
from configparser import ConfigParser from configparser import ConfigParser
from datetime import datetime from datetime import datetime
from email.message import EmailMessage
import smtplib
import random import random
import logging
import pytz import pytz
from flask import Flask, render_template, request
app = Flask(__name__, app = Flask(__name__,
static_url_path='', static_url_path='',
@ -19,150 +15,116 @@ app = Flask(__name__,
config = ConfigParser() config = ConfigParser()
config.read('config.ini') 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')) tz = pytz.timezone(config.get('main', 'timezone'))
i18n = { @app.after_request
'lang': config.get('i18n', 'lang'), def log_the_request(response):
'title': config.get('i18n', 'title'), now = datetime.now(tz=tz)
'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')
}
with open("ab.txt", "r", encoding="utf-8") as f: if 'X-Forwarded-For' in request.headers:
num_lines = sum(1 for _ in f) remote_addr = request.headers['X-Forwarded-For']
else:
remote_addr = request.remote_addr
def get_epoch(): if not request.remote_user:
""" remote_user = "-"
Get current time as epoch timestamp 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 = []
now = datetime.now(tz=tz) now = datetime.now(tz=tz)
epoch = now.timestamp() epoch = now.timestamp()
epoch = int(epoch) epoch = int(epoch)
return epoch lines = getContent()
@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
)
@app.route("/send", methods=['POST', 'GET'])
def sendmail():
"""
Send form data via E-Mail
"""
epoch = get_epoch()
if request.method == 'POST':
mailcontent = request.form['questions']
else:
mailcontent = request.args.get('questions')
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: while len(lines) < 2:
print('Error reading content') logger.error('Error reading content')
print(lines) print(lines)
lines = get_content() lines = getContent()
for line in lines: for line in lines:
abq = line.split(conf['separator_char']) ab = line.split(separator_char)
ablines.append( ablines.append(
{'A': str(abq[0]), 'B': str(abq[1])} {'A': str(ab[0]), 'B': str(ab[1])}
) )
return render_template( with open("ab.txt", "r") as f:
'index.html', num_lines = sum(1 for _ in f)
content=ablines,
config=conf,
i18n=i18n,
num_lines=num_lines,
epoch=epoch
)
def get_content(): 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)
"""
Read content from file def getContent():
""" lines = [a.strip() for a in open("ab.txt", "r").readlines()]
lines = [a.strip() for a in open("ab.txt", "r", encoding="utf-8").readlines()]
result = random.sample(lines, 5) result = random.sample(lines, 5)
return result return result

View file

@ -1,34 +1,10 @@
label.a.slide1 {
label.a.slide {
animation-duration: 1s; animation-duration: 1s;
animation-name: slideinfromleft; animation-name: slideinfromleft;
position: relative; 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 { @keyframes slideinfromleft {
from { from {
left: -640px; left: -640px;
@ -39,36 +15,12 @@ label.a.slide5 {
} }
} }
label.b.slide1 { label.b.slide {
animation-duration: 1s; animation-duration: 1s;
animation-name: slideinfromright; animation-name: slideinfromright;
position: relative; 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 { @keyframes slideinfromright {
from { from {
left: 640px; left: 640px;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,22 +0,0 @@
{% 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 %}

View file

@ -1,16 +0,0 @@
{% 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 %}