diff --git a/build/ansible/build.yaml b/build/ansible/build.yaml index 7bc3e69..aaf4835 100644 --- a/build/ansible/build.yaml +++ b/build/ansible/build.yaml @@ -4,7 +4,7 @@ git: repo: "https://github.com/TheCoreMan/make-git-better-2.git" dest: /home/{{ ansible_facts['user_id'] }}/make-git-better-2 - version: dev + version: 26/auto-hof-page accept_hostkey: yes - name: Compile rust @@ -16,6 +16,13 @@ shell: docker build --tag mgb:0.1 --build-arg CACHE_DATE=$(date +%Y-%m-%d:%H:%M:%S) . args: chdir: /home/{{ ansible_facts['user_id'] }}/make-git-better-2 + become: yes + + - name: Docker housekeeping, delete untagged built docker images to save space + shell: docker rmi $(docker images | grep "^" | awk '{ print $3 }') + # Can ignore errors if no such images exist + ignore_errors: true + become: yes - name: Clone docker-tcp-switchboard git: @@ -53,3 +60,25 @@ # when it finishes running. The nohup is there to prevent it, and the redirections # prevent breaking the process. become: yes + + - name: Submit Flags - Install wasm requirements 1 + yum: + name: + - openssl-devel + - pkgconfig + become: yes + + - name: Submit Flags - Install wasm-pack + command: /home/{{ ansible_facts['user_id'] }}/.cargo/bin/cargo install wasm-pack + + - name: Submit Flags - Build wasm + shell: /home/{{ ansible_facts['user_id'] }}/.cargo/bin/wasm-pack build --target web --out-name wasm --out-dir ./static + args: + chdir: /home/{{ ansible_facts['user_id'] }}/make-git-better-2/submit-flags/submit-flags-app + + - name: Submit Flags - Install HTTP server + command: /home/{{ ansible_facts['user_id'] }}/.cargo/bin/cargo install simple-http-server + + - name: Submit Flags - Run HTTP server + shell: nohup /home/{{ ansible_facts['user_id'] }}/.cargo/bin/simple-http-server --index --nocache --port 1337 /home/{{ ansible_facts['user_id'] }}/make-git-better-2/submit-flags/submit-flags-app/static /dev/null 2>&1 & + become: yes diff --git a/levels/game-config.toml b/levels/game-config.toml index 4d013a1..d0ce8a8 100644 --- a/levels/game-config.toml +++ b/levels/game-config.toml @@ -65,7 +65,7 @@ flags = ['log-5'] title = 'log-5' branch = 'pamphletary-harnessing-petticoaterie' solution_checker = 'hooks/checkers/log-5.sh' -flags = [] +flags = ['log-flag-meteorical'] [[levels]] title = 'merge-3' @@ -107,7 +107,7 @@ flags = [ title = 'tag-2' branch = 'individually-nonintroversive-chalcomancy' solution_checker = 'hooks/checkers/tag-2.sh' -flags = [] +flags = ['individually-nonintroversive-chalcomancy'] [[levels]] title = 'hooks-1' diff --git a/submit-flags/submit-flags-app/Cargo.toml b/submit-flags/submit-flags-app/Cargo.toml new file mode 100644 index 0000000..336325c --- /dev/null +++ b/submit-flags/submit-flags-app/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "submit-flags-app" +version = "0.1.0" +authors = ["Shay Nehmad "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +yew = "0.17" +wasm-bindgen = "0.2.67" +log = "0.4.6" +wasm-logger = "0.2.0" +sha2 = "0.9.1" +console_error_panic_hook = "0.1.6" +wasm-bindgen-futures = "0.4.18" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0.32" diff --git a/submit-flags/submit-flags-app/src/components/common.rs b/submit-flags/submit-flags-app/src/components/common.rs new file mode 100644 index 0000000..8df557a --- /dev/null +++ b/submit-flags/submit-flags-app/src/components/common.rs @@ -0,0 +1,30 @@ +use yew::callback::Callback; +use anyhow::Error; +use serde::{Serialize, Deserialize}; +// todo move to same lib as scripts +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LevelInfo { + pub name: String, + pub flag: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LevelsInfo { + pub levels: Vec, +} + +#[derive(Debug)] +pub struct SingleFlagStatus { + pub level_name: String, + pub is_correct: bool +} + +#[derive(Debug)] +pub enum MainPageMsg { + CheckSingleFlag(SingleFlagStatus), + // Fetch-related messages + GetFlagsResponse, + FlagsResponseReady(Result), +} + +pub type CheckFlagCallback = Callback; diff --git a/submit-flags/submit-flags-app/src/components/fetchflags.rs b/submit-flags/submit-flags-app/src/components/fetchflags.rs new file mode 100644 index 0000000..af50425 --- /dev/null +++ b/submit-flags/submit-flags-app/src/components/fetchflags.rs @@ -0,0 +1,58 @@ +use anyhow::{anyhow, Error}; + +use yew::callback::Callback; +use yew::format::{Nothing}; +use yew::services::fetch::{FetchService, FetchTask, Request, Response}; + +use super::common::LevelsInfo; + +#[derive(Default)] +pub struct GetFlagsService { + file_path: &'static str, +} + +impl GetFlagsService { + pub fn new(file_path: &'static str) -> Self { + Self { + file_path, + } + } + + pub fn get_response(&mut self, callback: Callback>) -> FetchTask { + let handler = move |response: Response>| { + let (head, body) = response.into_parts(); + if head.status.is_success() { + log::debug!("Response is a success"); + let body_value = body.unwrap(); + log::debug!("here's the body: {}", body_value); + let parsed = try_to_parse_levels_json(&body_value); + match parsed { + Ok(v) => { + log::debug!("JSON conversion went well! Found {} levels", v.levels.len()); + callback.emit(Ok(v)) + } + Err(e) => { + callback.emit(Err(anyhow!("{:?}", e))); + } + } + } else { + callback.emit(Err(anyhow!( + "{}: error getting levels from server", + head.status + ))) + } + }; + + // Local server + let url = format!("/{}", self.file_path); + + let request = Request::get(url.clone().as_str()).header("Cache-Control", "no-cache").body(Nothing).unwrap(); + log::debug!("Created get request to URI {}", request.uri()); + FetchService::fetch(request, handler.into()).unwrap() + } +} + +fn try_to_parse_levels_json(data: &str) -> Result { + let parsed: LevelsInfo = serde_json::from_str(data)?; + Ok(parsed) +} diff --git a/submit-flags/submit-flags-app/src/components/level.rs b/submit-flags/submit-flags-app/src/components/level.rs new file mode 100644 index 0000000..c96d0c6 --- /dev/null +++ b/submit-flags/submit-flags-app/src/components/level.rs @@ -0,0 +1,176 @@ +use log; + +use sha2::{Sha256, Digest}; + +use yew::prelude::{Component, ComponentLink, Properties, html, Html, ShouldRender}; +use yew::html::InputData; + +use super::common::{SingleFlagStatus, CheckFlagCallback}; + + +pub struct LevelComponent { + // The link enables us to interact (i.e. reqister callbacks and send messages) with the component itself. See https://yew.rs/docs/en/concepts/components/#create + link: ComponentLink, + // The level's name. This is so the user knows which flag belongs where + name: String, + // The flag itself. In the future this will become a hash so that the users can't get the flags using devtools. + flag: String, + // The user's guess for the flag, that they are typing + user_flag: String, + // Whether the correct flag has been entered. + is_flag_correct: bool, + // Callback to update parent that flag has been solved + check_callback: CheckFlagCallback, +} + +// These are the messages (think "events") that can happen in this component. +pub enum LevelMsg { + // This message indicates that it's time to check the user flag to see if it's the correct one. + CheckFlag, + // This message indicates that the user changed the flg they're guessing (when they're typing). Since we need to pass a value, this message has a parameter - see the `view` and `update` methods to see how this is used. + UserFlagChanged(String), +} + +// See https://yew.rs/docs/en/concepts/components/properties/ +// The properties allow enable child and parent components to communicate with each other. +// The parent of a level component is the page itself. +#[derive(Clone, PartialEq, Properties)] +pub struct LevelProps { + // This prop is the level's name. Passed from parent and won't change + pub name: String, + // This prop is the level's flag. Passed from parent and won't change + pub flag: String, + // This prop indicates whether the user's flag is correct. Not passed from parent, but rather used to communicate back to it from the level. + #[prop_or(false)] + pub is_flag_correct: bool, + // Callback to update parent that flag has been solved + pub check_callback: CheckFlagCallback, +} + +// See https://yew.rs/docs/en/concepts/components/ +// `Component` is a Trait (see https://doc.rust-lang.org/book/ch10-02-traits.html), defined here: https://github.com/yewstack/yew/blob/master/yew/src/html/mod.rs#L30 +impl Component for LevelComponent { + // Overriding properties since we have our own. + type Properties = LevelProps; + // Overriding `Message` since we have our own messages. + type Message = LevelMsg; + + // See https://yew.rs/docs/en/concepts/components/#create + fn create(props: Self::Properties, link: ComponentLink) -> Self { + log::debug!("Creating level {} component", props.name.clone()); + Self { + link: link, + // Pass the name from the parent component + name: props.name, + // Pass the flag from the parent component + flag: props.flag, + // The initial user flag is empty + user_flag: "".to_string(), + // This has a default value of `false`. Not passed from parent + is_flag_correct: props.is_flag_correct, + // parent has to pass the callback + check_callback: props.check_callback, + } + } + + // See https://yew.rs/docs/en/concepts/components/#update + fn update(&mut self, msg: Self::Message) -> ShouldRender { + // Do something different depending on the update message. + match msg { + LevelMsg::CheckFlag => { + // Hash the user flag to an array + let hashed_user_flag_arr = Sha256::digest(self.user_flag.as_bytes()); + // Cast the array to a string + let hashed_user_flag_str: String = format!("{:x}", hashed_user_flag_arr); + // Compare user hash to our hash + self.is_flag_correct = hashed_user_flag_str == self.flag; + + // update parent via callback + let status = SingleFlagStatus { level_name: self.name.clone(), is_correct: self.is_flag_correct }; + self.check_callback.emit(status); + + true // Re-render + } + LevelMsg::UserFlagChanged(new_user_flag) => { + log::debug!("update::{}, User flag changed from {} to {}", self.name.clone(), self.user_flag.clone(), new_user_flag.clone()); + self.user_flag = new_user_flag; + self.update(LevelMsg::CheckFlag); + true // Re-render + } + } + } + + // See https://yew.rs/docs/en/concepts/components/#change + // We're not using "change" + fn change(&mut self, _props: Self::Properties) -> ShouldRender{ + log::debug!("Changing level {} component", self.name.clone()); + false + } + + // See https://yew.rs/docs/en/concepts/components/#view + // In this method we're declaring what the element looks like. This is very reminiscent of JSX and React. + fn view(&self) -> Html { + log::debug!("Viewing level {} component", self.name.clone()); + + // TODO - move to "create" + let label_text = self.name.clone() + "'s flag goes here 🚩"; + let input_id = self.name.clone() + "-id"; + + // Creating the element as variables makes it clearer - similar to functional elements in react + + // This element just prints the component info to make it easier to develop. + let _debug_info_element = html! { +
+                { 
+                    format!("DEBUG: I am a level component! Name: {} | Flag: {} | Status: {}", 
+                        self.name.clone(),
+                        self.flag.clone(),
+                        self.is_flag_correct) 
+                }
+                
+
+ }; + + // This element is the input for the flag. + let input_element = html! { +
+ + // Cosmetics + +
+ }; + + // This element is for a11y - don't indicate status with color only, but with an emoji as well. + let status_element = html! { +
 { get_correct_emoji(self.is_flag_correct) }
