fkin finally
This commit is contained in:
		
							
								
								
									
										15
									
								
								include/errors.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								include/errors.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					#ifndef ERRORS_HPP
 | 
				
			||||||
 | 
					#define ERRORS_HPP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <crow.h>
 | 
				
			||||||
 | 
					#include "templating.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern Templating templating;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct CustomErrorHandler {
 | 
				
			||||||
 | 
					    struct context {};
 | 
				
			||||||
 | 
					    void before_handle(crow::request& req, crow::response& res, context&) {}
 | 
				
			||||||
 | 
					    void after_handle(crow::request& req, crow::response& res, context&);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@@ -7,12 +7,19 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Templating {
 | 
					class Templating {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    Templating(const std::string& template_dir) : inja_env{template_dir} {}
 | 
					    explicit Templating(const std::string& template_dir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    crow::response render_template(const std::string& template_name, const inja::json& data);
 | 
					    crow::response render_template(const std::string& template_name, const inja::json& data);
 | 
				
			||||||
    crow::response render_template(const std::string& template_name);
 | 
					    crow::response render_template(const std::string& template_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string render_template_string(const std::string& template_name, const inja::json& data);
 | 
				
			||||||
 | 
					    std::string render_template_string(const std::string& template_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    inja::Environment inja_env;
 | 
					    inja::Environment inja_env;
 | 
				
			||||||
 | 
					    std::string template_dir;  // absolute path to templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string preprocess_template(const std::string& template_name);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/errors.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/errors.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					#include "errors.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string render_404_template(const crow::request& req) {
 | 
				
			||||||
 | 
					    return templating.render_template_string("errors/404.html", {{"requested_url", req.url}});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void CustomErrorHandler::after_handle(crow::request& req, crow::response& res, context&) {
 | 
				
			||||||
 | 
					    if (res.code == 404 && res.body.empty()) {
 | 
				
			||||||
 | 
					        res.set_header("Content-Type", "text/html");
 | 
				
			||||||
 | 
					        res.body = render_404_template(req);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
#define CROW_STATIC_DIRECTORY "../static"
 | 
					#define CROW_STATIC_DIRECTORY "../static"
 | 
				
			||||||
#include "templating.hpp"
 | 
					#include "templating.hpp"
 | 
				
			||||||
 | 
					#include "errors.hpp"
 | 
				
			||||||
#include <crow.h>
 | 
					#include <crow.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,7 +8,7 @@ Templating templating{"../templates"};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int main() {
 | 
					int main() {
 | 
				
			||||||
    crow::SimpleApp app;
 | 
					    crow::App<CustomErrorHandler> app;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CROW_ROUTE(app, "/")([]() {
 | 
					    CROW_ROUTE(app, "/")([]() {
 | 
				
			||||||
        return templating.render_template("index.html");
 | 
					        return templating.render_template("index.html");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,62 @@
 | 
				
			|||||||
#include "templating.hpp"
 | 
					#include "templating.hpp"
 | 
				
			||||||
 | 
					#include <filesystem>
 | 
				
			||||||
 | 
					#include <fstream>
 | 
				
			||||||
 | 
					#include <regex>
 | 
				
			||||||
 | 
					#include <stdexcept>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Templating::Templating(const std::string& template_dir)
 | 
				
			||||||
 | 
					    : inja_env(std::filesystem::canonical(template_dir).string()),
 | 
				
			||||||
 | 
					      template_dir(std::filesystem::canonical(template_dir).string())
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    inja_env.set_search_included_templates_in_files(true);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string Templating::preprocess_template(const std::string& template_name) {
 | 
				
			||||||
 | 
					    namespace fs = std::filesystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fs::path abs_template_dir = fs::path(template_dir);
 | 
				
			||||||
 | 
					    fs::path abs_template_file = fs::canonical(abs_template_dir / template_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::ifstream file(abs_template_file);
 | 
				
			||||||
 | 
					    if (!file.is_open()) {
 | 
				
			||||||
 | 
					        throw std::runtime_error("Failed to open template file: " + abs_template_file.string());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::regex extends_regex(R"(\{\%\s*extends\s*(['"])(.+?)\1\s*\%\})");
 | 
				
			||||||
 | 
					    std::smatch match;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (std::regex_search(content, match, extends_regex)) {
 | 
				
			||||||
 | 
					        std::string quote = match[1].str();
 | 
				
			||||||
 | 
					        std::string original_path = match[2].str();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (original_path.find("/") == std::string::npos &&
 | 
				
			||||||
 | 
					            !original_path.empty() &&
 | 
				
			||||||
 | 
					            original_path.front() != '/') {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fs::path abs_extended_template = fs::canonical(abs_template_dir / original_path);
 | 
				
			||||||
 | 
					            fs::path rel_path = fs::relative(abs_extended_template, abs_template_file.parent_path());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            std::string new_path = rel_path.generic_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            content = std::regex_replace(content, extends_regex,
 | 
				
			||||||
 | 
					                "{% extends " + quote + new_path + quote + " %}");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return content;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
crow::response Templating::render_template(const std::string& template_name, const inja::json& data) {
 | 
					crow::response Templating::render_template(const std::string& template_name, const inja::json& data) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        inja::Template template_obj = inja_env.parse_template(template_name);
 | 
					        std::string preprocessed = preprocess_template(template_name);
 | 
				
			||||||
        std::string rendered = inja_env.render(template_obj, data);
 | 
					        inja::Template tpl = inja_env.parse(preprocessed);
 | 
				
			||||||
 | 
					        std::string rendered = inja_env.render(tpl, data);
 | 
				
			||||||
        return crow::response(rendered);
 | 
					        return crow::response(rendered);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    } catch (const inja::RenderError& e) {
 | 
					    } catch (const std::exception& e) {
 | 
				
			||||||
        return crow::response(500, e.what());
 | 
					        return crow::response(500, e.what());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -14,3 +64,13 @@ crow::response Templating::render_template(const std::string& template_name, con
 | 
				
			|||||||
crow::response Templating::render_template(const std::string& template_name) {
 | 
					crow::response Templating::render_template(const std::string& template_name) {
 | 
				
			||||||
    return render_template(template_name, inja::json{});
 | 
					    return render_template(template_name, inja::json{});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string Templating::render_template_string(const std::string& template_name, const inja::json& data) {
 | 
				
			||||||
 | 
					    std::string preprocessed = preprocess_template(template_name);
 | 
				
			||||||
 | 
					    inja::Template tpl = inja_env.parse(preprocessed);
 | 
				
			||||||
 | 
					    return inja_env.render(tpl, data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string Templating::render_template_string(const std::string& template_name) {
 | 
				
			||||||
 | 
					    return render_template_string(template_name, inja::json{});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,155 +0,0 @@
 | 
				
			|||||||
#snakeContainer {
 | 
					 | 
				
			||||||
    padding: 15px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
canvas#snakeCanvas {
 | 
					 | 
				
			||||||
    box-sizing: border-box;
 | 
					 | 
				
			||||||
    border: 2px solid var(--secondary-background-color);
 | 
					 | 
				
			||||||
    border-radius: 10px;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
form {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    width: min-content;
 | 
					 | 
				
			||||||
    gap: 1rem;
 | 
					 | 
				
			||||||
    padding: 15px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
form input[type="text"] {
 | 
					 | 
				
			||||||
    padding: 10px;
 | 
					 | 
				
			||||||
    box-sizing: border-box;
 | 
					 | 
				
			||||||
    border: 1px solid var(--secondary-background-color);
 | 
					 | 
				
			||||||
    border-radius: 6px;
 | 
					 | 
				
			||||||
    background-color: var(--secondary-background-color-but-slightly-transparent);
 | 
					 | 
				
			||||||
    color: var(--text-color);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
form button[type="submit"] {
 | 
					 | 
				
			||||||
    padding: 10px;
 | 
					 | 
				
			||||||
    box-sizing: border-box;
 | 
					 | 
				
			||||||
    border: 1px solid var(--secondary-background-color);
 | 
					 | 
				
			||||||
    border-radius: 6px;
 | 
					 | 
				
			||||||
    background-color: var(--secondary-background-color-but-slightly-transparent);
 | 
					 | 
				
			||||||
    color: var(--text-color);
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    cursor: pointer;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.flex-row {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: row;
 | 
					 | 
				
			||||||
    gap: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.min-width {
 | 
					 | 
				
			||||||
    width: min-content;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.max-width {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#snakeLeaderboardSection {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
    padding: 0;
 | 
					 | 
				
			||||||
    max-height: 272px   ;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#snakeLeaderboard {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    list-style: none;
 | 
					 | 
				
			||||||
    padding: 0;
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    overflow-y: scroll;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#snakeLeaderboard li {
 | 
					 | 
				
			||||||
    padding: 5px 20px;
 | 
					 | 
				
			||||||
    background-color: var(--secondary-background-color-but-slightly-transparent);
 | 
					 | 
				
			||||||
    color: var(--text-color);
 | 
					 | 
				
			||||||
    font-size: 1rem;
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    justify-content: space-between;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#snakeLeaderboard li:nth-child(even) {
 | 
					 | 
				
			||||||
    background-color: var(--secondary-background-color);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dialog {
 | 
					 | 
				
			||||||
    width: 90%;
 | 
					 | 
				
			||||||
    max-width: 500px;
 | 
					 | 
				
			||||||
    padding: 20px;
 | 
					 | 
				
			||||||
    background-color: var(--background-color);
 | 
					 | 
				
			||||||
    color: var(--text-color);
 | 
					 | 
				
			||||||
    border: 2px solid var(--secondary-background-color);
 | 
					 | 
				
			||||||
    border-radius: 8px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dialog button {
 | 
					 | 
				
			||||||
    padding: 10px;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    box-sizing: border-box;
 | 
					 | 
				
			||||||
    border: 2px solid var(--secondary-background-color);
 | 
					 | 
				
			||||||
    border-radius: 6px;
 | 
					 | 
				
			||||||
    background-color: var(--secondary-background-color-but-slightly-transparent);
 | 
					 | 
				
			||||||
    color: var(--text-color);
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    cursor: pointer;
 | 
					 | 
				
			||||||
    margin-top: 10px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dialog h2 {
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    font-size: 1.5rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dialog p {
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    font-size: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen and (max-width: 850px) {
 | 
					 | 
				
			||||||
    .flex-row {
 | 
					 | 
				
			||||||
        flex-direction: column;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    form {
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
        width: 100%;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .min-width {
 | 
					 | 
				
			||||||
        width: 100%;
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        flex-direction: column;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen and (max-width: 650px) {
 | 
					 | 
				
			||||||
    .mobileOnly {
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    .pcOnly {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen and (min-width: 651px) {
 | 
					 | 
				
			||||||
    .mobileOnly {
 | 
					 | 
				
			||||||
        display: none !important;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    .pcOnly {
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,20 +0,0 @@
 | 
				
			|||||||
cap-widget {
 | 
					 | 
				
			||||||
  --cap-background: var(--secondary-background-color-but-slightly-transparent);
 | 
					 | 
				
			||||||
  --cap-border-color: var(--secondary-background-color);
 | 
					 | 
				
			||||||
  --cap-border-radius: 14px;
 | 
					 | 
				
			||||||
  --cap-widget-width: 230px;
 | 
					 | 
				
			||||||
  --cap-widget-padding: 14px;
 | 
					 | 
				
			||||||
  --cap-gap: 15px;
 | 
					 | 
				
			||||||
  --cap-color: var(--text-color);
 | 
					 | 
				
			||||||
  --cap-checkbox-size: 25px;
 | 
					 | 
				
			||||||
  --cap-checkbox-border: 1px solid var(--secondary-background-color);
 | 
					 | 
				
			||||||
  --cap-checkbox-border-radius: 6px;
 | 
					 | 
				
			||||||
  --cap-checkbox-background: none;
 | 
					 | 
				
			||||||
  --cap-checkbox-margin: 2px;
 | 
					 | 
				
			||||||
  --cap-font: "Space Mono", "serif";
 | 
					 | 
				
			||||||
  --cap-spinner-color: var(--primary-color);
 | 
					 | 
				
			||||||
  --cap-spinner-background-color: var(--secondary-background-color-but-slightly-transparent);
 | 
					 | 
				
			||||||
  --cap-spinner-thickness: 5px;
 | 
					 | 
				
			||||||
  --cap-credits-font-size: 12px;
 | 
					 | 
				
			||||||
  --cap-opacity-hover: 0.8;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -37,7 +37,6 @@
 | 
				
			|||||||
                    <li><a href="https://www.youtube.com/@acetheking987">YouTube</a></li>
 | 
					                    <li><a href="https://www.youtube.com/@acetheking987">YouTube</a></li>
 | 
				
			||||||
                    <li><a href="https://acetheking987.tumblr.com/">Tumblr</a></li>
 | 
					                    <li><a href="https://acetheking987.tumblr.com/">Tumblr</a></li>
 | 
				
			||||||
                    <li><a href="https://www.reddit.com/user/acetheking987">Reddit</a></li>
 | 
					                    <li><a href="https://www.reddit.com/user/acetheking987">Reddit</a></li>
 | 
				
			||||||
                    <li><a href="/404">404 >:3</a></li>
 | 
					 | 
				
			||||||
                </ul>
 | 
					                </ul>
 | 
				
			||||||
            </section>
 | 
					            </section>
 | 
				
			||||||
        </nav>
 | 
					        </nav>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
{% extends "bases/base.html" %}
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
 | 
					{% block title %}/{{ directory }} - Alfie's basement{% endblock %}
 | 
				
			||||||
{% block description %}server backend survivor{% endblock %}
 | 
					{% block description %}server backend survivor{% endblock %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,80 +3,18 @@
 | 
				
			|||||||
{% block title %}404 - Not Found{% endblock %}
 | 
					{% block title %}404 - Not Found{% endblock %}
 | 
				
			||||||
{% block description %}The page you are looking for does not exist.{% endblock %}
 | 
					{% block description %}The page you are looking for does not exist.{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block head %}
 | 
					 | 
				
			||||||
<link rel="stylesheet" href="/static/css/404.css">
 | 
					 | 
				
			||||||
<link rel="stylesheet" href="/static/css/cap.css">
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
<section>
 | 
					<section>
 | 
				
			||||||
    <h1>404</h1>
 | 
					    <h1>404</h1>
 | 
				
			||||||
    <p>
 | 
					    <p>
 | 
				
			||||||
        It seems like the thing you are looking for is not here :[
 | 
					        Hey so you know that thing you were looking for? Yeah, it doesn't exist. :P
 | 
				
			||||||
        <br><br>
 | 
					        <br><br>
 | 
				
			||||||
        <span class="pcOnly">
 | 
					        So why not try going back to the <a href="/">homepage</a>?
 | 
				
			||||||
        while you're here, why not play some snake?
 | 
					 | 
				
			||||||
        </span>
 | 
					 | 
				
			||||||
        <span class="mobileOnly">
 | 
					 | 
				
			||||||
        You can't play snake on mobile, sorry :(
 | 
					 | 
				
			||||||
        </span>
 | 
					 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
    <div class="pcOnly" id="snakeContainer">
 | 
					 | 
				
			||||||
        <canvas id="snakeCanvas"></canvas>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
<section class="pcOnly flex-row">
 | 
					<section>
 | 
				
			||||||
    <section class="min-width">
 | 
					    <h2>The actual error for the 2 ppl who care</h2>
 | 
				
			||||||
        <h2>Submit score</h2>
 | 
					    <p>
 | 
				
			||||||
        <form action="/snake/submit" method="POST" id="snakeForm">
 | 
					        404: {{ requested_url }} not found :3
 | 
				
			||||||
            <input type="text" id="name" name="name" maxlength=15 minlength=3 placeholder="Your name" required>
 | 
					    </p>
 | 
				
			||||||
            <cap-widget id="captcha" data-cap-api-endpoint="https://cap.alfieking.dev/{{ cap_key }}/"></cap-widget>
 | 
					 | 
				
			||||||
            <input type="hidden" id="score" name="score" value="0">
 | 
					 | 
				
			||||||
            <input type="hidden" id="game_token" name="game_token" value="{{ token}}">
 | 
					 | 
				
			||||||
            <button type="submit" id="submit">Submit</button>
 | 
					 | 
				
			||||||
        </form>
 | 
					 | 
				
			||||||
    </section>
 | 
					 | 
				
			||||||
    <section class="max-width" id="snakeLeaderboardSection">
 | 
					 | 
				
			||||||
        <h2>Leaderboard</h2>
 | 
					 | 
				
			||||||
        <ul id="snakeLeaderboard">
 | 
					 | 
				
			||||||
            {% for score in scores %}
 | 
					 | 
				
			||||||
            <li>
 | 
					 | 
				
			||||||
                <span>{{ score.position }}</span>
 | 
					 | 
				
			||||||
                <span>{{ score.name }}</span>
 | 
					 | 
				
			||||||
                <span>{{ score.score }}</span>
 | 
					 | 
				
			||||||
            </li>
 | 
					 | 
				
			||||||
            {% endfor %}
 | 
					 | 
				
			||||||
        </ul>
 | 
					 | 
				
			||||||
    </section>
 | 
					 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
<section class="max-width mobileOnly" id="snakeLeaderboardSection">
 | 
					 | 
				
			||||||
    <h2>Leaderboard</h2>
 | 
					 | 
				
			||||||
    <ul id="snakeLeaderboard">
 | 
					 | 
				
			||||||
        {% for score in scores %}
 | 
					 | 
				
			||||||
        <li>
 | 
					 | 
				
			||||||
            <span>{{ score.position }}</span>
 | 
					 | 
				
			||||||
            <span>{{ score.name }}</span>
 | 
					 | 
				
			||||||
            <span>{{ score.score }}</span>
 | 
					 | 
				
			||||||
        </li>
 | 
					 | 
				
			||||||
        {% endfor %}
 | 
					 | 
				
			||||||
    </ul>
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
{% if error %}
 | 
					 | 
				
			||||||
<dialog id="errorDialog">
 | 
					 | 
				
			||||||
    <h2>Error</h2>
 | 
					 | 
				
			||||||
    <p>{{ error }}</p>
 | 
					 | 
				
			||||||
    <button onclick="errorDialog.close()">Close</button>
 | 
					 | 
				
			||||||
</dialog>
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
    const errorDialog = document.getElementById('errorDialog');
 | 
					 | 
				
			||||||
    if (errorDialog) {
 | 
					 | 
				
			||||||
        errorDialog.showModal();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
{% endif %}
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% block scripts %}
 | 
					 | 
				
			||||||
<script src="/static/js/snake.js"></script>
 | 
					 | 
				
			||||||
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user