From cbcdb7115a145be2bc78e3e016d42d878f653196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiesner=20Andr=C3=A1s?= Date: Thu, 9 Jun 2022 00:31:50 +0200 Subject: [PATCH] =?UTF-8?q?Els=C5=91=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 + .idea/mkkkvv.iml | 8 + .idea/modules.xml | 8 + .idea/php.xml | 6 + .idea/vcs.xml | 6 + control_panel.php | 58 ++++ index.php | 43 ++- interface.php | 703 ++++++++++++++++++++++++++++++++++++++++++++-- js/mkkkvv.js | 227 +++++++++++++-- js/mkkkvv_cp.js | 114 ++++++++ js/o.js | 12 + media/arrow.svg | 78 +++++ media/spinner.svg | 7 + mkkkvv.css | 226 ++++++++++++++- 14 files changed, 1449 insertions(+), 55 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/mkkkvv.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/php.xml create mode 100644 .idea/vcs.xml create mode 100644 control_panel.php create mode 100644 js/mkkkvv_cp.js create mode 100644 media/arrow.svg create mode 100644 media/spinner.svg diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/mkkkvv.iml b/.idea/mkkkvv.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/mkkkvv.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..648bea2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..0e09af4 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/control_panel.php b/control_panel.php new file mode 100644 index 0000000..01ca86f --- /dev/null +++ b/control_panel.php @@ -0,0 +1,58 @@ + + + + + MKK Közivezető-választás vezérlőpult + + + + + + +
+

Közivezető-választás vezérlőpult

+
+
+
+
Belépés
+
+
+
+
+ Betöltés... +
+
+

Szavazás-vezérlés

+
Szavazás megnyitása
+
Szavazás lezárása
+
+
Voksolás + engedélyezése +
+
Voksolás + tiltása +
+
+
+
+ Tudom, a + nyilvánosságra hozott infók nem igazán vonhatók már vissza...
+
+ Összesítés és nyilvánosságra hozás +
+
+
Nyilvánosságra hozás + visszavonása +
+
+
+
Zárt összesítés és megjelenítés
+
+
+ + + \ No newline at end of file diff --git a/index.php b/index.php index b4b845d..32e0b54 100644 --- a/index.php +++ b/index.php @@ -1,11 +1,19 @@ + + MKK Közivezető-választás @@ -13,13 +21,36 @@ -
- -
-
+
+

Közivezető-választás

+
+
+ >
+
Belépés
-
+
+

+
+
+ +
Mehet!
+
+
+
+
+
+

Köszönjük, te vagy a...

+
42.
+

...szavazó!

+

Feltétlen nézz vissza később az eredményekért!