+ }; + + // This is the complete HTML component we're returning from `view`. + html! { + +
+ { input_element } + { status_element } +
+
+ } + } +} + +fn get_correct_emoji(correct: bool) -> String { + if correct { + return "✔".to_string(); + } else { + return "❌".to_string(); + } +} diff --git a/submit-flags/submit-flags-app/src/components/mainpage.rs b/submit-flags/submit-flags-app/src/components/mainpage.rs new file mode 100644 index 0000000..155d80c --- /dev/null +++ b/submit-flags/submit-flags-app/src/components/mainpage.rs @@ -0,0 +1,198 @@ +use std::collections::HashMap; +use anyhow::Error; +use yew::prelude::{Component, ComponentLink, html, Html, ShouldRender}; +use yew::callback::Callback; +use yew::format::Json; +use yew::services::fetch::FetchTask; + +use super::level::LevelComponent; +use super::common::{LevelInfo, LevelsInfo, MainPageMsg, CheckFlagCallback}; + +use super::fetchflags::GetFlagsService; + +pub struct MainPage { + link: ComponentLink, + + // All the level information - used to create the level components + levels: Option>, + + // The status of each level component (name to is_correct) + levels_status: HashMap, + + // If there's an error in any of the states, shove it here so the user can see it. + error: String, + + // Fetch-related members + flags_service: GetFlagsService, + flags_service_response: Option, + flags_service_callback: Callback>, + flags_service_task: Option, + requested_flags: bool, +} + +impl Component for MainPage { + type Message = MainPageMsg; + type Properties = (); + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Self { + link: link.clone(), + levels: None, + levels_status: HashMap::new(), + error: "".to_string(), + + flags_service: GetFlagsService::new("levels_info.json"), + flags_service_response: None, + flags_service_callback: link.callback(MainPageMsg::FlagsResponseReady), + flags_service_task: None, + requested_flags: false, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + log::debug!("MainPage: Update message {:?}", msg); + match msg { + // Fetch Messages + MainPageMsg::GetFlagsResponse => { + log::debug!("Sending a get response"); + let task = self.flags_service.get_response(self.flags_service_callback.clone()); + log::debug!("Sent a get response"); + self.flags_service_task = Some(task); + self.requested_flags = true; + } + MainPageMsg::FlagsResponseReady(Ok(response)) => { + self.flags_service_response = Some(response); + log::debug!("Got response: {:?}", Json(self.flags_service_response.clone())); + // Finally, get the levels from the response. Phew! + self.levels = Some(self.flags_service_response.as_mut().unwrap().levels.clone()); + for level in self.levels.as_ref().unwrap().iter() { + self.levels_status.insert(level.name.clone(), false); + } + } + MainPageMsg::FlagsResponseReady(Err(err)) => { + log::error!("Error while trying to fetch flags: {:?}", err); + self.error = format!("{:?}", err); + } + // Callback for level component + MainPageMsg::CheckSingleFlag(status) => { + *self.levels_status.get_mut(&status.level_name).unwrap() = status.is_correct; + log::debug!("map {:?}", self.levels_status); + } + } + true + } + + fn change(&mut self, _props: Self::Properties) -> ShouldRender { + // Should only return "true" if new properties are different to + // previously received properties. + // This component has no properties so we will always return "false". + false + } + + fn view(&self) -> Html { + // If you didn't request flags yet, try to. + if !self.requested_flags { + log::debug!("Requesting flags for the first time."); + self.link.send_message(MainPageMsg::GetFlagsResponse); + } + + html! { + <> +
+

{ "Make Git Better CTF - Submit Flags" }

+ { self.get_levels_comp() } + { self.get_totals_comp() } +
+ + } + } +} + +// Extra, non-component methods for MainPage +impl MainPage { + fn get_totals_comp(&self) -> Html { + if self.levels_status.is_empty() { + html! {} + } else { + let mut len = 0; + let mut counter = 0; + for (_, is_correct) in self.levels_status.iter() { + len += 1; + if *is_correct { + counter += 1; + } + }; + + let victory_comp: Html; + + if len == counter { + victory_comp = html! { + <> + // fireworks! +
+
+

