3 Commits

  1. 283
      index.php
  2. 84
      script.js
  3. 170
      style.css

283
index.php

@ -3,6 +3,7 @@ locale_set_default('de_DE');
$config = require_once './config.php'; $config = require_once './config.php';
$START_DATE = $config['start_date']; $START_DATE = $config['start_date'];
$EVENTS = $config['events']; $EVENTS = $config['events'];
$CITIES = ['Berlin', 'Potsdam', 'Magdeburg', 'Braunschweig', 'Hannover', 'Leipzig'];
if ($_SERVER['REQUEST_METHOD'] == "POST") { if ($_SERVER['REQUEST_METHOD'] == "POST") {
$n = 0; $n = 0;
@ -10,7 +11,21 @@ if ($_SERVER['REQUEST_METHOD'] == "POST") {
$file_name = $config['data_dir'] . "/" . time(); $file_name = $config['data_dir'] . "/" . time();
$file = fopen($file_name, "x"); $file = fopen($file_name, "x");
} while ($file === false && ++$n < 5); } while ($file === false && ++$n < 5);
if (!$file || !fwrite($file, var_export($_POST, true))) {
$data = ['dates' => []];
$string_keys = ['name', 'email'];
foreach ($_POST as $key => $v) {
if (($key == 'city' && in_array($v, $CITIES)) ||
($key == 'count' && is_numeric($v)) ||
(in_array($key, $string_keys))) {
$data[$key] = $v;
} else if (preg_match('/^date_(\d\d\d\d-\d\d-\d\d)$/', $key, $matches) && $v == "on") {
$data['dates'][] = $matches[1];
} else {
echo "Invalid value " . htmlspecialchars($v) . " for key " . htmlspecialchars($key);
exit;
}
}
if (!$file || !fwrite($file, json_encode($data))) {
echo "Leider konnte deine Anmeldung nicht gespeichert werden. Bitte melde dich bei luetzi-vernetzung-bb@systemli.org damit wir den Fehler beheben können!"; echo "Leider konnte deine Anmeldung nicht gespeichert werden. Bitte melde dich bei luetzi-vernetzung-bb@systemli.org damit wir den Fehler beheben können!";
exit; exit;
} }
@ -75,180 +90,7 @@ function print_month($start) {
<html lang=de> <html lang=de>
<title>Soli-Bus-Fahrten nach Lützerath</title> <title>Soli-Bus-Fahrten nach Lützerath</title>
<meta name=viewport content="width=device-width, initial-scale=1"> <meta name=viewport content="width=device-width, initial-scale=1">
<style>
body {
margin: 0
}
footer {
border-top: 0.3em dashed #722CA2;
padding: 1em 1em 0 1em;
background: #f1cc37;
color: #5A4B0B;
padding: 1em 0;
}
main {
background: #FFE98E;
padding: 1em 0;
}
header {
border-bottom: 0.3em dashed #722CA2;
background: #f1cc37;
color: #5A4B0B;
padding: 1em 0;
}
h1 {
margin: 0
}
footer > *,
header > *,
main > * {
margin: 0 auto;
max-width: 80rem;
padding: 0 1rem;
}
.month {
display: inline-block;
vertical-align: top;
margin-right: 2em;
border-spacing: 0;
}
.month tbody {
border-radius: 0.3em;
color: #5A4B0B;
}
.month tbody,
.row > input,
.row > select {
box-shadow: .4em .5em 0 0 RGBA(114, 44, 162,0.5);
}
.month ::selection {
background: inherit;
}
.month th, .month td {
width: 3.5em;
height: 3.5em;
border: 1px solid #CBC19A;
border-width: 1px 0 0 1px;
text-align: center;
padding: 0;
background: white;
box-sizing: border-box;
}
.month th:first-of-type {
border-top-left-radius: 0.3em;
}
.month th:last-of-type {
border-top-right-radius: 0.3em;
}
.month tr > :last-of-type {
border-right-width: 1px;
}
.month tr:last-of-type td:first-of-type {
border-bottom-left-radius: 0.3em;
}
.month tr:last-of-type td:last-of-type {
border-bottom-right-radius: 0.3em;
}
.month tr:last-of-type td {
border-bottom-width: 1px;
}
.month input[type=checkbox] {
appearance: none;
position: absolute
}
.month label {
display: block;
line-height: 3.5em;
}
.month :focus + label {
border: 1px solid #722CA2;
margin: -1px;
}
:checked + label:not(.unselecting),
.selecting {
background: #f1cc37;
}
.invalid {
color: #CBC19A;
}
@media only screen and (max-width: 55em) {
.month {
margin-right: 1.5vw;
}
.month th, .month td {
width: 6vw;
}
}
@media only screen and (max-width: 40em) {
.month {
margin: 0 auto;
display: table;
}
.month th, .month td {
width: 3.5em;
}
}
.event {
position: relative;
top: -3.5em;
right: -0.9em;
line-height: initial;
display: block;
}
.event::before {
content: "";
position: absolute;
padding: 0.5em;
top: -0.5em;
right: 0.5em;
}
.event-content {
visibility: hidden;
position: absolute;
background: white;
top: 0.4em;
left: 1.8em;
padding: 0.4em;
border: 1px solid #CBC19A;
border-radius: 0.2em;
box-shadow: .2em .25em 0 0 RGBA(114, 44, 162,0.5);
z-index: 1;
}
.month :focus + label .event-content,
.month td:hover .event-content,
.month td:active .event-content {
visibility: initial;
}
.row {
display: block;
margin: 1em 0;
}
.row > input,
.row > select {
padding: 0.5rem;
margin-right: 1rem;
width: 12rem;
box-sizing: border-box;
border: 1px solid #5A4B0B;
border-radius: .3em;
}
.row > input[type="number"] {
margin-left: 8.5rem;
width: 3.5rem;
}
.row > input[type="submit"] {
background: #f1cc37;
}
.row > input[type="submit"]:active {
background: white;
}
.row :focus-visible {
outline: 0.2em #722CA2 solid;
}
</style>
<style><?= file_get_contents('./style.css') ?></style>
<header> <header>
<h1>Soli-Bus-Fahrten von Berlin nach Lützerath</h1> <h1>Soli-Bus-Fahrten von Berlin nach Lützerath</h1>
@ -277,94 +119,7 @@ print_month($month_end);
?> ?>
</div> </div>
<script>
(function() {
"use strict"
const months = document.getElementById('months')
let selecting = null // { origin, className, range }
let is_select = false
months.addEventListener("pointerdown", event => {
selecting = null
is_select = false
for (const elem of document.querySelectorAll(".selecting, .unselecting")) {
elem.className = ""
}
if (event.buttons !== 1) return
let target = event.target
if (target.nodeType == 3) target = target.parentNode
if (target.tagName !== "LABEL") return
event.preventDefault()
const className = target.className = document.getElementById(target.htmlFor).checked ? "unselecting" : "selecting"
selecting = {
origin: target,
className,
range: [ target ]
}
})
months.addEventListener('pointermove', event => {
if (selecting === null) return
event.preventDefault()
is_select = true
const range = document.createRange()
range.selectNode(selecting.origin)
if (range.comparePoint(event.target, 0) > 0) range.setEndAfter(event.target)
else range.setStartBefore(event.target)
const treeWalker = document.createTreeWalker(
range.commonAncestorContainer,
NodeFilter.SHOW_ELEMENT,
{ acceptNode: (node) => node.nodeName.toLowerCase() === 'label' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }
)
const new_range = []
let currentNode
while (currentNode = treeWalker.nextNode()) {
if (range.intersectsNode(currentNode)) {
new_range.push(currentNode)
const idx = selecting.range.indexOf(currentNode)
if (idx !== -1) selecting.range.splice(idx, 1)
else currentNode.className = selecting.className
}
}
for (const elem of selecting.range) {
elem.className = ""
}
selecting.range = new_range
})
document.addEventListener('pointercancel', event => {
if (selecting === null) return
for (const elem of selecting.range) {
elem.className = ""
}
selecting = null
is_select = false
})
document.addEventListener('pointerup', event => {
if (selecting === null) return
event.preventDefault()
for (const elem of selecting.range) {
if (is_select) document.getElementById(elem.htmlFor).checked = selecting.className == "selecting"
elem.className = ""
}
selecting = null
})
months.addEventListener('click', event => {
if (is_select) {
event.preventDefault()
is_select = false
}
})
})()
</script>
<script>(function() { <?= file_get_contents('./script.js') ?> })()</script>
<h3>Tag X</h3> <h3>Tag X</h3>
<label><input type=checkbox name=tagx>Ich möchte im Fall eines Räumungsversuchs mit dem Solibus nach Lützerath fahren.</label> <label><input type=checkbox name=tagx>Ich möchte im Fall eines Räumungsversuchs mit dem Solibus nach Lützerath fahren.</label>
<h2>Kontaktmöglichkeiten</h2> <h2>Kontaktmöglichkeiten</h2>
@ -375,7 +130,7 @@ Alle Angaben sind freiwillig. Sie werden nur gespeichert, um euch zu kontaktiere
<label class=row><input name=email type=email placeholder=feldmaus@posteo.de>Deine E-Mail-Adresse</label> <label class=row><input name=email type=email placeholder=feldmaus@posteo.de>Deine E-Mail-Adresse</label>
<h2>Allgemeines</h2> <h2>Allgemeines</h2>
<label class=row><input name=count type=number value=1>Wie viele seid ihr?</label> <label class=row><input name=count type=number value=1>Wie viele seid ihr?</label>
<label class=row><select name=city><option>Berlin</option><option>Potsdam</option><option>Magdeburg<option>Braunschweig<option>Hannover<option>Leipzig</select>Deine Stadt</label>
<label class=row><select name=city><?php foreach($CITIES as $city) { echo "<option>$city"; } ?></select>Deine Stadt</label>
<p class=row><input type=submit value=Abschicken> <p class=row><input type=submit value=Abschicken>
</form> </form>
</main> </main>

84
script.js

@ -0,0 +1,84 @@
"use strict"
const months = document.getElementById('months')
let selecting = null // { origin, className, range }
let is_select = false
months.addEventListener("pointerdown", event => {
selecting = null
is_select = false
for (const elem of document.querySelectorAll(".selecting, .unselecting")) {
elem.className = ""
}
if (event.buttons !== 1) return
let target = event.target
if (target.nodeType == 3) target = target.parentNode
if (target.tagName !== "LABEL") return
event.preventDefault()
const className = target.className = document.getElementById(target.htmlFor).checked ? "unselecting" : "selecting"
selecting = {
origin: target,
className,
range: [ target ]
}
})
months.addEventListener('pointermove', event => {
if (selecting === null) return
event.preventDefault()
is_select = true
const range = document.createRange()
range.selectNode(selecting.origin)
if (range.comparePoint(event.target, 0) > 0) range.setEndAfter(event.target)
else range.setStartBefore(event.target)
const treeWalker = document.createTreeWalker(
range.commonAncestorContainer,
NodeFilter.SHOW_ELEMENT,
{ acceptNode: (node) => node.nodeName.toLowerCase() === 'label' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }
)
const new_range = []
let currentNode
while (currentNode = treeWalker.nextNode()) {
if (range.intersectsNode(currentNode)) {
new_range.push(currentNode)
const idx = selecting.range.indexOf(currentNode)
if (idx !== -1) selecting.range.splice(idx, 1)
else currentNode.className = selecting.className
}
}
for (const elem of selecting.range) {
elem.className = ""
}
selecting.range = new_range
})
document.addEventListener('pointercancel', event => {
if (selecting === null) return
for (const elem of selecting.range) {
elem.className = ""
}
selecting = null
is_select = false
})
document.addEventListener('pointerup', event => {
if (selecting === null) return
event.preventDefault()
for (const elem of selecting.range) {
if (is_select) document.getElementById(elem.htmlFor).checked = selecting.className == "selecting"
elem.className = ""
}
selecting = null
})
months.addEventListener('click', event => {
if (is_select) {
event.preventDefault()
is_select = false
}
})

170
style.css

@ -0,0 +1,170 @@
/* LAYOUT */
body {
margin: 0
}
footer {
border-top: 0.3em dashed #722CA2;
padding: 1em 1em 0 1em;
background: #f1cc37;
color: #5A4B0B;
padding: 1em 0
}
main {
background: #FFE98E;
padding: 1em 0
}
header {
border-bottom: 0.3em dashed #722CA2;
background: #f1cc37;
color: #5A4B0B;
padding: 1em 0
}
h1 {
margin: 0
}
:where(footer, header, main) > * {
margin: 0 auto;
max-width: 80rem;
padding: 0 1rem
}
/* CALENDAR */
.month {
display: inline-block;
vertical-align: top;
margin-right: 2em;
border-spacing: 0
}
.month tbody {
border-radius: 0.3em;
color: #5A4B0B;
box-shadow: .4em .5em 0 0 RGBA(114, 44, 162,0.5)
}
.month th:first-of-type {
border-top-left-radius: 0.3em
}
.month th:last-of-type {
border-top-right-radius: 0.3em
}
.month tr > :last-of-type {
border-right-width: 1px
}
.month tr:last-of-type td:first-of-type {
border-bottom-left-radius: 0.3em
}
.month tr:last-of-type td:last-of-type {
border-bottom-right-radius: 0.3em
}
.month tr:last-of-type td {
border-bottom-width: 1px
}
/* date in calendar */
.month :where(th, td) {
width: 3.5em;
height: 3.5em;
border: 1px solid #CBC19A;
border-width: 1px 0 0 1px;
text-align: center;
padding: 0;
background: white;
box-sizing: border-box
}
.month input[type=checkbox] {
appearance: none;
position: absolute
}
.month label {
display: block;
line-height: 3.5em
}
.month :focus + label {
border: 1px solid #722CA2;
margin: -1px
}
:checked + label:not(.unselecting),
.selecting {
background: #f1cc37
}
.invalid {
color: #CBC19A
}
/* calendar on smaller screens */
@media only screen and (max-width: 55em) {
.month {
margin-right: 1.5vw
}
.month th, .month td {
width: 6vw
}
}
@media only screen and (max-width: 40em) {
.month {
margin: 0 auto;
display: table
}
.month th, .month td {
width: 3.5em
}
}
/* events in calendar */
.event {
position: relative;
top: -3.5em;
right: -0.9em;
line-height: initial;
display: block
}
.event::before {
content: "★";
position: absolute;
padding: 0.5em;
top: -0.5em;
right: 0.5em
}
.event-content {
visibility: hidden;
position: absolute;
background: white;
top: 0.4em;
left: 1.8em;
padding: 0.4em;
border: 1px solid #CBC19A;
border-radius: 0.2em;
box-shadow: .2em .25em 0 0 RGBA(114, 44, 162,0.5);
z-index: 1
}
.month :where(:focus + label, td:hover, td:active) .event-content {
visibility: initial
}
/* OTHER FORM ELEMENTS */
.row {
display: block;
margin: 1em 0
}
.row > input,
.row > select {
padding: 0.5rem;
margin-right: 1rem;
width: 12rem;
box-sizing: border-box;
border: 1px solid #5A4B0B;
border-radius: .3em;
box-shadow: .4em .5em 0 0 RGBA(114, 44, 162,0.5)
}
.row > input[type="number"] {
margin-left: 8.5rem;
width: 3.5rem
}
.row > input[type="submit"] {
background: #f1cc37
}
.row > input[type="submit"]:active {
background: white
}
.row :focus-visible {
outline: 0.2em #722CA2 solid
}
Loading…
Cancel
Save