+
+
+ Betöltés... +
+ diff --git a/interface.php b/interface.php index 2712f21..ffe1c9f 100644 --- a/interface.php +++ b/interface.php @@ -34,7 +34,8 @@ define("MINIMUM_NEEDLE_LENGTH", 3); define("HIDE_GROUP_OF_NON_DUPLICATES", true); // keresés a nevek között -function search_in_names(array $records, string $needle, string $group = ""): array +function search_in_names(array $records, string $needle, string $group = "", bool $show_only_exact_match = false, + bool $hide_groups_of_non_duplicates = HIDE_GROUP_OF_NON_DUPLICATES): array { // keresés $needle = trim($needle); @@ -46,13 +47,18 @@ function search_in_names(array $records, string $needle, string $group = ""): ar $exact_match = []; $hitpos_array = []; - $filter = function (array $var) use (&$hitpos_array, &$exact_match, $needle, $group): bool { + $filter = function (array $var) use (&$hitpos_array, &$exact_match, $needle, $group, $show_only_exact_match): bool { // ha a csoporton belül keres - if ($group !== "" && $var["group"] !== $group) { + if ($group !== "" && strtolower($var["group"]) !== strtolower($group)) { return false; } - $hitpos = stripos($var["name"], $needle); + $hitpos = iconv_strpos(strtolower($var["name"]), strtolower($needle)); + + // pontos egyezés vizsgálata (hossz és kezdőindex vizsgálata) + if ($show_only_exact_match && ($hitpos != 0 || strlen($var["name"]) !== strlen($needle))) { + $hitpos = false; + } // ha van egyezés... if ($hitpos !== false) { @@ -75,7 +81,7 @@ function search_in_names(array $records, string $needle, string $group = ""): ar } } - if (HIDE_GROUP_OF_NON_DUPLICATES) { + if ($hide_groups_of_non_duplicates) { // nevek rendezése hossz szerint (substr gyorsítása n*(n-1)-ről (n-1)+(n-2)+(n-3)... vizsgálatra) usort($filter_res, fn($a, $b) => strlen($a["name"]) - strlen($b["name"])); @@ -113,30 +119,695 @@ function search_in_names(array $records, string $needle, string $group = ""): ar return $filter_res; } +define("DATA_DIR", "data"); +define("LOGIN_ID_FILE_NAME", "electors_masked.md"); +define("VOTE_FILENAME", "votes.dat"); +define("VOTE_COUNT_FILENAME", "vote_count.dat"); +define("USED_ELECTOR_HASH_FILENAME", "electors_submitted_vote.dat"); +define("VOTE_STATE_FILENAME", "vote_state.json"); +define("VOTE_TIMESTAMP_FILENAME", "vote_timestamps.dat"); + +function submit_name(string $name, string $group, string $elector_id) +{ + // default... + $ret = [ + "successful" => false, + "index" => -1 + ]; + + // --------------- + + $used_hash_filename = DATA_DIR . DIRECTORY_SEPARATOR . USED_ELECTOR_HASH_FILENAME; + $votes_filename = DATA_DIR . DIRECTORY_SEPARATOR . VOTE_FILENAME; + $vote_count_filename = DATA_DIR . DIRECTORY_SEPARATOR . VOTE_COUNT_FILENAME; + $timestamp_filename = DATA_DIR . DIRECTORY_SEPARATOR . VOTE_TIMESTAMP_FILENAME; + + // --------------- + + // kölcsönös kizárás a számláló fájllal + $fp = fopen($vote_count_filename, "r+"); + flock($fp, LOCK_EX); // kölcsönös kizárás + + // megvizsgáljuk, hogy szavazhat-e a felhasználó + $used_elector_hashes = file_get_contents($used_hash_filename); + $elector_hash = sha1($elector_id); + if (strpos($used_elector_hashes, sha1($elector_id)) === false) { // ha szavazhat... + // személy kikeresése + $p = read_electable_people(ELECTABLE_PEOPLE_DATABASE); + $hits = search_in_names($p, $name, $group, true, false); + + // ha egzakt találat van, azaz a szavazó megfelelő nevet adott meg + if (count($hits) === 1) { + // felhasználó azonosítójának beírása a felhasznált hash-ek jegyzékébe + copy($used_hash_filename, "$used_hash_filename.prev"); + $hash_save_successful = file_put_contents($used_hash_filename, "$elector_hash\n", FILE_APPEND) !== false; + + // szavazat mentése + $name = $hits[0]["name"]; + $group = $hits[0]["group"]; + $record = "$name//$group\n"; + copy($votes_filename, "$votes_filename.prev"); + $vote_save_successful = file_put_contents($votes_filename, $record, FILE_APPEND) !== false; + + // időbélyeg mentése + copy($timestamp_filename, "$timestamp_filename.prev"); + $timestamp = date("Y-m-d H:i:s"); + $timestamping_successful = file_put_contents($timestamp_filename, "$timestamp\n", FILE_APPEND) !== false; + + $success = $hash_save_successful && $vote_save_successful && $timestamping_successful; + + if ($success) { // ha sikeres minden... + // szavazatszám növelése + $cnt = (int)(fread($fp, 255)); + $cnt++; + $ret["index"] = $cnt; + fseek($fp, 0); + fwrite($fp, $cnt); + } else { // ha valamelyik nem sikerül, akkor visszaállunk a korábbi állapotba + rename($used_hash_filename, "$used_hash_filename.err_" . time()); // hibás fájl eltárolása + rename($votes_filename, "$votes_filename.err_" . time()); + rename($timestamp_filename, "$timestamp_filename.err_" . time()); + copy("$used_hash_filename.prev", $used_hash_filename); // régi állapot visszaállítása + copy("$votes_filename.prev", $votes_filename); + copy("$timestamp_filename.prev", $timestamp_filename); + } + + $ret["successful"] = $success; + } + } + + fclose($fp); // lock elengedése és fájl bezárása + + return $ret; +} + +// név és csoport szétválasztása (NEM UGYANAZ, MINT JS-ben!) +function fetch_name_and_group(string $compound): array +{ + $parts = explode("//", $compound); + return ["name" => $parts[0], "group" => $parts[1]]; +} + +// állapotfájl előállítása, ha nem létezik +function load_state() +{ + $vote_state_fname = DATA_DIR . DIRECTORY_SEPARATOR . VOTE_STATE_FILENAME; + $state = []; + if (!file_exists($vote_state_fname)) { + $state = [ + "state" => "closed", + "open_for_submit" => false, + "auto_manage" => false, + "open_date" => "2022-08-01 08:00:00", + "close_date" => "2022-08-01 20:00:00", + "results_public" => false, + "public_results_plot_filename" => "", + "public_results_plot_filename_mobile" => "" + ]; + } else { + $state = json_decode(file_get_contents($vote_state_fname), true); + } + + return $state; +} + +function save_state($state) +{ + file_put_contents(DATA_DIR . DIRECTORY_SEPARATOR . VOTE_STATE_FILENAME, json_encode($state, JSON_PRETTY_PRINT)); +} + +// jelentés generálása +function generate_report($omit_votes = true) +{ + $state = load_state(); + $elector_hashes = explode("\n", file_get_contents(DATA_DIR . DIRECTORY_SEPARATOR . LOGIN_ID_FILE_NAME)); + $total_elector_count = 0; + foreach ($elector_hashes as $elector_hash) { + if (trim($elector_hash) !== "") { + $total_elector_count++; + } + } + + $vote_list_str = file_get_contents(DATA_DIR . DIRECTORY_SEPARATOR . VOTE_FILENAME); + $vote_list = explode("\n", $vote_list_str); + $votes_grouped = []; + $total_votes = 0; + foreach ($vote_list as $vote) { + // üres sorok kihagyása + if (strlen(trim($vote)) === 0) { + continue; + } + + $votes_grouped[$vote] = (($votes_grouped[$vote]) ?? 0) + 1; + $total_votes++; + } + + // rendezés szavazatok száma alapján + uasort($votes_grouped, fn($a, $b) => ($b - $a)); + + // név és csoport szétválasztása + $votes_structured = []; + foreach ($votes_grouped as $id_compound => $vote_cnt) { + $expl_vote = fetch_name_and_group($id_compound); + $votes_structured[$expl_vote["name"]] = ["name" => $expl_vote["name"], "group" => $expl_vote["group"], "count" => $vote_cnt, "ratio" => ($vote_cnt / $total_votes)]; + } + + $report = [ + "total_votes" => $total_votes, + "total_electors" => $total_elector_count, + "state" => $state + ]; + + if (!$omit_votes) { + $report["records"] = array_values($votes_structured); + } + + return $report; +} + +function RGBToHSLV($r, $g, $b, bool $hsv = false) +{ + $r /= 255; + $g /= 255; + $b /= 255; + + $x_max = max($r, $g, $b); + $x_min = min($r, $g, $b); + $c = $x_max - $x_min; + + $l = ($x_max + $x_min) / 2; + + $h = 0; + if ($c === 0) { + $h = 0; + } else if ($x_max === $r) { + $h = 60 * ($g - $b) / $c; + } else if ($x_max === $g) { + $h = 60 * (2 + ($b - $r) / $c); + } else if ($x_max === $b) { + $h = 60 * (4 + ($r - $g) / $c); + } + + if ($hsv) { + $s = (($x_max === 0)) ? 0 : ((float)$c / $x_max); + return [$h, $s, $x_max]; + } else { + $s = (($l === 0) || ($l === 1)) ? 0 : (2 * ($x_max - $l) / (1 - abs(2 * $l - 1))); + return [$h, $s, $l]; + } + +} + +function HSVtoRGB($h, $s, $v) +{ + $c = $v * $s; + $h_ = $h / 60; + $x = $c * (1 - abs(fmod($h_, 2) - 1)); + + $rgb1 = [0, 0, 0]; + if ($h_ >= 0 && $h_ < 1) { + $rgb1 = [$c, $x, 0]; + } else if ($h_ >= 1 && $h_ < 2) { + $rgb1 = [$x, $c, 0]; + } else if ($h_ >= 2 && $h_ < 3) { + $rgb1 = [0, $c, $x]; + } else if ($h_ >= 3 && $h_ < 4) { + $rgb1 = [0, $x, $c]; + } else if ($h_ >= 4 && $h_ < 5) { + $rgb1 = [$x, 0, $c]; + } else if ($h_ >= 5 && $h_ < 6) { + $rgb1 = [$c, 0, $x]; + } + + $m = $v - $c; + $rgb = [$rgb1[0] + $m, $rgb1[1] + $m, $rgb1[2] + $m]; + + return $rgb; +} + +function RGBtoHex($r, $g, $b) +{ + $r_str = dechex($r); + $g_str = dechex($g); + $b_str = dechex($b); + + $r_str = (strlen($r_str) === 1) ? "0$r_str" : $r_str; + $g_str = (strlen($g_str) === 1) ? "0$g_str" : $g_str; + $b_str = (strlen($b_str) === 1) ? "0$b_str" : $b_str; + + return "#$r_str$g_str$b_str"; +} + +//function RGBtoHCL($r, $g, $b) +//{ +// +//} + +define("SVG_REPORT_WIDTH", 700); +define("SVG_REPORT_HEIGHT", 350); +define("SVG_REPORT_STROKE_WIDTH", 30); +define("SVG_REPORT_LEGEND_MARK_SIZE", 20); +define("SVG_REPORT_HIGHLIGHT_STROKE_WIDTH", 40); +define("SVG_REPORT_PLOT_THRESHOLD_PERCENT", 5); +define("SVG_REPORT_LEGEND_FONT_SIZE", 20); +define("SVG_REPORT_START_COLOR", "003F5C"); +define("SVG_REPORT_END_COLOR", "F89A17"); +define("SVG_REPORT_PIE_RADIUS", 110); +define("SVG_REPORT_ANIMATION_DUR", 2); + +// SVG-kép generálása a jelentésből +function generate_svg_plot($report, $mobile = false): string +{ + $pie_orig = [SVG_REPORT_WIDTH / 2, SVG_REPORT_HEIGHT / 2]; + $pie_radius = SVG_REPORT_PIE_RADIUS; + $cum_percent = 0; + $legend_font_size = SVG_REPORT_LEGEND_FONT_SIZE; + + $last_segment_midpoint = [0, 0]; + $last_segment_midpoint_angle = 0; + + // százalékértéknyi körív rajzolása + $svg_curve = function ($percent) use (&$cum_percent, $pie_radius, $pie_orig, &$last_segment_midpoint, &$last_segment_midpoint_angle) { + $x_rot = $cum_percent / 100.0 * 360; // x-tengely forgatása + + $start_angle = -$cum_percent / 100.0 * 2 * M_PI; // radián + $end_angle = -($cum_percent + $percent) / 100.0 * 2 * M_PI; // radián + $middle_point_angle = -($cum_percent + $percent / 2); // fok + + $start_x = $pie_orig[0] + cos($start_angle) * $pie_radius; + $start_y = $pie_orig[1] + sin($start_angle) * $pie_radius; + + $end_x = $pie_orig[0] + cos($end_angle) * $pie_radius; + $end_y = $pie_orig[1] + sin($end_angle) * $pie_radius; + + $last_segment_midpoint[0] = $pie_orig[0] + cos($middle_point_angle / 100.0 * 2 * M_PI) * $pie_radius; + $last_segment_midpoint[1] = $pie_orig[1] + sin($middle_point_angle / 100.0 * 2 * M_PI) * $pie_radius; + $last_segment_midpoint_angle = $middle_point_angle * 3.6; + + $cum_percent += $percent; + return "M $start_x $start_y + A $pie_radius $pie_radius $x_rot 0 0 $end_x $end_y\n"; + }; + + // kurzor mozgatása + $svg_move_cursor = function ($x, $y) { + return "M $x $y\n"; + }; + + // útvonal generálása + $svg_path = function ($d, $color, $id, $class = "sector", $stroke_width = SVG_REPORT_STROKE_WIDTH) { + return "\n"; + }; + + // jelmagyarázat generálása + $svg_legend = function ($color, $text, $x, $y, $mark_size = SVG_REPORT_LEGEND_MARK_SIZE) use ($legend_font_size, $pie_orig, $mobile) { + $rect_x = $x; + $rect_y = $y; + $circ_radius = $mark_size / 2; + $anchor_cond = ($x > $pie_orig[0] || $mobile); + $text_x_offset_sign = $anchor_cond ? 1 : -1; + $text_anchor = $anchor_cond ? "start" : "end"; + $text_x = $x + $text_x_offset_sign * ($circ_radius + 0.5 * $legend_font_size); + $text_y = $y; // + ($mark_size - 0.4 * $legend_font_size) / 2; + + //return " + return " + $text\n"; + }; + + // színpaletta előállítása + $generate_palette = function ($start_hex, $end_hex, $cnt): array { + if (gettype($start_hex) === "string") { + $start_hex = hexdec($start_hex); + } + if (gettype($end_hex) === "string") { + $end_hex = hexdec($end_hex); + } + + // szétválasztás komponensekre + $start_rgb = [($start_hex & 0xFF0000) >> 16, ($start_hex & 0x00FF00) >> 8, ($start_hex & 0x0000FF)]; + $end_rgb = [($end_hex & 0xFF0000) >> 16, ($end_hex & 0x00FF00) >> 8, ($end_hex & 0x0000FF)]; + + $start_hsv = RGBToHSLV($start_rgb[0], $start_rgb[1], $start_rgb[2], true); + $end_hsv = RGBToHSLV($end_rgb[0], $end_rgb[1], $end_rgb[2], true); + + // mindig a hosszabb görbe választása + $hue_diff = $end_hsv[0] - $start_hsv[0]; + if (abs($hue_diff) < 180) { + $start_hsv[0] -= 360; + } + + // interpoláció + $step = [($end_hsv[0] - $start_hsv[0]) / ($cnt - 1), + ($end_hsv[1] - $start_hsv[1]) / ($cnt - 1), + ($end_hsv[2] - $start_hsv[2]) / ($cnt - 1)]; + + $palette = []; + for ($i = 0; $i < $cnt; $i++) { + $hue = $start_hsv[0] + $step[0] * $i % 360; + $hue += ($hue < 0) ? 360 : 0; + $color = [$hue, $start_hsv[1] + $step[1] * $i, $start_hsv[2] + $step[2] * $i]; + $color_rgb = HSVtoRGB($color[0], $color[1], $color[2]); + //$palette[] = "hsl(" . (floor($color[0])) . "," . (floor($color[1])) . "%," . (floor($color[2])) . "%)"; + //$palette[] = "rgb(" . (round($color_rgb[0] * 255)) . "," . (round($color_rgb[1] * 255)) . "," . (round($color_rgb[2] * 255)) . ")"; + $palette[] = RGBtoHex((round($color_rgb[0] * 255)), (round($color_rgb[1] * 255)), (round($color_rgb[2] * 255))); + } + + return $palette; + }; + + // ------------------- + + // küszöbérték feletti blokkok számának meghatározása + $sector_count = 0; + foreach ($report["records"] as $record) { + $sector_count++; + if ($record["ratio"] * 100.0 < SVG_REPORT_PLOT_THRESHOLD_PERCENT) { + break; + } + } + + // színpaletta előállítása + $palette = $generate_palette(SVG_REPORT_START_COLOR, SVG_REPORT_END_COLOR, $sector_count); + //$palette = [ "#003f5c", "#424d83", "#8e5092", "#d24f81", "#fa6756", "#f89a17" ]; + + // svg-kép előállítása + $svg = ""; + + // stíluslap hozzáadása + $hlsw = SVG_REPORT_HIGHLIGHT_STROKE_WIDTH; + $animdur = SVG_REPORT_ANIMATION_DUR; + $svg .= ""; + + $total = $report["total_votes"]; + $last_legend_anchor = []; + foreach ($report["records"] as $idx => $record) { + // kördiagram cikkei + $percent = $record["count"] * 100.0 / $total; + $joined_sector = $percent < SVG_REPORT_PLOT_THRESHOLD_PERCENT; // az küszöbérték alatti szavaztokat egybe gyűjtjük + if ($joined_sector) { + $percent = 100.0 - $cum_percent; // az összes maradék helyet kitöltjük + } + + //$color = "rgb(" . random_int(0, 255) . "," . random_int(0, 255) . "," . random_int(0, 255) . ")"; + $color = $palette[$idx]; + + $anim_delay = SVG_REPORT_ANIMATION_DUR * 2/3 * ($sector_count - $idx - 1); + $svg .= ""; + $sector_id = "sector$idx"; + $svg .= $svg_path($svg_curve($percent), $color, "$sector_id"); // körcikk + $svg .= "" // szöveg rajta + . number_format((float)$percent, 1, ',', '') . + "%\n"; + + // jelmagyarázat + $legend_anchor = []; + if (!$mobile) { // normál megjelenítés + $legend_anchor = $pie_orig; + $legend_anchor[0] += ($last_segment_midpoint[0] - $pie_orig[0]) * (1 + 1.2 * SVG_REPORT_STROKE_WIDTH / $pie_radius); + $legend_anchor[1] += ($last_segment_midpoint[1] - $pie_orig[1]) * (1 + 1.2 * SVG_REPORT_STROKE_WIDTH / $pie_radius); + } else { // mobilos megjelenítés + $legend_anchor[0] = $pie_orig[0] - SVG_REPORT_PIE_RADIUS; + $legend_anchor[1] = $pie_orig[1] + SVG_REPORT_PIE_RADIUS + SVG_REPORT_HIGHLIGHT_STROKE_WIDTH * 1.5 + 1.5 * $legend_font_size * $idx; + } + + if (!$joined_sector) { + $legend_text = "${record['name']} (${record['count']})"; + } else { + $legend_text = "Többiek"; + } + $svg .= $svg_legend($color, $legend_text, $legend_anchor[0], $legend_anchor[1]); + $svg .= ""; + + $last_legend_anchor = $legend_anchor; + + if ($joined_sector) { + break; + } + } + + // viewBox beállítása + $viewBox = []; + if (!$mobile) { // asztali kép + $viewBox = "0 0 " . SVG_REPORT_WIDTH . " " . SVG_REPORT_HEIGHT; + $style = "style='width: " . SVG_REPORT_WIDTH . "px;'"; + } else { // mobilos kép + $vBx = SVG_REPORT_WIDTH / 2 - (SVG_REPORT_PIE_RADIUS + SVG_REPORT_HIGHLIGHT_STROKE_WIDTH + 1); + $vBy = SVG_REPORT_HEIGHT / 2 - (SVG_REPORT_PIE_RADIUS + SVG_REPORT_HIGHLIGHT_STROKE_WIDTH + 1); + $vBw = (SVG_REPORT_PIE_RADIUS + SVG_REPORT_HIGHLIGHT_STROKE_WIDTH) * 2; + $vBh = $last_legend_anchor[1] + 2 * $legend_font_size; + $viewBox = "$vBx $vBy $vBw $vBh"; + $style = ""; + } + + $svg = "" . $svg; + + + $svg .= ""; + + return $svg; +} + +// azonosító ellenőrzése +function verify_id($id) +{ + if (trim($id) === "") { + return ["valid" => false, "used" => false]; + } + $ids = file_get_contents(DATA_DIR . DIRECTORY_SEPARATOR . LOGIN_ID_FILE_NAME); + $used_hashes = file_get_contents(DATA_DIR . DIRECTORY_SEPARATOR . USED_ELECTOR_HASH_FILENAME); + $needle = sha1($id) . "\n"; // teljes sort kersünk, mert különben elég lenne egy részét ismerni az adatnak + $id_valid = strpos($ids, $needle) !== false; + $id_used = strpos($used_hashes, $needle) !== false; + return ["valid" => $id_valid, "used" => $id_used]; +} + // -------------------------------- -define("ELECTABLE_PEOPLE_DATABASE", "data/electable_people_test.md"); +define("CP_LOGIN_ID_FILENAME", "cp_login_hashes.dat"); + +// Vezérlőpult kezelése + +// bejelentkezés a vezérlőpultba +function cp_verify_id($id) +{ + if (trim($id) === "") { + return false; + } + $ids = file_get_contents(DATA_DIR . DIRECTORY_SEPARATOR . CP_LOGIN_ID_FILENAME); + return strpos($ids, sha1($id) . "\n") !== false; +} + +define("STATE_LOCK_FILENAME", "state.lock"); +define("PUBLIC_DIR", "public"); +define("FULL_RESULT_RECORDS_FILENAME", "full_results.json"); + +// szavazásállapot módosítása +function cp_modify_state($command) +{ + $fp = fopen(DATA_DIR . DIRECTORY_SEPARATOR . STATE_LOCK_FILENAME, "w"); + flock($fp, LOCK_EX); + + $state = load_state(); + switch ($command) { + case "open_election": + $state["state"] = "open"; + break; + case "close_election": + $state["state"] = "closed"; + break; + case "enable_submitting": + $state["open_for_submit"] = true; + break; + case "disable_submitting": + $state["open_for_submit"] = false; + break; + case "publish_results": + { + // grafikon rajzolása + $state["results_public"] = true; + $uniqid = uniqid(); + $plot_filename_normal = PUBLIC_DIR . DIRECTORY_SEPARATOR . "results_$uniqid.svg"; + $plot_filename_mobile = PUBLIC_DIR . DIRECTORY_SEPARATOR . "m_results_$uniqid.svg"; + $report = generate_report(false); + $svg_normal = generate_svg_plot($report); + file_put_contents($plot_filename_normal, $svg_normal); + $svg_mobile = generate_svg_plot($report, true); + file_put_contents($plot_filename_mobile, $svg_mobile); + $state["public_results_plot_filename"] = $plot_filename_normal; + $state["public_results_plot_filename_mobile"] = $plot_filename_mobile; + + // szavazat-eredmények mentése + $records_without_group = []; + foreach ($report["records"] as $record) { // csoport törlése (privacy and like that) + $records_without_group[] = [ "name" => $record["name"], "count" => $record["count"], "ratio" => $record["ratio"] ]; + } + file_put_contents(PUBLIC_DIR . DIRECTORY_SEPARATOR . FULL_RESULT_RECORDS_FILENAME, json_encode($records_without_group)); + } + break; + case "unpublish_results": + $state["results_public"] = false; + unlink($state["public_results_plot_filename"]); + unlink($state["public_results_plot_filename_mobile"]); + $state["public_results_plot_filename"] = ""; + $state["public_results_plot_filename_mobile"] = ""; + break; + } + save_state($state); + + fclose($fp); + + return $state; +} + + +// -------------------------------- + +define("ELECTABLE_PEOPLE_DATABASE", "data/electable_people.md"); $action = json_decode($_POST["action"]) ?? "none"; +$req_data = json_decode($_POST["data"] ?? "[]", true); $res = ""; +$disable_jsoning = false; +// Belépés nélküli feldolgozás, ha nem sikerül, akkor processed = false; +$processed = true; switch ($action) { - case "search": + case "login": { - $needle = json_decode($_POST["needle"]); - $group = json_decode($_POST["group"]); - $p = read_electable_people(ELECTABLE_PEOPLE_DATABASE); - $hits = search_in_names($p, $needle, $group); - $res = $hits; + $elector_id = $req_data["elector_id"] ?? ""; + $res = verify_id($elector_id); + if ($res["valid"]) { + $state = load_state(); + $res["open"] = $state["state"] === "open"; + if ($res["open"]) { + $res["open_for_submit"] = $state["open_for_submit"]; + $res["results_public"] = $state["results_public"]; + } + } else if(cp_verify_id($elector_id)) { // megnézzük, hogy admin-e, ha igen, akkor a szavazás indítása előtt beléphet + $res["valid"] = true; + $res["used"] = false; + $state = load_state(); + $res["open"] = $state["state"] === "open"; + $res["open_for_submit"] = true; + $res["results_public"] = false; + } } break; - case "none": + case "cp_login": + { + $cp_id = $req_data["cp_id"] ?? ""; + $res = cp_verify_id($cp_id); + } + break; + default: + $processed = false; break; } -echo json_encode($res); +// ha feldolgozta, akkor ugrás a végére (inkább a goto, mint 46 zárójel... :D) +if ($processed) { + goto exit_point; // ---> +} -//$p = read_electable_people("data/electable_people_test.md"); +// Belépéses (azonosítóhoz kötött) feldolgozás +$elector_id = json_decode($_POST["elector_id"] ?? ""); +$state = load_state(); +$admin_login = cp_verify_id($elector_id); +if (((verify_id($elector_id)["valid"]) || $admin_login) && $state["state"] === "open") { + $processed = true; + switch ($action) { + case "search": + { + if (!$state["open_for_submit"] && !$admin_login) { + break; + } -return; \ No newline at end of file + $needle = $req_data["needle"] ?? ""; + $group = $req_data["group"] ?? ""; + $p = read_electable_people(ELECTABLE_PEOPLE_DATABASE); + $hits = search_in_names($p, $needle, $group); + $res = $hits; + } + break; + case "submit": + { + if (!$state["open_for_submit"] && !$admin_login) { + break; + } + + $name = $req_data["needle"] ?? ""; + $group = $req_data["group"] ?? ""; + $res = submit_name($name, $group, $elector_id); + } + break; + case "results": + { + $mobile = $req_data["mobile"] ?? false; + $state = load_state(); + $res = ["available" => $state["results_public"]]; + if ($state["results_public"]) { + $res["plot_url"] = $mobile ? $state["public_results_plot_filename_mobile"] : $state["public_results_plot_filename"]; + $res["details"] = json_decode(file_get_contents(PUBLIC_DIR . DIRECTORY_SEPARATOR . FULL_RESULT_RECORDS_FILENAME), true); + } + } + break; + case "none": + break; + default: + $processed = false; + break; + } +} + +if ($processed) { + goto exit_point; +} + +// Control panel parancsok +$processed = true; +$elector_id = json_decode($_POST["cp_id"] ?? ""); +if (cp_verify_id($elector_id)) { + switch ($action) { + case "cp_short_info": + { + $res = generate_report(true); // az összesített szavazatokat hagyja ki! + } + break; + case "cp_mod_state": + { + $command = $req_data["command"]; + $res = cp_modify_state($command); + } + break; + case "cp_generate_plot": + { + $disable_jsoning = true; + $res = generate_svg_plot(generate_report(false)); + } + break; + default: + $processed = false; + break; + } +} + +exit_point: +echo $disable_jsoning ? $res : json_encode($res); + +//$p = read_electable_people("data/electable_people.md"); + +return; diff --git a/js/mkkkvv.js b/js/mkkkvv.js index c961507..088c8c4 100644 --- a/js/mkkkvv.js +++ b/js/mkkkvv.js @@ -1,31 +1,42 @@ const MINIMUM_NEEDLE_LENGTH = 3; +function fetch_name_and_group(needle) { + let ret = { + needle: "", + group: "" + }; + let group_regex = /(\[[a-záéíóöúü]+\]$)/gi; + let group_extracted = needle.match(group_regex); + if (group_extracted != null) { + ret["group"] = group_extracted[0].substring(1, group_extracted[0].length - 1); + ret["needle"] = needle.substring(0, needle.length - group_extracted[0].length - 1).trim(); + } else { + ret["needle"] = needle; + } + return ret; +} + +let ELECTOR_ID; + // keresés indítása a nevek között -function search_in_names(needle) { +function search_in_names() { let hintbox = document.getElementById("hintbox"); + let needle = document.getElementById("nameinput").value; if (needle.trim().length < MINIMUM_NEEDLE_LENGTH) { hintbox.innerHTML = ""; - hide("hintzone"); + collapse("hintzone"); + collapse("submitbtn"); + stop_submit_caption_generator(); o("nameinput").setAttribute("validity", ""); return; } - let request_data = { - action: "search", - needle: "", - group: "" - }; - // csoport és név szétválasztása - let group_regex = /(\[[a-záéíóöúü]+\]$)/gi; - let group_extracted = needle.match(group_regex); - if (group_extracted != null) { - request_data["group"] = group_extracted[0].substring(1, group_extracted[0].length - 1); - request_data["needle"] = needle.substring(0, needle.length - group_extracted[0].length - 1).trim(); - } else { - request_data["needle"] = needle; - } + let request_data = {}; + request_data["data"] = fetch_name_and_group(needle); + request_data["action"] = "search"; + request_data["elector_id"] = ELECTOR_ID; request(request_data).then(resp => { hintbox.innerHTML = ""; @@ -40,8 +51,10 @@ function search_in_names(needle) { hit.addEventListener("click", () => { let group_id = val["duplicate"] ? ` [${val["group"]}]` : ""; fill_name_input(`${val["name"]}${group_id}`); - hide("hintzone"); + collapse("hintzone"); ninput.setAttribute("validity", "ok"); + expand("submitbtn"); + start_submit_caption_generator(); }); let name_node = document.createElement("span"); @@ -79,7 +92,7 @@ function search_in_names(needle) { }); // doboz megjelenítése, ha szükséges - hits.length > 0 ? show("hintzone") : hide("hintzone"); + hits.length > 0 ? expand("hintzone") : collapse("hintzone"); // szövegmező színezése let validity = ""; @@ -87,9 +100,17 @@ function search_in_names(needle) { if (ninput.value.length > MINIMUM_NEEDLE_LENGTH) { if (hits.length === 0) { validity = "bad"; + collapse("submitbtn"); + stop_submit_caption_generator(); } else if (hits.length === 1 && hits[0]["exact_match"]) { validity = "ok"; - hide("hintzone"); + collapse("hintzone"); + expand("submitbtn"); + start_submit_caption_generator(); + } else { + expand("hintzone"); + collapse("submitbtn"); + stop_submit_caption_generator(); } } ninput.setAttribute("validity", validity); @@ -137,3 +158,171 @@ function print_from_group(group) { function fill_name_input(str) { document.getElementById("nameinput").value = str; } + +const ARROWS = [/*"→",*/ "↠", "↣", "↦", "↬", "⇉", + /*"⇒",*/ "⇛", "⇝"]; + +const SUBMIT_CAPTION_PREFIX = "Mehet! "; + +function generate_new_caption() { + document.getElementById("submitbtn").innerHTML = SUBMIT_CAPTION_PREFIX + + ARROWS[Math.floor(Math.random() * (ARROWS.length))]; +} + +CAPTION_TIMER = -1; + +function start_submit_caption_generator() { + if (CAPTION_TIMER === -1) { + generate_new_caption(); + CAPTION_TIMER = setInterval(() => { + generate_new_caption(); + }, 1000); + } +} + +function stop_submit_caption_generator() { + if (CAPTION_TIMER !== -1) { + clearInterval(CAPTION_TIMER); + CAPTION_TIMER = -1; + } +} + +function submit_name() { + stop_submit_caption_generator(); + let submitbtn = document.getElementById("submitbtn"); + submitbtn.innerText = "Már megy is!..."; + + needle = document.getElementById("nameinput").value; + + // csoport és név szétválasztása + let request_data = {}; + request_data["data"] = fetch_name_and_group(needle); + request_data["action"] = "submit"; + request_data["elector_id"] = ELECTOR_ID; + + request(request_data).then(resp_str => { + let resp = JSON.parse(resp_str); + console.log(resp); + if (resp["successful"]) { + hide("mainbox"); + show("thankbox"); + o("elector_index").innerText = `${resp["index"]}.`; + } else { + submitbtn.innerText = "Hiba, próbáld meg újra!"; + } + }); +} + +function login() { + let id_str = document.getElementById("idinput").value; + + if (id_str.trim().length === 0) { + return; + } + + if (login.login_in_progress === undefined) { + login.login_in_progress = true; + } else if (login.login_in_progress) { + return; + } + + let request_data = { + action: "login", + data: { + elector_id: id_str + } + } + + let loginbtn = document.getElementById("loginbtn"); + loginbtn.innerText = "Türelem..."; + + request(request_data).then(resp_str => { + let resp = JSON.parse(resp_str); + if (resp["valid"]) { + ELECTOR_ID = id_str; // így nem kell süti... + loginbtn.innerText = "Sikeres! :)"; + hide("loginbox"); + if (resp["open"]) { + if (resp["results_public"]) { // eredmények publikálva vannak + show("resultbox"); + load_results(); + } else if (resp["open_for_submit"] && !resp["used"]) { // nem publikusak az eredmények, lehet szavazni és még nem szavazott + show("mainbox"); + } else if (resp["open_for_submit"] && resp["used"]) { // nem publikusak az eredmények, lehet szavazni és de már szavazott + show("infobox"); + o("infoheader").innerText = "Már szavaztál. Az eredmények még nem elérhetők, de mi is kíváncsian várjuk őket! :)"; + } else { // nem publikusak az eredmények, nem lehet szavazni + show("infobox"); + o("infoheader").innerText = "Még vagy már nem lehet szavazatot leadni!"; + } + } else { + show("infobox"); + o("infoheader").innerText = "A szavazófelület még vagy már nem elérhető, nézz vissza később!"; + } + } else { + loginbtn.innerText = "Megpróbálom újra!"; + } + login.login_in_progress = false; + } + ) + ; +} + +function load_results() { + let request_data = { + action: "results", + elector_id: ELECTOR_ID, + data: { + mobile: MOBILE + } + } + + request(request_data).then(resp_str => { + let resbox = document.getElementById("resultbox"); + let resp = JSON.parse(resp_str); + if (resp["available"]) { + let pie_plot_url = resp["plot_url"]; + let details = resp["details"]; + let headline = `

