You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

386 lines
10 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. <?php
  2. locale_set_default('de_DE');
  3. $config = require_once './config.php';
  4. $START_DATE = $config['start_date'];
  5. $EVENTS = $config['events'];
  6. if ($_SERVER['REQUEST_METHOD'] == "POST") {
  7. $n = 0;
  8. do {
  9. $file_name = $config['data_dir'] . "/" . time();
  10. $file = fopen($file_name, "x");
  11. } while ($file === false && ++$n < 5);
  12. if (!$file || !fwrite($file, var_export($_POST, true))) {
  13. echo "Leider konnte deine Anmeldung nicht gespeichert werden. Bitte melde dich bei luetzi-vernetzung-bb@systemli.org damit wir den Fehler beheben können!";
  14. exit;
  15. }
  16. echo "Danke für deine Anmeldung.";
  17. if ($_POST['email']) {
  18. echo " Wir schreiben dir eine E-Mail falls der Solibus in einem von dir gewählten Zeitraum fährt.";
  19. }
  20. exit;
  21. }
  22. $ONE_DAY = new DateInterval("P1D");
  23. function get_days_of_week() {
  24. global $ONE_DAY;
  25. $res = [];
  26. $date = (new DateTime())->setTimestamp(strtotime("this week"));
  27. for ($n = 0; $n < 7; ++$n) {
  28. $res[] = IntlDateFormatter::formatObject($date, "ccc");
  29. $date->add($ONE_DAY);
  30. }
  31. return $res;
  32. }
  33. $DAYS_OF_WEEK = get_days_of_week();
  34. function print_month($start) {
  35. global $DAYS_OF_WEEK, $ONE_DAY, $EVENTS;
  36. $month_start = (new DateTimeImmutable())->setTimestamp(strtotime("first day of this month", $start->getTimestamp()));
  37. $week_start = (new DateTimeImmutable())->setTimestamp(strtotime("this week", $month_start->getTimestamp()));
  38. $month_end = (new DateTimeImmutable())->setTimestamp(strtotime("+1 month", $month_start->getTimestamp()));
  39. $month_name = IntlDateFormatter::formatObject($month_start, "MMMM yyyy");
  40. ?><table class=month>
  41. <caption><h3><?= $month_name ?></h3></caption>
  42. <tr><?php foreach ($DAYS_OF_WEEK as $day) { echo "<th>$day</th>"; }?></tr>
  43. <?php
  44. $date = DateTime::createFromImmutable($week_start);
  45. while ($date->diff($month_end)->invert == 0) {
  46. echo "<tr>";
  47. for ($n = 0; $n < 7; ++$n) {
  48. $invalid = $start->diff($date)->invert == 1 || $month_end->diff($date)->invert == 0;
  49. echo "<td" . ($invalid ? " class=invalid" : "") . ">";
  50. $label = IntlDateFormatter::formatObject($date, "dd");
  51. if ($invalid) {
  52. echo $label;
  53. } else {
  54. $id = IntlDateFormatter::formatObject($date, "yyyy-MM-dd");
  55. echo "<input type=checkbox name=\"date_$id\" id=\"date_$id\"><label for=\"date_$id\">$label";
  56. if (isset($EVENTS[$id])) {
  57. echo "<span class=event><span class=event-content>".htmlspecialchars($EVENTS[$id])."</span></span>";
  58. }
  59. echo "</label>";
  60. }
  61. echo "</td>";
  62. $date->add($ONE_DAY);
  63. }
  64. }
  65. ?>
  66. </table><?php
  67. }
  68. ?><!DOCTYPE html>
  69. <html lang=de>
  70. <title>Soli-Bus-Fahrten nach Lützerath</title>
  71. <meta name=viewport content="width=device-width, initial-scale=1">
  72. <style>
  73. body {
  74. margin: 0
  75. }
  76. footer {
  77. border-top: 0.3em dashed #722CA2;
  78. padding: 1em 1em 0 1em;
  79. background: #f1cc37;
  80. color: #5A4B0B;
  81. padding: 1em 0;
  82. }
  83. main {
  84. background: #FFE98E;
  85. padding: 1em 0;
  86. }
  87. header {
  88. border-bottom: 0.3em dashed #722CA2;
  89. background: #f1cc37;
  90. color: #5A4B0B;
  91. padding: 1em 0;
  92. }
  93. h1 {
  94. margin: 0
  95. }
  96. footer > *,
  97. header > *,
  98. main > * {
  99. margin: 0 auto;
  100. max-width: 80rem;
  101. padding: 0 1rem;
  102. }
  103. .month {
  104. display: inline-block;
  105. vertical-align: top;
  106. margin-right: 2em;
  107. border-spacing: 0;
  108. }
  109. .month tbody {
  110. border-radius: 0.3em;
  111. color: #5A4B0B;
  112. }
  113. .month tbody,
  114. .row > input,
  115. .row > select {
  116. box-shadow: .4em .5em 0 0 RGBA(114, 44, 162,0.5);
  117. }
  118. .month ::selection {
  119. background: inherit;
  120. }
  121. .month th, .month td {
  122. width: 3.5em;
  123. height: 3.5em;
  124. border: 1px solid #CBC19A;
  125. border-width: 1px 0 0 1px;
  126. text-align: center;
  127. padding: 0;
  128. background: white;
  129. box-sizing: border-box;
  130. }
  131. .month th:first-of-type {
  132. border-top-left-radius: 0.3em;
  133. }
  134. .month th:last-of-type {
  135. border-top-right-radius: 0.3em;
  136. }
  137. .month tr > :last-of-type {
  138. border-right-width: 1px;
  139. }
  140. .month tr:last-of-type td:first-of-type {
  141. border-bottom-left-radius: 0.3em;
  142. }
  143. .month tr:last-of-type td:last-of-type {
  144. border-bottom-right-radius: 0.3em;
  145. }
  146. .month tr:last-of-type td {
  147. border-bottom-width: 1px;
  148. }
  149. .month input[type=checkbox] {
  150. appearance: none;
  151. position: absolute
  152. }
  153. .month label {
  154. display: block;
  155. line-height: 3.5em;
  156. }
  157. .month :focus + label {
  158. border: 1px solid #722CA2;
  159. margin: -1px;
  160. }
  161. :checked + label:not(.unselecting),
  162. .selecting {
  163. background: #f1cc37;
  164. }
  165. .invalid {
  166. color: #CBC19A;
  167. }
  168. @media only screen and (max-width: 55em) {
  169. .month {
  170. margin-right: 1.5vw;
  171. }
  172. .month th, .month td {
  173. width: 6vw;
  174. }
  175. }
  176. @media only screen and (max-width: 40em) {
  177. .month {
  178. margin: 0 auto;
  179. display: table;
  180. }
  181. .month th, .month td {
  182. width: 3.5em;
  183. }
  184. }
  185. .event {
  186. position: relative;
  187. top: -3.5em;
  188. right: -0.9em;
  189. line-height: initial;
  190. display: block;
  191. }
  192. .event::before {
  193. content: "";
  194. position: absolute;
  195. padding: 0.5em;
  196. top: -0.5em;
  197. right: 0.5em;
  198. }
  199. .event-content {
  200. visibility: hidden;
  201. position: absolute;
  202. background: white;
  203. top: 0.4em;
  204. left: 1.8em;
  205. padding: 0.4em;
  206. border: 1px solid #CBC19A;
  207. border-radius: 0.2em;
  208. box-shadow: .2em .25em 0 0 RGBA(114, 44, 162,0.5);
  209. z-index: 1;
  210. }
  211. .month :focus + label .event-content,
  212. .month td:hover .event-content,
  213. .month td:active .event-content {
  214. visibility: initial;
  215. }
  216. .row {
  217. display: block;
  218. margin: 1em 0;
  219. }
  220. .row > input,
  221. .row > select {
  222. padding: 0.5rem;
  223. margin-right: 1rem;
  224. width: 12rem;
  225. box-sizing: border-box;
  226. border: 1px solid #5A4B0B;
  227. border-radius: .3em;
  228. }
  229. .row > input[type="number"] {
  230. margin-left: 8.5rem;
  231. width: 3.5rem;
  232. }
  233. .row > input[type="submit"] {
  234. background: #f1cc37;
  235. }
  236. .row > input[type="submit"]:active {
  237. background: white;
  238. }
  239. .row :focus-visible {
  240. outline: 0.2em #722CA2 solid;
  241. }
  242. </style>
  243. <header>
  244. <h1>Soli-Bus-Fahrten von Berlin nach Lützerath</h1>
  245. </header>
  246. <main>
  247. <form method=post>
  248. <p>
  249. Der <a href="https://www.soli-bus.org/">Soli-Bus</a> fährt mit uns nach Lützerath, wenn genug Leute es wollen. Wir brauchen eure Hilfe, um einschätzen zu können, wann es sich lohnt, eine Busfahrt zu organisieren.
  250. Deine Angaben sind nicht verbindlich! Sie helfen uns nur, zu planen, und euch (falls ihr das wollt) zu kontaktieren.
  251. </p>
  252. <p>
  253. Zur groben Einordnung: Eine Fahrt dauert rund 9 Stunden und kostet pro Person mindestens 20 (an Betriebskosten). Der Bus fährt üblicherweise einige Tage später auch zurück. Wir können euch ggf. in einigen Städten (Potsdam, Magdeburg, Braunschweig und Hannover) auf dem Weg einsammeln oder größere Gruppen sogar vorher aus Leipzig abholen.
  254. </p>
  255. <h2>Termine</h2>
  256. <p>Bitte markier alle Tage im Kalender, an denen du mit dem Soli-Bus nach Lützerath fahren könntest (also nur die Hinfahrt). </p>
  257. <div id=months>
  258. <?php
  259. $start = (new DateTimeImmutable())->setTimestamp(mktime(0, 0, 0, $START_DATE[1], $START_DATE[2], $START_DATE[0]));
  260. print_month($start);
  261. $month_end = (new DateTimeImmutable())->setTimestamp(strtotime("first day of next month", $start->getTimestamp()));
  262. print_month($month_end);
  263. $month_end = (new DateTimeImmutable())->setTimestamp(strtotime("first day of next month", $month_end->getTimestamp()));
  264. print_month($month_end);
  265. ?>
  266. </div>
  267. <script>
  268. (function() {
  269. "use strict"
  270. const months = document.getElementById('months')
  271. let selecting = null // { origin, className, range }
  272. let is_select = false
  273. months.addEventListener("pointerdown", event => {
  274. selecting = null
  275. is_select = false
  276. for (const elem of document.querySelectorAll(".selecting, .unselecting")) {
  277. elem.className = ""
  278. }
  279. if (event.buttons !== 1) return
  280. let target = event.target
  281. if (target.nodeType == 3) target = target.parentNode
  282. if (target.tagName !== "LABEL") return
  283. event.preventDefault()
  284. const className = target.className = document.getElementById(target.htmlFor).checked ? "unselecting" : "selecting"
  285. selecting = {
  286. origin: target,
  287. className,
  288. range: [ target ]
  289. }
  290. })
  291. months.addEventListener('pointermove', event => {
  292. if (selecting === null) return
  293. event.preventDefault()
  294. is_select = true
  295. const range = document.createRange()
  296. range.selectNode(selecting.origin)
  297. if (range.comparePoint(event.target, 0) > 0) range.setEndAfter(event.target)
  298. else range.setStartBefore(event.target)
  299. const treeWalker = document.createTreeWalker(
  300. range.commonAncestorContainer,
  301. NodeFilter.SHOW_ELEMENT,
  302. { acceptNode: (node) => node.nodeName.toLowerCase() === 'label' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }
  303. )
  304. const new_range = []
  305. let currentNode
  306. while (currentNode = treeWalker.nextNode()) {
  307. if (range.intersectsNode(currentNode)) {
  308. new_range.push(currentNode)
  309. const idx = selecting.range.indexOf(currentNode)
  310. if (idx !== -1) selecting.range.splice(idx, 1)
  311. else currentNode.className = selecting.className
  312. }
  313. }
  314. for (const elem of selecting.range) {
  315. elem.className = ""
  316. }
  317. selecting.range = new_range
  318. })
  319. document.addEventListener('pointercancel', event => {
  320. if (selecting === null) return
  321. for (const elem of selecting.range) {
  322. elem.className = ""
  323. }
  324. selecting = null
  325. is_select = false
  326. })
  327. document.addEventListener('pointerup', event => {
  328. if (selecting === null) return
  329. event.preventDefault()
  330. for (const elem of selecting.range) {
  331. if (is_select) document.getElementById(elem.htmlFor).checked = selecting.className == "selecting"
  332. elem.className = ""
  333. }
  334. selecting = null
  335. })
  336. months.addEventListener('click', event => {
  337. if (is_select) {
  338. event.preventDefault()
  339. is_select = false
  340. }
  341. })
  342. })()
  343. </script>
  344. <h3>Tag X</h3>
  345. <label><input type=checkbox name=tagx>Ich möchte im Fall eines Räumungsversuchs mit dem Solibus nach Lützerath fahren.</label>
  346. <h2>Kontaktmöglichkeiten</h2>
  347. <p>
  348. Alle Angaben sind freiwillig. Sie werden nur gespeichert, um euch zu kontaktieren, falls ein Bus im von euch gewählten Zeitraum fährt.
  349. </p>
  350. <label class=row><input name=name placeholder=Feldmaus>Dein Name (gerne ein Waldname oder anderes Pseudonym)</label>
  351. <label class=row><input name=email type=email placeholder=feldmaus@posteo.de>Deine E-Mail-Adresse</label>
  352. <h2>Allgemeines</h2>
  353. <label class=row><input name=count type=number value=1>Wie viele seid ihr?</label>
  354. <label class=row><select name=city><option>Berlin</option><option>Potsdam</option><option>Magdeburg<option>Braunschweig<option>Hannover<option>Leipzig</select>Deine Stadt</label>
  355. <p class=row><input type=submit value=Abschicken>
  356. </form>
  357. </main>
  358. <footer>
  359. <p>
  360. Ein Angebot der Berlin-Lützerath-Vernetzung, erreichbar über luetzi-vernetzung-bb [at] systemli.org.
  361. </p>
  362. </footer>