{ "You win! 🏆" }

+

+ { "Screenshot this and send it to me to get into the " } + {"make-git-better Hall of Fame"}{"! "} + {"Here's a list of ways to contact me."} +

+

{ "Thanks for playing! 😀" }

+
+ + }; + } else { victory_comp = html! { }; } + + html! { +
+
{ format!("{} / {}", counter, len) }
+ { victory_comp } +
+ } + } + } + + fn get_levels_comp(&self) -> Html { + match &self.levels { + None => { + // Check if still laoding, or an actual error + if self.error.is_empty() { // Still loading + html! { + <> +
+

{ "No levels yet. Loading from server. If this is taking more than a few seconds, check the console logs." }

+ + } + } else { // Error state + html! { + <> +
+

{ format!("An error has occured! Details:") }

+
 { self.error.clone() } 
+

{ "Please reach out to Shay Nehmad (@ShayNehmad on Twitter) with the details!" }

+ + } + } + } + Some(levels) => { + let render_level_component = |level| { + self.create_component_from_level_info(level) + }; + + html! { +
+ { + for levels.iter().map(render_level_component) + } +
+ } + } + } + } + + fn create_component_from_level_info(&self, level_info: &LevelInfo) -> Html { + let callback: CheckFlagCallback = self.link.clone().callback(MainPageMsg::CheckSingleFlag); + html! { + + } + } +} diff --git a/submit-flags/submit-flags-app/src/components/mod.rs b/submit-flags/submit-flags-app/src/components/mod.rs new file mode 100644 index 0000000..eb20dbc --- /dev/null +++ b/submit-flags/submit-flags-app/src/components/mod.rs @@ -0,0 +1,7 @@ +// Public +pub mod mainpage; +pub mod level; + +// Internal +mod common; +mod fetchflags; diff --git a/submit-flags/submit-flags-app/src/lib.rs b/submit-flags/submit-flags-app/src/lib.rs new file mode 100644 index 0000000..57c418d --- /dev/null +++ b/submit-flags/submit-flags-app/src/lib.rs @@ -0,0 +1,21 @@ +// See https://github.com/yewstack/yew/issues/97 +#![recursion_limit="256"] +use yew::App; +use wasm_bindgen::prelude::*; +use console_error_panic_hook; +use std::panic; +use wasm_logger; + +mod components; + +#[wasm_bindgen(start)] +pub fn run_app() { + // Logging to console - see https://yew.rs/docs/en/more/debugging/ + wasm_logger::init(wasm_logger::Config::default()); + + // Stacktrace to console on panics - see https://yew.rs/docs/en/more/debugging/ + panic::set_hook(Box::new(console_error_panic_hook::hook)); + + // Actually start the wasm app + App::::new().mount_to_body(); +} diff --git a/submit-flags/submit-flags-app/static/css/style.css b/submit-flags/submit-flags-app/static/css/style.css new file mode 100644 index 0000000..54725fa --- /dev/null +++ b/submit-flags/submit-flags-app/static/css/style.css @@ -0,0 +1,1863 @@ +@charset "UTF-8";/*!normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css*/ +html { + line-height: 1.15; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +h1 { + font-size: 2em; + margin: .67em 0 +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} + +pre { + font-family: monospace,monospace; + font-size: 1em +} + +a { + background-color: transparent +} + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} + +b,strong { + font-weight: bolder +} + +code,kbd,samp { + font-family: monospace,monospace; + font-size: 1em +} + +small { + font-size: 80% +} + +sub,sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sub { + bottom: -.25em +} + +sup { + top: -.5em +} + +img { + border-style: none +} + +button,input,optgroup,select,textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0 +} + +button,input { + overflow: visible +} + +button,select { + text-transform: none +} + +button,[type=button],[type=reset],[type=submit] { + -webkit-appearance: button +} + +button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner { + border-style: none; + padding: 0 +} + +button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring { + outline: 1px dotted ButtonText +} + +fieldset { + padding: .35em .75em .625em +} + +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal +} + +progress { + vertical-align: baseline +} + +textarea { + overflow: auto +} + +[type=checkbox],[type=radio] { + box-sizing: border-box; + padding: 0 +} + +[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button { + height: auto +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px +} + +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit +} + +details { + display: block +} + +summary { + display: list-item +} + +template { + display: none +} + +[hidden] { + display: none +} + +.chroma { + color: #eee; + background-color: #2c3e50 +} + +.chroma .err { + color: #960050; + background-color: #1e0010 +} + +.chroma .lntd { + vertical-align: top; + padding: 0; + margin: 0; + border: 0 +} + +.chroma .lntable { + border-spacing: 0; + padding: 0; + margin: 0; + border: 0; + width: auto; + overflow: auto; + display: block +} + +.chroma .hl { + display: block; + width: 100%; + background-color: #ffc +} + +.chroma .lnt { + margin-right: .4em; + padding: 0 .4em +} + +.chroma .ln { + margin-right: .4em; + padding: 0 .4em +} + +.chroma .k { + color: #66d9ef +} + +.chroma .kc { + color: #66d9ef +} + +.chroma .kd { + color: #66d9ef +} + +.chroma .kn { + color: #f92672 +} + +.chroma .kp { + color: #66d9ef +} + +.chroma .kr { + color: #66d9ef +} + +.chroma .kt { + color: #66d9ef +} + +.chroma .na { + color: #a6e22e +} + +.chroma .nc { + color: #a6e22e +} + +.chroma .no { + color: #66d9ef +} + +.chroma .nd { + color: #a6e22e +} + +.chroma .ne { + color: #a6e22e +} + +.chroma .nf { + color: #a6e22e +} + +.chroma .nx { + color: #a6e22e +} + +.chroma .nt { + color: #f92672 +} + +.chroma .l { + color: #ae81ff +} + +.chroma .ld { + color: #e6db74 +} + +.chroma .s { + color: #e6db74 +} + +.chroma .sa { + color: #e6db74 +} + +.chroma .sb { + color: #e6db74 +} + +.chroma .sc { + color: #e6db74 +} + +.chroma .dl { + color: #e6db74 +} + +.chroma .sd { + color: #e6db74 +} + +.chroma .s2 { + color: #e6db74 +} + +.chroma .se { + color: #ae81ff +} + +.chroma .sh { + color: #e6db74 +} + +.chroma .si { + color: #e6db74 +} + +.chroma .sx { + color: #e6db74 +} + +.chroma .sr { + color: #e6db74 +} + +.chroma .s1 { + color: #e6db74 +} + +.chroma .ss { + color: #e6db74 +} + +.chroma .m { + color: #ae81ff +} + +.chroma .mb { + color: #ae81ff +} + +.chroma .mf { + color: #ae81ff +} + +.chroma .mh { + color: #ae81ff +} + +.chroma .mi { + color: #ae81ff +} + +.chroma .il { + color: #ae81ff +} + +.chroma .mo { + color: #ae81ff +} + +.chroma .o { + color: #f92672 +} + +.chroma .ow { + color: #f92672 +} + +.chroma .c { + color: #75715e +} + +.chroma .ch { + color: #75715e +} + +.chroma .cm { + color: #75715e +} + +.chroma .c1 { + color: #75715e +} + +.chroma .cs { + color: #75715e +} + +.chroma .cp { + color: #75715e +} + +.chroma .cpf { + color: #75715e +} + +.chroma .gd { + color: #f92672 +} + +.chroma .ge { + font-style: italic +} + +.chroma .gi { + color: #a6e22e +} + +.chroma .gs { + font-weight: 700 +} + +.chroma .gu { + color: #75715e +} + +/*!* animate.css - https://animate.style/ +* Version - 4.1.0 +* Licensed under the MIT license - http://opensource.org/licenses/MIT +* +* Copyright (c) 2020 Animate.css*/ +:root { + --animate-duration:1s;--animate-delay:1s;--animate-repeat:1} + +.animated { + animation-duration: 1s; + animation-duration: var(--animate-duration); + animation-fill-mode: both +} + +.animated.infinite { + animation-iteration-count: infinite +} + +.animated.repeat-1 { + animation-iteration-count: 1; + animation-iteration-count: var(--animate-repeat) +} + +.animated.repeat-2 { + animation-iteration-count: 2; + animation-iteration-count: calc(var(--animate-repeat)*2) +} + +.animated.repeat-3 { + animation-iteration-count: 3; + animation-iteration-count: calc(var(--animate-repeat)*3) +} + +.animated.delay-1s { + animation-delay: 1s; + animation-delay: var(--animate-delay) +} + +.animated.delay-2s { + animation-delay: 2s; + animation-delay: calc(var(--animate-delay)*2) +} + +.animated.delay-3s { + animation-delay: 3s; + animation-delay: calc(var(--animate-delay)*3) +} + +.animated.delay-4s { + animation-delay: 4s; + animation-delay: calc(var(--animate-delay)*4) +} + +.animated.delay-5s { + animation-delay: 5s; + animation-delay: calc(var(--animate-delay)*5) +} + +.animated.faster { + animation-duration: .5s; + animation-duration: calc(var(--animate-duration)/2) +} + +.animated.fast { + animation-duration: .8s; + animation-duration: calc(var(--animate-duration)*0.8) +} + +.animated.slow { + animation-duration: 2s; + animation-duration: calc(var(--animate-duration)*2) +} + +.animated.slower { + animation-duration: 3s; + animation-duration: calc(var(--animate-duration)*3) +} + +@media(prefers-reduced-motion:reduce),print { + .animated { + animation-duration: 1ms!important; + transition-duration: 1ms!important; + animation-iteration-count: 1!important + } + + .animated[class*=Out] { + opacity: 0 + } +} + +@keyframes bounce { + 0%,20%,53%,to { + animation-timing-function: cubic-bezier(0.215,0.61,0.355,1); + transform: translateZ(0) + } + + 40%,43% { + animation-timing-function: cubic-bezier(0.755,0.05,0.855,0.06); + transform: translate3d(0,-30px,0)scaleY(1.1) + } + + 70% { + animation-timing-function: cubic-bezier(0.755,0.05,0.855,0.06); + transform: translate3d(0,-15px,0)scaleY(1.05) + } + + 80% { + transition-timing-function: cubic-bezier(0.215,0.61,0.355,1); + transform: translateZ(0)scaleY(0.95) + } + + 90% { + transform: translate3d(0,-4px,0)scaleY(1.02) + } +} + +.bounce { + animation-name: bounce; + transform-origin: center bottom +} + +@keyframes bounceInRight { + 0%,60%,75%,90%,to { + animation-timing-function: cubic-bezier(0.215,0.61,0.355,1) + } + + 0% { + opacity: 0; + transform: translate3d(3000px,0,0)scaleX(3) + } + + 60% { + opacity: 1; + transform: translate3d(-25px,0,0)scaleX(1) + } + + 75% { + transform: translate3d(10px,0,0)scaleX(0.98) + } + + 90% { + transform: translate3d(-5px,0,0)scaleX(0.995) + } + + to { + transform: translateZ(0) + } +} + +.bounceInRight { + animation-name: bounceInRight +} + +@keyframes bounceOutRight { + 20% { + opacity: 1; + transform: translate3d(-20px,0,0)scaleX(0.9) + } + + to { + opacity: 0; + transform: translate3d(2000px,0,0)scaleX(2) + } +} + +.bounceOutRight { + animation-name: bounceOutRight +} + +@keyframes fadeIn { + 0% { + opacity: 0 + } + + to { + opacity: 1 + } +} + +.fadeIn { + animation-name: fadeIn +} + +@keyframes slideInUp { + 0% { + transform: translate3d(0,100%,0); + visibility: visible + } + + to { + transform: translateZ(0) + } +} + +.slideInUp { + animation-name: slideInUp +} + +@keyframes slideOutDown { + 0% { + transform: translateZ(0) + } + + to { + visibility: hidden; + transform: translate3d(0,100%,0) + } +} + +.slideOutDown { + animation-name: slideOutDown +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; + background: #2c3e50 +} + +::-webkit-scrollbar-thumb { + background: #888 +} + +::-webkit-scrollbar-thumb:hover { + background: #e8eef2 +} + +html { + background: #494f5c; + line-height: 1.6; + letter-spacing: .06em; + scroll-behavior: smooth +} + +body,button,input,select,textarea { + color: #e8eef2; + font-family: trebuchet ms,Verdana,verdana ref,segoe ui,Candara,lucida grande,lucida sans unicode,lucida sans,Tahoma,sans-serif +} + +pre,code,pre tt { + font-family: Consolas,andale mono wt,andale mono,Menlo,Monaco,lucida console,lucida sans typewriter,dejavu sans mono,bitstream vera sans mono,liberation mono,nimbus mono l,courier new,Courier,yahei consolas hybrid,monospace,segoe ui emoji,pingfang sc,microsoft yahei +} + +pre { + padding: .7em 1.1em; + overflow: auto; + font-size: .9em; + line-height: 1.5; + letter-spacing: normal; + white-space: pre; + color: #eee; + background: #2c3e50; + border-radius: 4px +} + +pre code { + padding: 0; + margin: 0; + background: #2c3e50 +} + +code { + color: #eee; + background: #7d828a; + border-radius: 3px; + padding: 0 3px; + margin: 0 4px; + word-wrap: break-word; + letter-spacing: normal +} + +blockquote { + border-left: .25em solid; + margin: 1em; + padding: 0 1em; + font-style: italic +} + +blockquote cite { + font-weight: 700; + font-style: normal +} + +blockquote cite::before { + content: "—— " +} + +a { + color: #e8eef2; + text-decoration: none; + border: none; + transition-property: color; + transition-duration: .4s; + transition-timing-function: ease-out +} + +a:hover { + color: #fff; + text-shadow: 0 0 1px #fff +} + +hr { + opacity: .2; + border-width: 0 0 5px; + border-style: dashed; + background: 0 0; + width: 50%; + margin: 1.8em auto +} + +table { + border-collapse: collapse; + border-spacing: 0; + empty-cells: show; + width: 100%; + max-width: 100% +} + +table th,table td { + padding: 1.5%; + border: 1px solid +} + +table th { + font-weight: 700; + vertical-align: bottom +} + +.section-inner { + margin: 0 auto; + max-width: 1200px; + width: 93% +} + +.thin { + max-width: 720px; + margin: auto +} + +.feather { + display: inline-block; + vertical-align: -.125em; + width: 1em; + height: 1em +} + +.sub-menu { + font-size: .7em +} + +.desktop-only,.desktop-only-ib { + display: none +} + +.highlight { + position: relative +} + +.highlight pre { + padding-right: 75px +} + +.highlight-copy-btn { + position: absolute; + bottom: 7px; + right: 7px; + border: 0; + border-radius: 4px; + padding: 1px; + font-size: .7em; + line-height: 1.8; + color: #fff; + background-color: #777; + opacity: .6; + min-width: 55px; + text-align: center +} + +.highlight-copy-btn:hover { + background-color: #666 +} + +.screen-reader-text { + border: 0; + clip: rect(1px,1px,1px,1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute!important; + width: 1px; + word-wrap: normal!important +} + +.screen-reader-text:focus { + background-color: #f1f1f1; + border-radius: 3px; + box-shadow: 0 0 2px 2px rgba(0,0,0,.6); + clip: auto!important; + clip-path: none; + color: #21759b; + display: block; + font-size: 14px; + font-size: .875rem; + font-weight: 700; + height: auto; + left: 5px; + line-height: normal; + padding: 15px 23px 14px; + text-decoration: none; + top: 5px; + width: auto; + z-index: 100000 +} + +#site-header { + position: fixed; + z-index: 1; + bottom: 0; + width: 100%; + box-sizing: border-box; + box-shadow: -1px -2px 3px rgba(0,0,0,.45); + background-color: #3b3e48; + animation-duration: .3s +} + +.hdr-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding: .5em 0; + font-size: 1.2rem +} + +.hdr-wrapper .site-branding { + display: inline-block; + margin-right: .8em; + font-size: 1.2em +} + +.hdr-wrapper .site-nav { + display: inline-block; + font-size: 1.1em; + opacity: .8 +} + +.hdr-wrapper .site-nav .has-children { + padding-right: .5em; + border-right: 2px solid #7d828a +} + +.hdr-wrapper .site-nav .sub-menu>a { + margin-left: .3em +} + +.hdr-wrapper .site-nav a { + margin-left: .8em +} + +.hdr-icons { + font-size: 1.2em +} + +.hdr-social { + display: inline-block; + margin-left: .6em +} + +.hdr-social>a { + margin-left: .4em +} + +.hdr-btn { + border: none; + background: 0 0; + padding: 0; + margin-left: .4em; + cursor: pointer +} + +#menu-btn { + display: none; + margin-left: .6em; + cursor: pointer +} + +#mobile-menu { + position: fixed; + bottom: 4.8em; + right: 1.5em; + display: none; + padding: .6em 1.8em; + z-index: 1; + box-sizing: border-box; + box-shadow: -1px -2px 3px 0 rgba(0,0,0,.45); + background-color: #3b3e48 +} + +#mobile-menu ul { + list-style: none; + margin: 0; + padding: 0; + line-height: 2; + font-size: 1.2em +} + +#site-footer { + text-align: center; + font-size: .9em; + margin-bottom: 96px; + margin-top: 64px +} + +#site-footer p { + margin: 0 +} + +#spotlight { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; + max-width: 93%; + margin: auto; + font-size: 1.5rem +} + +#spotlight.error-404 { + flex-direction: row; + line-height: normal +} + +p.img-404 { + margin: 0 +} + +p.img-404 svg { + width: 180px; + max-width: 100%; + height: auto +} + +.banner-404 { + margin-left: 2em +} + +.banner-404 h1 { + font-size: 3em; + margin: .5rem 0 +} + +.banner-404 p { + margin-top: 0 +} + +.banner-404 .btn-404 { + font-size: .8em +} + +.banner-404 .btn-404 a { + display: inline-block; + border: 2px solid #e8eef2; + border-radius: 5px; + padding: 5px; + transition-property: color,border-color +} + +.banner-404 .btn-404 a:first-child { + margin-right: 1em +} + +.banner-404 .btn-404 a:hover { + border-color: #fff +} + +.banner-404 .btn-404 a svg { + margin-right: .5em +} + +#home-center { + display: flex; + flex-grow: 1; + flex-direction: column; + justify-content: center +} + +#home-title { + margin: 0; + text-align: center +} + +#home-subtitle { + margin-top: 0; + margin-bottom: 1.5em; + text-align: center; + line-height: normal; + font-size: .7em; + font-style: italic; + opacity: .9 +} + +#home-social { + font-size: 1.4em; + text-align: center; + opacity: .8 +} + +#home-social a { + margin: 0 .2em +} + +#home-nav { + opacity: .8 +} + +#home-nav a { + display: block; + text-align: center; + margin-top: .5em +} + +#home-footer { + text-align: center; + font-size: .6em; + line-height: normal; + opacity: .6 +} + +#home-footer p { + margin-top: 0 +} + +.posts-group { + display: flex; + margin-bottom: 1.9em; + line-height: normal +} + +.posts-group .post-year { + padding-top: 6px; + margin-right: 1.8em; + font-size: 1.6em; + opacity: .6 +} + +.posts-group .post-year:hover { + text-decoration: underline; + cursor: pointer +} + +.posts-group .posts-list { + flex-grow: 1; + margin: 0; + padding: 0; + list-style: none +} + +.posts-group .post-item { + border-bottom: 1px #7d828a dashed +} + +.posts-group .post-item a { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 12px 0 +} + +.posts-group .post-day { + flex-shrink: 0; + margin-left: 1em; + opacity: .6 +} + +.bg-img { + width: 100vw; + height: 100vh; + opacity: .03; + z-index: -1; + position: fixed; + top: 0; + background-attachment: fixed; + background-repeat: no-repeat; + background-size: cover; + background-position: 50%; + transition: opacity .5s +} + +.show-bg-img { + z-index: 100; + opacity: 1; + cursor: pointer +} + +.post-header { + margin-top: 1.2em; + line-height: normal +} + +.post-header .post-meta { + font-size: .9em; + letter-spacing: normal; + opacity: .6 +} + +.post-header h1 { + margin-top: .1em +} + +hr.post-end { + width: 50%; + margin-top: 1.6em; + margin-bottom: .8em; + margin-left: 0; + border-style: solid; + border-bottom-width: 4px +} + +.content a { + word-wrap: break-word; + border: none; + box-shadow: inset 0 -4px 0 #018574; + transition-property: box-shadow; + transition-duration: .1s +} + +.content a:hover { + box-shadow: inset 0 -1em 0 #018574 +} + +.content figure { + max-width: 100%; + height: auto; + margin: 0; + text-align: center +} + +.content figure p { + font-size: .8em; + font-style: italic; + opacity: .6 +} + +.content figure.left { + float: left; + margin-right: 1.5em; + max-width: 50% +} + +.content figure.right { + float: right; + margin-left: 1.5em; + max-width: 50% +} + +.content figure.big { + max-width: 100vw +} + +.content img { + display: block; + max-width: 100%; + height: auto; + margin: auto; + border-radius: 4px +} + +.content ul,.content ol { + padding: 0; + margin-left: 1.8em +} + +.content a.anchor { + float: left; + margin-left: -20px; + padding-right: 6px; + box-shadow: none; + opacity: .8 +} + +.content a.anchor:hover { + background: 0 0; + color: #018574; + opacity: 1 +} + +.content a.anchor svg { + display: inline-block; + width: 14px; + height: 14px; + vertical-align: baseline; + visibility: hidden +} + +.content a.anchor:focus svg { + visibility: visible +} + +.content h1:hover a.anchor svg,.content h2:hover a.anchor svg,.content h3:hover a.anchor svg,.content h4:hover a.anchor svg,.content h5:hover a.anchor svg,.content h6:hover a.anchor svg { + visibility: visible +} + +.footnotes { + font-size: .85em +} + +.footnotes a { + box-shadow: none; + text-decoration: underline; + transition-property: color +} + +.footnotes a:hover { + background: 0 0 +} + +.footnotes a.footnote-backref { + text-decoration: none +} + +.footnotes ol { + line-height: 1.8 +} + +a.footnote-ref { + box-shadow: none; + text-decoration: none; + padding: 2px; + border-radius: 2px; + background-color: #2c3e50 +} + +a.footnote-ref:hover { + box-shadow: none; + background-color: #018574; + transition-property: background-color +} + +.post-info { + font-size: .8rem; + line-height: normal; + opacity: .6 +} + +.post-info p { + margin: .8em 0 +} + +.post-info a:hover { + border-bottom: 1px solid #018574 +} + +.post-info svg { + margin-right: .8em +} + +.post-info .tag { + margin-right: .5em +} + +.post-info .tag::before { + content: "#" +} + +#toc { + position: fixed; + left: 50%; + top: 0; + display: none +} + +.toc-title { + margin-left: 1em; + margin-bottom: .5em; + font-size: .8em; + font-weight: 700 +} + +#TableOfContents { + font-size: .8em; + opacity: .6 +} + +#TableOfContents ul { + padding-left: 1em; + margin: 0 +} + +#TableOfContents>ul { + list-style-type: none +} + +#TableOfContents>ul ul ul { + font-size: .9em +} + +#TableOfContents a:hover { + border-bottom: #018574 1px solid +} + +.post-nav { + display: flex; + justify-content: space-between; + margin-top: 1.5em; + margin-bottom: 2.5em; + font-size: 1.2em +} + +.post-nav a { + flex-basis: 50%; + flex-grow: 1 +} + +.post-nav .next-post { + text-align: left; + padding-right: 5px +} + +.post-nav .prev-post { + text-align: right; + padding-left: 5px +} + +.post-nav .post-nav-label { + font-size: .8em; + opacity: .8; + text-transform: uppercase +} + +.related-posts { + padding: .8em; + margin-top: 1.5em; + font-size: .8rem; + border: 3px dashed rgba(255,255,255,.2); + border-radius: 5px +} + +.related-posts h2 { + margin: 0; + line-height: normal +} + +.related-posts ul { + margin-top: .5em; + margin-bottom: 0 +} + +@media(min-width: 800px) { + .site-main { + margin-top:3em + } + + hr.post-end { + width: 40% + } +} + +@media(min-width: 960px) { + .site-main { + margin-top:6em + } +} + +@media(min-width: 1300px) { + .site-main { + margin-top:8em + } + + .desktop-only,#toc.show-toc { + display: block + } + + .desktop-only-ib { + display: inline-block + } + + figure.left { + margin-left: -240px + } + + figure.left p { + text-align: left + } + + figure.right { + margin-right: -240px + } + + figure.right p { + text-align: right + } + + figure.big { + width: 1200px; + margin-left: -240px + } + + hr.post-end { + width: 30% + } + + #toc { + top: 13em; + margin-left: 370px; + max-width: 220px + } +} + +@media(min-width: 1800px) { + .site-main { + margin-top:10em + } + + .section-inner { + max-width: 1600px + } + + .thin { + max-width: 960px + } + + figure.left { + max-width: 75%; + margin-left: -320px + } + + figure.right { + max-width: 75%; + margin-right: -320px + } + + figure.big { + width: 1600px; + margin-left: -320px + } + + hr.post-end { + width: 30% + } + + #toc { + top: 15em; + margin-left: 490px; + max-width: 300px + } +} + +@media(max-width: 760px) { + .hide-in-mobile,.site-nav.hide-in-mobile { + display:none + } + + #menu-btn { + display: inline-block + } + + .posts-group { + display: block + } + + .posts-group .post-year { + margin: -6px 0 4px + } + + #spotlight.error-404 { + flex-direction: column; + text-align: center + } + + #spotlight.error-404 .banner-404 { + margin: 0 + } +} + +@media(max-width: 520px) { + .content figure.left,.content figure.right { + float:unset; + max-width: 100%; + margin: 0 + } + + hr.post-end { + width: 60% + } + + #mobile-menu { + right: 1.2em + } +} + +#check-flag-btn { + float: right; + padding: 10px 15px; + cursor: pointer; + text-align: center; + text-decoration: none; + outline: none; + color: #fff; + background-color: #999; + border: none; + border-radius: 15px; + box-shadow: 0 9px #444; +} + +#check-flag-btn:hover { + background-color: #3e8e41 +} + +#check-flag-btn:active { + background-color: #3e8e41; + box-shadow: 0 5px #666; + transform: translateY(4px); +} + + +#check-flag-btn span { + cursor: pointer; + display: inline-block; + position: relative; + transition: 0.5s; +} + +#check-flag-btn span:after { + content: '\00bb'; + position: absolute; + opacity: 0; + top: 0; + right: -20px; + transition: 0.5s; +} + +#check-flag-btn:hover span { + padding-right: 25px; +} + +#check-flag-btn:hover span:after { + opacity: 1; + right: 0; +} + +#single-level-checker { + clear: both; +} +/* necessary to give position: relative to parent. */ +input[type="text"]{font: 15px/24px 'Muli', sans-serif; color: #ededed; width: 100%; box-sizing: border-box; letter-spacing: 1px;} +input[type="text"]::placeholder{color: #ccc;} + +:focus{outline: none;} + +.input-effect{ + float: left; + width: 70%; + margin-left: 3%; + margin-right: 3%; + position: relative; +} + +.effect-8 ~ .focus-border:before, +.effect-8 ~ .focus-border:after{content: ""; position: absolute; top: 0; left: 0; width: 0; height: 2px; background-color: #eee; transition: 0.3s;} +.effect-8 ~ .focus-border:after{top: auto; bottom: 0; left: auto; right: 0;} +.effect-8 ~ .focus-border i:before, +.effect-8 ~ .focus-border i:after{content: ""; position: absolute; top: 0; left: 0; width: 2px; height: 0; background-color: #eee; transition: 0.4s;} +.effect-8 ~ .focus-border i:after{left: auto; right: 0; top: auto; bottom: 0;} +.effect-8:focus ~ .focus-border:before, +.effect-8:focus ~ .focus-border:after{width: 100%; transition: 0.3s;} +.effect-8:focus ~ .focus-border i:before, +.effect-8:focus ~ .focus-border i:after{height: 100%; transition: 0.4s;} + +.effect-10-bad, +.effect-10-good{border: 0; padding: 7px 15px; border: 1px solid #ccc; position: relative; background: transparent;} + +.effect-10-bad ~ .focus-bg{position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: red; opacity: 0; transition: 0.5s; z-index: -1;} +.effect-10-bad:focus ~ .focus-bg{transition: 0.5s; opacity: 0.2;} + +.effect-10-good ~ .focus-bg{position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: #4caf50; opacity: 0.2; transition: 0.5s; z-index: -1;} + +.status { + width: max-content; + text-align: center; +} + +.spinner { + width: 40px; + height: 40px; + background-color: #333; + + margin: 100px auto; + -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out; + animation: sk-rotateplane 1.2s infinite ease-in-out; +} + +@-webkit-keyframes sk-rotateplane { + 0% { -webkit-transform: perspective(120px) } + 50% { -webkit-transform: perspective(120px) rotateY(180deg) } + 100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) } +} + +@keyframes sk-rotateplane { + 0% { + transform: perspective(120px) rotateX(0deg) rotateY(0deg); + -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg) + } 50% { + transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); + -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) + } 100% { + transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); + -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); + } +} + +.pyro { + position: absolute; + left: 40%; + top: 50%; + width: 20%; +} + +.pyro > .before, +.pyro > .after { +position: absolute; +width: 5px; +height: 5px; +border-radius: 50%; +box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff; +-moz-animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; +-webkit-animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; +-o-animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; +-ms-animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; +animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; +} + +.pyro > .after { +-moz-animation-delay: 1.25s, 1.25s, 1.25s; +-webkit-animation-delay: 1.25s, 1.25s, 1.25s; +-o-animation-delay: 1.25s, 1.25s, 1.25s; +-ms-animation-delay: 1.25s, 1.25s, 1.25s; +animation-delay: 1.25s, 1.25s, 1.25s; +-moz-animation-duration: 1.25s, 1.25s, 6.25s; +-webkit-animation-duration: 1.25s, 1.25s, 6.25s; +-o-animation-duration: 1.25s, 1.25s, 6.25s; +-ms-animation-duration: 1.25s, 1.25s, 6.25s; +animation-duration: 1.25s, 1.25s, 6.25s; +} + +@-webkit-keyframes bang { +to { + box-shadow: -231px 31.3333333333px #8cff00, -155px -46.6666666667px #002fff, 186px -322.6666666667px #fb00ff, 159px -224.6666666667px #f200ff, -159px -268.6666666667px #ff5900, 129px 12.3333333333px #00ddff, 16px -157.6666666667px #00ffbb, -38px -289.6666666667px #ff00c4, -169px -367.6666666667px #fbff00, -212px -47.6666666667px #ffdd00, -101px -122.6666666667px #00ff80, 139px -359.6666666667px #00ff88, 21px -58.6666666667px #ff001a, 206px 82.3333333333px #0dff00, 21px -98.6666666667px #66ff00, 79px -292.6666666667px #b700ff, 209px -183.6666666667px #7300ff, 107px -203.6666666667px #ff1e00, -79px 50.3333333333px #ff00bf, -162px -13.6666666667px #ff00d5, 181px -6.6666666667px #0022ff, 131px -128.6666666667px #ff00d0, 136px 11.3333333333px #ff006a, 0px -79.6666666667px #ffcc00, -162px -19.6666666667px #00e6ff, -18px -56.6666666667px #0011ff, 48px -353.6666666667px #44ff00, -61px -130.6666666667px #d000ff, -211px -348.6666666667px #00ff91, -106px -100.6666666667px #b7ff00, -89px -20.6666666667px #11ff00, 108px -101.6666666667px #ff0022, -175px 17.3333333333px #00ff88, 149px -342.6666666667px #00e6ff, 60px -11.6666666667px #6aff00, -26px -267.6666666667px #ff0004, 183px -338.6666666667px #1aff00, 0px -221.6666666667px #00ffcc, 222px -239.6666666667px #ff2600, -53px -88.6666666667px #ff0040, -73px 7.3333333333px #ff1100, 101px -121.6666666667px #ff6600, 70px -245.6666666667px #ff0080, -78px -42.6666666667px #ffaa00, -28px -192.6666666667px #d900ff, -159px -386.6666666667px #ff002b, 167px 3.3333333333px #ff4d00, -246px -6.6666666667px #ffee00, -56px 61.3333333333px #ff2200, -175px -74.6666666667px #ff0011, 214px 39.3333333333px #ff006a; +} +} +@-moz-keyframes bang { +to { + box-shadow: -231px 31.3333333333px #8cff00, -155px -46.6666666667px #002fff, 186px -322.6666666667px #fb00ff, 159px -224.6666666667px #f200ff, -159px -268.6666666667px #ff5900, 129px 12.3333333333px #00ddff, 16px -157.6666666667px #00ffbb, -38px -289.6666666667px #ff00c4, -169px -367.6666666667px #fbff00, -212px -47.6666666667px #ffdd00, -101px -122.6666666667px #00ff80, 139px -359.6666666667px #00ff88, 21px -58.6666666667px #ff001a, 206px 82.3333333333px #0dff00, 21px -98.6666666667px #66ff00, 79px -292.6666666667px #b700ff, 209px -183.6666666667px #7300ff, 107px -203.6666666667px #ff1e00, -79px 50.3333333333px #ff00bf, -162px -13.6666666667px #ff00d5, 181px -6.6666666667px #0022ff, 131px -128.6666666667px #ff00d0, 136px 11.3333333333px #ff006a, 0px -79.6666666667px #ffcc00, -162px -19.6666666667px #00e6ff, -18px -56.6666666667px #0011ff, 48px -353.6666666667px #44ff00, -61px -130.6666666667px #d000ff, -211px -348.6666666667px #00ff91, -106px -100.6666666667px #b7ff00, -89px -20.6666666667px #11ff00, 108px -101.6666666667px #ff0022, -175px 17.3333333333px #00ff88, 149px -342.6666666667px #00e6ff, 60px -11.6666666667px #6aff00, -26px -267.6666666667px #ff0004, 183px -338.6666666667px #1aff00, 0px -221.6666666667px #00ffcc, 222px -239.6666666667px #ff2600, -53px -88.6666666667px #ff0040, -73px 7.3333333333px #ff1100, 101px -121.6666666667px #ff6600, 70px -245.6666666667px #ff0080, -78px -42.6666666667px #ffaa00, -28px -192.6666666667px #d900ff, -159px -386.6666666667px #ff002b, 167px 3.3333333333px #ff4d00, -246px -6.6666666667px #ffee00, -56px 61.3333333333px #ff2200, -175px -74.6666666667px #ff0011, 214px 39.3333333333px #ff006a; +} +} +@-o-keyframes bang { +to { + box-shadow: -231px 31.3333333333px #8cff00, -155px -46.6666666667px #002fff, 186px -322.6666666667px #fb00ff, 159px -224.6666666667px #f200ff, -159px -268.6666666667px #ff5900, 129px 12.3333333333px #00ddff, 16px -157.6666666667px #00ffbb, -38px -289.6666666667px #ff00c4, -169px -367.6666666667px #fbff00, -212px -47.6666666667px #ffdd00, -101px -122.6666666667px #00ff80, 139px -359.6666666667px #00ff88, 21px -58.6666666667px #ff001a, 206px 82.3333333333px #0dff00, 21px -98.6666666667px #66ff00, 79px -292.6666666667px #b700ff, 209px -183.6666666667px #7300ff, 107px -203.6666666667px #ff1e00, -79px 50.3333333333px #ff00bf, -162px -13.6666666667px #ff00d5, 181px -6.6666666667px #0022ff, 131px -128.6666666667px #ff00d0, 136px 11.3333333333px #ff006a, 0px -79.6666666667px #ffcc00, -162px -19.6666666667px #00e6ff, -18px -56.6666666667px #0011ff, 48px -353.6666666667px #44ff00, -61px -130.6666666667px #d000ff, -211px -348.6666666667px #00ff91, -106px -100.6666666667px #b7ff00, -89px -20.6666666667px #11ff00, 108px -101.6666666667px #ff0022, -175px 17.3333333333px #00ff88, 149px -342.6666666667px #00e6ff, 60px -11.6666666667px #6aff00, -26px -267.6666666667px #ff0004, 183px -338.6666666667px #1aff00, 0px -221.6666666667px #00ffcc, 222px -239.6666666667px #ff2600, -53px -88.6666666667px #ff0040, -73px 7.3333333333px #ff1100, 101px -121.6666666667px #ff6600, 70px -245.6666666667px #ff0080, -78px -42.6666666667px #ffaa00, -28px -192.6666666667px #d900ff, -159px -386.6666666667px #ff002b, 167px 3.3333333333px #ff4d00, -246px -6.6666666667px #ffee00, -56px 61.3333333333px #ff2200, -175px -74.6666666667px #ff0011, 214px 39.3333333333px #ff006a; +} +} +@-ms-keyframes bang { +to { + box-shadow: -231px 31.3333333333px #8cff00, -155px -46.6666666667px #002fff, 186px -322.6666666667px #fb00ff, 159px -224.6666666667px #f200ff, -159px -268.6666666667px #ff5900, 129px 12.3333333333px #00ddff, 16px -157.6666666667px #00ffbb, -38px -289.6666666667px #ff00c4, -169px -367.6666666667px #fbff00, -212px -47.6666666667px #ffdd00, -101px -122.6666666667px #00ff80, 139px -359.6666666667px #00ff88, 21px -58.6666666667px #ff001a, 206px 82.3333333333px #0dff00, 21px -98.6666666667px #66ff00, 79px -292.6666666667px #b700ff, 209px -183.6666666667px #7300ff, 107px -203.6666666667px #ff1e00, -79px 50.3333333333px #ff00bf, -162px -13.6666666667px #ff00d5, 181px -6.6666666667px #0022ff, 131px -128.6666666667px #ff00d0, 136px 11.3333333333px #ff006a, 0px -79.6666666667px #ffcc00, -162px -19.6666666667px #00e6ff, -18px -56.6666666667px #0011ff, 48px -353.6666666667px #44ff00, -61px -130.6666666667px #d000ff, -211px -348.6666666667px #00ff91, -106px -100.6666666667px #b7ff00, -89px -20.6666666667px #11ff00, 108px -101.6666666667px #ff0022, -175px 17.3333333333px #00ff88, 149px -342.6666666667px #00e6ff, 60px -11.6666666667px #6aff00, -26px -267.6666666667px #ff0004, 183px -338.6666666667px #1aff00, 0px -221.6666666667px #00ffcc, 222px -239.6666666667px #ff2600, -53px -88.6666666667px #ff0040, -73px 7.3333333333px #ff1100, 101px -121.6666666667px #ff6600, 70px -245.6666666667px #ff0080, -78px -42.6666666667px #ffaa00, -28px -192.6666666667px #d900ff, -159px -386.6666666667px #ff002b, 167px 3.3333333333px #ff4d00, -246px -6.6666666667px #ffee00, -56px 61.3333333333px #ff2200, -175px -74.6666666667px #ff0011, 214px 39.3333333333px #ff006a; +} +} +@keyframes bang { +to { + box-shadow: -231px 31.3333333333px #8cff00, -155px -46.6666666667px #002fff, 186px -322.6666666667px #fb00ff, 159px -224.6666666667px #f200ff, -159px -268.6666666667px #ff5900, 129px 12.3333333333px #00ddff, 16px -157.6666666667px #00ffbb, -38px -289.6666666667px #ff00c4, -169px -367.6666666667px #fbff00, -212px -47.6666666667px #ffdd00, -101px -122.6666666667px #00ff80, 139px -359.6666666667px #00ff88, 21px -58.6666666667px #ff001a, 206px 82.3333333333px #0dff00, 21px -98.6666666667px #66ff00, 79px -292.6666666667px #b700ff, 209px -183.6666666667px #7300ff, 107px -203.6666666667px #ff1e00, -79px 50.3333333333px #ff00bf, -162px -13.6666666667px #ff00d5, 181px -6.6666666667px #0022ff, 131px -128.6666666667px #ff00d0, 136px 11.3333333333px #ff006a, 0px -79.6666666667px #ffcc00, -162px -19.6666666667px #00e6ff, -18px -56.6666666667px #0011ff, 48px -353.6666666667px #44ff00, -61px -130.6666666667px #d000ff, -211px -348.6666666667px #00ff91, -106px -100.6666666667px #b7ff00, -89px -20.6666666667px #11ff00, 108px -101.6666666667px #ff0022, -175px 17.3333333333px #00ff88, 149px -342.6666666667px #00e6ff, 60px -11.6666666667px #6aff00, -26px -267.6666666667px #ff0004, 183px -338.6666666667px #1aff00, 0px -221.6666666667px #00ffcc, 222px -239.6666666667px #ff2600, -53px -88.6666666667px #ff0040, -73px 7.3333333333px #ff1100, 101px -121.6666666667px #ff6600, 70px -245.6666666667px #ff0080, -78px -42.6666666667px #ffaa00, -28px -192.6666666667px #d900ff, -159px -386.6666666667px #ff002b, 167px 3.3333333333px #ff4d00, -246px -6.6666666667px #ffee00, -56px 61.3333333333px #ff2200, -175px -74.6666666667px #ff0011, 214px 39.3333333333px #ff006a; +} +} +@-webkit-keyframes gravity { +to { + transform: translateY(200px); + -moz-transform: translateY(200px); + -webkit-transform: translateY(200px); + -o-transform: translateY(200px); + -ms-transform: translateY(200px); + opacity: 0; +} +} +@-moz-keyframes gravity { +to { + transform: translateY(200px); + -moz-transform: translateY(200px); + -webkit-transform: translateY(200px); + -o-transform: translateY(200px); + -ms-transform: translateY(200px); + opacity: 0; +} +} +@-o-keyframes gravity { +to { + transform: translateY(200px); + -moz-transform: translateY(200px); + -webkit-transform: translateY(200px); + -o-transform: translateY(200px); + -ms-transform: translateY(200px); + opacity: 0; +} +} +@-ms-keyframes gravity { +to { + transform: translateY(200px); + -moz-transform: translateY(200px); + -webkit-transform: translateY(200px); + -o-transform: translateY(200px); + -ms-transform: translateY(200px); + opacity: 0; +} +} +@keyframes gravity { +to { + transform: translateY(200px); + -moz-transform: translateY(200px); + -webkit-transform: translateY(200px); + -o-transform: translateY(200px); + -ms-transform: translateY(200px); + opacity: 0; +} +} +@-webkit-keyframes position { +0%, +19.9% { + margin-top: 10%; + margin-left: 40%; +} +20%, +39.9% { + margin-top: 40%; + margin-left: 30%; +} +40%, +59.9% { + margin-top: 20%; + margin-left: 70%; +} +60%, +79.9% { + margin-top: 30%; + margin-left: 20%; +} +80%, +99.9% { + margin-top: 30%; + margin-left: 80%; +} +} +@-moz-keyframes position { +0%, +19.9% { + margin-top: 10%; + margin-left: 40%; +} +20%, +39.9% { + margin-top: 40%; + margin-left: 30%; +} +40%, +59.9% { + margin-top: 20%; + margin-left: 70%; +} +60%, +79.9% { + margin-top: 30%; + margin-left: 20%; +} +80%, +99.9% { + margin-top: 30%; + margin-left: 80%; +} +} +@-o-keyframes position { +0%, +19.9% { + margin-top: 10%; + margin-left: 40%; +} +20%, +39.9% { + margin-top: 40%; + margin-left: 30%; +} +40%, +59.9% { + margin-top: 20%; + margin-left: 70%; +} +60%, +79.9% { + margin-top: 30%; + margin-left: 20%; +} +80%, +99.9% { + margin-top: 30%; + margin-left: 80%; +} +} +@-ms-keyframes position { +0%, +19.9% { + margin-top: 10%; + margin-left: 40%; +} +20%, +39.9% { + margin-top: 40%; + margin-left: 30%; +} +40%, +59.9% { + margin-top: 20%; + margin-left: 70%; +} +60%, +79.9% { + margin-top: 30%; + margin-left: 20%; +} +80%, +99.9% { + margin-top: 30%; + margin-left: 80%; +} +} +@keyframes position { +0%, +19.9% { + margin-top: 10%; + margin-left: 40%; +} +20%, +39.9% { + margin-top: 40%; + margin-left: 30%; +} +40%, +59.9% { + margin-top: 20%; + margin-left: 70%; +} +60%, +79.9% { + margin-top: 30%; + margin-left: 20%; +} +80%, +99.9% { + margin-top: 30%; + margin-left: 80%; +} +} diff --git a/submit-flags/submit-flags-app/static/favicon.ico b/submit-flags/submit-flags-app/static/favicon.ico new file mode 100644 index 0000000..ec3f627 Binary files /dev/null and b/submit-flags/submit-flags-app/static/favicon.ico differ diff --git a/submit-flags/submit-flags-app/static/index.html b/submit-flags/submit-flags-app/static/index.html new file mode 100644 index 0000000..28d5af1 --- /dev/null +++ b/submit-flags/submit-flags-app/static/index.html @@ -0,0 +1,14 @@ + + + + + + 🚩 Make Git Better CTF 🚩 Submit Flags 🚩 + + + + + diff --git a/submit-flags/submit-flags-app/static/levels_info.json b/submit-flags/submit-flags-app/static/levels_info.json new file mode 100644 index 0000000..51b1cf2 --- /dev/null +++ b/submit-flags/submit-flags-app/static/levels_info.json @@ -0,0 +1,25 @@ +{ + "levels": + [ + { + "name": "remote-1", + "flag": "cb045a9406baa9354fa33cff3fdd27f48165b07cc5a9da025eb4f52ead3d483f" + }, + { + "name": "log-5", + "flag": "cd8a9375f7d4e9c37e1d402f55ac13fd7762d48d975b6ff6b84b136e4dd8f96f" + }, + { + "name": "merge-5", + "flag": "b1a4235065bbb7ac9ba90106d56c2d7fa691524621d1c5067920b04b291031e2" + }, + { + "name": "hooks-2", + "flag": "2ac12517dba3277afbffeece186e2884e5cecf90e970ce9a7c8f550f12c5ef60" + }, + { + "name": "tag-2", + "flag": "7ca275600a4d349e3ba7517d0ca4a9f5e0347f4b7a3fb90e3aa102a82db607fe" + } + ] +}