Eredmények

` + resbox.innerHTML = headline + `Betöltés...`; + + // grafikon betöltése + let load_pie_req = new XMLHttpRequest(); + load_pie_req.addEventListener("load", () => { + let str = headline + load_pie_req.response; + str += `

Teljes összesítés:

` + let total_count = 0; + for (let i = 0; i < details.length; i++) { + let rec = details[i]; + total_count += rec["count"]; + let percent = (rec["ratio"] * 100).toFixed(1); + str += `${i+1}. ${rec["name"]} (${percent}%, ${rec["count"]} szavazat)
`; + } + str += "
"; + str += `...összesen ${total_count} szavazat alapján! Gratulálunk! :)
` + resbox.innerHTML = str; + + o("full_records").style.animationDelay = o("datagroup0").style.animationDelay; + }); + load_pie_req.open("GET", pie_plot_url); + load_pie_req.send(); + + } else { + //resbox.innerHTML = `

Még nem elérhetők az eredmények, de mi is kíváncsian várjuk! :)

` + } + }) +} + +// #003f5c +// #444e86 +// #955196 +// #dd5182 +// #ff6e54 +// #ffa600 + +// #003f5c +// #424d83 +// #8e5092 +// #d24f81 +// #fa6756 +// #f89a17 \ No newline at end of file diff --git a/js/mkkkvv_cp.js b/js/mkkkvv_cp.js new file mode 100644 index 0000000..1bb6d34 --- /dev/null +++ b/js/mkkkvv_cp.js @@ -0,0 +1,114 @@ +let CP_ID = ""; + +function cp_login() { + let cp_id = document.getElementById("cpidinput").value.trim(); + if (cp_id.length === 0) { + return; + } + + let request_data = { + action: "cp_login", + data: { + cp_id: cp_id + } + } + request(request_data).then(resp_str => { + let resp = JSON.parse(resp_str); + if (resp) { + CP_ID = cp_id; + cp_report(); // információk letöltése + show("control_panel"); + hide("loginbox"); + } + }); +} + +function cp_report() { + let request_data = { + action: "cp_short_info", + cp_id: CP_ID, + } + request(request_data).then(resp_str => { + // alapinfók kiírása + let report = JSON.parse(resp_str); + let short_info = `

Összefoglaló információk

+

Szavazatok: ${report["total_votes"]}/${report["total_electors"]}

`; + document.getElementById("short_info").innerHTML = short_info; + + // gombok láthatóságának vezérlése + cp_refresh_control_buttons(report["state"]); + }); +} + +function cp_toggle_publish_results(en) { + if (en) { + show("publishbtn"); + } else { + hide("publishbtn"); + } +} + +function cp_refresh_control_buttons(state) { + if (state["open_for_submit"]) { + hide("enablesubmitbtn"); + show("disablesubmitbtn"); + } else { + show("enablesubmitbtn"); + hide("disablesubmitbtn"); + } + + if (state["state"] === "open") { + hide("openelectionbtn"); + show("closeelectionbtn"); + show("submitbox"); + } else if (state["state"] === "closed") { + show("openelectionbtn"); + hide("closeelectionbtn"); + hide("submitbox"); + } + + if (state["results_public"]) { + show("undopublishbtn"); + hide("publishbox"); + } else { + hide("undopublishbtn"); + show("publishbox"); + } + + if (state["results_public"]) { + show("undopublishbtn"); + hide("publishbox"); + } else { + hide("undopublishbtn"); + show("publishbox"); + } +} + +function cp_modify_state(cmd) { + let request_data = { + action: "cp_mod_state", + cp_id: CP_ID, + data: { + command: cmd + } + }; + + request(request_data).then(resp_str => { + let state = JSON.parse(resp_str); + cp_refresh_control_buttons(state); + }); +} + +function cp_internal_totaling(filename) { + let request_data = { + action: "cp_generate_plot", + cp_id: CP_ID + }; + + request(request_data).then(svg => { + let plotTab = window.open("newtab"); + plotTab.addEventListener("load", () => { + plotTab.document.write(svg); + }); + }); +} \ No newline at end of file diff --git a/js/o.js b/js/o.js index eabb5bf..d42695e 100644 --- a/js/o.js +++ b/js/o.js @@ -23,3 +23,15 @@ function toggle_show(obj) { function scroll_enable(on) { document.body.style.overflowY = on ? "scroll" : "hidden"; } + +function collapse(obj) { + o(obj).setAttribute("collapsed", "true"); +} + +function expand(obj) { + o(obj).setAttribute("collapsed", "false"); +} + +function toggle_collapse(obj) { + o(obj).setAttribute("collapsed", o(obj).getAttribute("collapsed") === "false"); +} \ No newline at end of file diff --git a/media/arrow.svg b/media/arrow.svg new file mode 100644 index 0000000..4bd4d16 --- /dev/null +++ b/media/arrow.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/media/spinner.svg b/media/spinner.svg new file mode 100644 index 0000000..efbae43 --- /dev/null +++ b/media/spinner.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/mkkkvv.css b/mkkkvv.css index eb61a47..bca8469 100644 --- a/mkkkvv.css +++ b/mkkkvv.css @@ -1,15 +1,17 @@ @import url('https://fonts.googleapis.com/css2?family=Faustina&display=swap'); -body{ +body { --C_PRIMARY: #f89a17; --C_SEC: #44968e; --C_TER: #2a5999; + --C_TERL: #7ba2d0; --C_OK: #67b60a; --C_TRIADIC1: #85ea11; --C_TRIADIC2: #11ea76; --C_BG1: #c0ccdf; --C_BG2: #e3ecf3; --C_BG3: #e9f3e3; + --C_BG4: #DEE6ED; --C_UNKNOWN: #f84c17; --C_TEXTINPUT_COLOR: #2f4c6e; @@ -18,21 +20,52 @@ body{ --MAINBOX_WIDTH: 30em; } -input[type="text"] { - font-family: 'Faustina', serif; - font-size: 32px; - border-color: #2a5999; - border-width: 0 0 0.1em 0; +h1 { + font-size: 40px; } -input[type="text"]:focus { +body { + display: block; + /*position: fixed;*/ + top: 0; + right: 0; + /*bottom: 0;*/ + left: 0; + margin: 0; +} + +h1, h2, h3, h4 { + padding: 0.1em 1em; +} + +input[type="text"], +input[type="password"] { + font-family: 'Faustina', serif; + font-size: 32px; + border-width: 0 0 0.1em 0; + border-color: #2a5999; + + background: repeating-linear-gradient( + -45deg, + var(--C_BG2), + var(--C_BG2) 3px, + var(--C_BG4) 3px, + var(--C_BG4) 6px + ); +} + +input[type="text"]:focus, +input[type="password"]:focus{ outline: none; + border-color: var(--C_TERL); } input#nameinput { + display: inline-block; width: 100%; color: var(--C_TEXTINPUT_COLOR); transition: color 0.3s ease; + padding: 0 0.5em; } input#nameinput[validity="ok"] { @@ -46,22 +79,47 @@ input#nameinput[validity="bad"] { } section#hintbox { - background-color: var(--C_BG2); - margin: 0.2em 0.5em; + margin: 0.2em 0; color: var(--C_TER); font-size: 24px; padding: 0.5em 0; } +section#hintzone { + background-color: var(--C_BG2); + overflow: hidden; + border-bottom: 8px dashed white; +} + +section#hintzone[collapsed="true"] { + max-height: 0; + transition: max-height 0.3s ease; + border-bottom-width: 0; +} + +section#hintzone[collapsed="false"] { + max-height: 100em; + transition: max-height 0.3s ease; + border-bottom-width: 8px; +} + + span.hit { display: block; position: static; padding: 0.2em 0.5em; cursor: pointer; + text-align: left; + + border-left-width: 0; + transition: border-left-width 0.3s ease; } span.hit:hover { background-color: var(--C_BG3); + border-left: 8px dotted var(--C_BG2); + border-right: 8px dotted var(--C_BG2); + transition: border-left-width 0.3s ease; } span.highlight { @@ -72,20 +130,160 @@ span.highlight { section#mainbox { display: block; - position: absolute; - width: min(var(--MAINBOX_WIDTH), calc(100vw - 1em)); + /*position: absolute;*/ + /*width: min(var(--MAINBOX_WIDTH), calc(100vw - 1em));*/ + width: 100%; + /*top: 3em;*/ + /*left: 50%;*/ + /*transform: translate(-50%, 0);*/ + overflow: hidden; } +div.btn { + /*width: 4em;*/ + display: inline-block; + position: relative; + /*top: 0.555em;*/ + background-color: var(--C_TER); + color: white; + font-size: 28px; + padding: 0 0.2em 0 0.2em; + border-radius: 0.2em; + /*text-indent: -3px;*/ + cursor: pointer; + transition: background-color 0.3s ease; + padding-bottom: 0.2em; + margin: 0.1em 0; +} + +div.btn:hover { + background-color: var(--C_TERL); + transition: background-color 0.3s ease; +} + +div#submitbtn { + /*margin-top: 0.2em;*/ + display: block; + width: 100%; + text-align: center; + border-radius: 0; + padding: 0; + overflow: hidden; + margin-top: 0; +} + +div#submitbtn[collapsed="true"] { + height: 0 !important; + transition: height 0.3s ease; +} + +div#submitbtn[collapsed="false"] { + height: 1.25em; + border-bottom: 8px dashed var(--C_BG2); + padding-bottom: 0.2em; + transition: height 0.3s ease; +} + +input[type="text"]#idinput { + align-content: center; + width: 100%; + margin-bottom: 0.2em; + text-align: center; +} + +section#display { + display: block; + position: relative; + /*width: min(var(--MAINBOX_WIDTH), calc(100vw - 1em));*/ + width: max-content; + border-top: 8px dashed white; + border-bottom: 8px dashed white; + background-color: var(--C_BG2); + margin: 3em auto; + /*top: 3em;*/ + padding: 2em 0; + overflow-x: hidden; + text-align: center; +} + +section#elector_index { + background: var(--C_TER); + display: inline-block; + font-size: 50px; + padding: 0.15em 0.4em 0.3em 0.4em; + border-left: 8px dashed var(--C_BG2); + border-right: 8px dashed var(--C_BG2); + color: white; +} + +section#full_records { + font-size: 20px; + animation: fadeIn 3s forwards; + opacity: 0; +} + +@media only screen and (max-width: 600px) { + section#display { + width: 100vw; + } + h1, h2, h3, h4 { + padding: 0.1em 0.5em; + } +} + +/* ------------------------ */ + *[shown="false"] { visibility: hidden; - display: none; + display: none !important; opacity: 0; - transition: 0.3s ease; + transition: opacity 0.5s ease; width: 0; height: 0; + /*animation: fadeOut 0.3s ease;*/ } *[shown="true"] { opacity: 1; - transition: 0.3s ease; + transition: opacity 0.5s ease; + /*animation: fadeIn 0.3s ease-out;*/ } + +*[collapsed="true"] { + max-height: 0; + transition: max-height 0.3s ease; +} + +/*@keyframes fadeIn { + 0% { + display: none; + opacity: 0; + } + + 1% { + display: unset; + opacity: 0; + } + + 100% { + display: unset; + opacity: 1; + } +} + +@keyframes fadeOut { + 0% { + display: unset; + opacity: 1; + } + + 99% { + display: unset; + opacity: 0; + } + + 100% { + display: none; + opacity: 0; + } +}*/ \ No newline at end of file