diff --git a/composer.json b/composer.json index 5bd50c2..6352271 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "tuupola/slim-jwt-auth": "^3.6", "ext-dom": "*", "ext-libxml": "*", - "donatello-za/rake-php-plus": "^1.0" + "donatello-za/rake-php-plus": "^1.0", + "phpmailer/phpmailer": "^6.9" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index e8a2eee..02c0408 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f675ad7eec6390b82dca0b13dec49e5b", + "content-hash": "f156a57e5e895727417d4274c8ad414c", "packages": [ { "name": "donatello-za/rake-php-plus", @@ -1047,6 +1047,87 @@ "time": "2020-10-12T12:39:22+00:00" }, { + "name": "phpmailer/phpmailer", + "version": "v6.9.1", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2023-11-25T22:23:28+00:00" + }, + { "name": "psr/container", "version": "1.1.2", "source": { diff --git a/dist/api/blog/blogData.php b/dist/api/blog/blogData.php index 3221e58..459442c 100644 --- a/dist/api/blog/blogData.php +++ b/dist/api/blog/blogData.php @@ -8,11 +8,15 @@ use DOMDocument; use PDO; use Psr\Http\Message\UploadedFileInterface; use DonatelloZa\RakePlus\RakePlus; -use function DI\string; +use PHPMailer\PHPMailer\PHPMailer; +use PHPMailer\PHPMailer\Exception; +use function api\utils\dbConn; +use function api\utils\getEmailPassword; use const api\utils\feedGenerator\ATOM; use const api\utils\feedGenerator\RSS2; + require_once __DIR__ . "/../utils/config.php"; require_once __DIR__ . "/../utils/imgUtils.php"; require_once __DIR__ . "/../utils/feedGenerator/FeedWriter.php"; @@ -254,10 +258,10 @@ class blogData * @param string $dateCreated - Date the blog post was created * @param bool $featured - Whether the blog post is featured or not * @param string $categories - Categories of the blog post - * @param UploadedFileInterface $headerImg - Header image of the blog post + * @param UploadedFileInterface|null $headerImg - Header image of the blog post * @return int|string - ID of the blog post or error message */ - public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string + public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface|null $headerImg): int|string { $conn = dbConn(); $folderID = uniqid(); @@ -289,6 +293,13 @@ class blogData $keywords = implode(", ", RakePlus::create($bodyText)->keywords()); + $latest = $this->getLatestBlogPost(); + $prevTitle = $latest["title"]; + $prevAbstract = $latest["abstract"]; + $prevHeaderImage = $latest["headerImg"]; + + $headerImage = $targetFile["imgLocation"]; + $stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, bodyText, categories, keywords, folderID) VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :bodyText, :categories, :keywords, :folderID);"); $stmt->bindParam(":title", $title); @@ -296,7 +307,7 @@ class blogData $stmt->bindParam(":dateModified", $dateCreated); $isFeatured = $featured ? 1 : 0; $stmt->bindParam(":featured", $isFeatured); - $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); + $stmt->bindParam(":headerImg", $headerImage); $stmt->bindParam(":abstract", $abstract); $stmt->bindParam(":body", $newBody); $stmt->bindParam(":bodyText", $bodyText); @@ -304,12 +315,267 @@ class blogData $stmt->bindParam(":keywords", $keywords); $stmt->bindParam(":folderID", $folderID); - if ($stmt->execute()) + if (!$stmt->execute()) { - return intval($conn->lastInsertId()); + return "Error, couldn't create post"; } - return "Error, couldn't create post"; + $stmtEmails = $conn->prepare("SELECT email FROM newsletter;"); + $stmtEmails->execute(); + $emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC); + + $emailBody = << + + + + Rohit Pai's blog + + + +
+
+

Hey, I've got a new post!

+
+ +
+
+
+

latest post

+
+ header image of the latest post +
+ +
+

$title

+

$abstract

+ See Post +
+
+ +
+

in case you missed the previous post

+
+ header image of the previous post +
+ +
+

$prevTitle

+

$prevAbstract

+ See Post +
+
+
+ + + +EOD; + + foreach ($emails as $email) + { + $this->sendMail($email["email"], $emailBody, "Hey, Rohit's blog has a new post!"); + } + + return intval($conn->lastInsertId()); + } /** @@ -616,12 +882,12 @@ class blogData foreach ($posts as $post) { $items[] = array( - "id" => string($post["ID"]), + "id" => strval($post["ID"]), "url" => "https://rohitpai.co.uk/blog/post/" . rawurlencode($post["title"]) . "#disqus_thread", "title" => $post["title"], "date_published" => date($post["dateCreated"]), "date_modified" => date($post["dateModified"]), -// "description" => $post["abstract"], + "description" => $post["abstract"], "banner_image" => "https://rohitpai.co.uk/" . rawurlencode($post["headerImg"]), "content_html" => $post["body"] ); @@ -655,4 +921,572 @@ class blogData return $feed; } + + /** + * Add an email to the newsletter and send welcome email + * @param string $email - Email to add to the newsletter + * @return string|array - Success or error message + */ + public function addNewsletterEmail(string $email): string|array + { + $conn = dbConn(); + $stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;"); + $stmtCheckEmail->bindParam(":email", $email); + $stmtCheckEmail->execute(); + $result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC); + + if ($result) + { + return "Email already exists"; + } + + $stmt = $conn->prepare("INSERT INTO newsletter (email) VALUES (:email);"); + $stmt->bindParam(":email", $email); + $stmt->execute(); + + $body = << + + + + Rohit Pai's blog + + + +
+
+

hello from rohit

+
+ +
+
+
+

hey there, i'm rohit!

+
+
+

What to Expect

+

You'll get an email from me everytime I make a new post to my blog. Sometimes, you may get a special + email on occasion, where I talk about something interesting that's not worth a full on post but I + Still want to tell you about it.

+

Don't worry, I won't spam you with emails. Well, thanks for signing up to my newsletter, hopefully + you'll hear from me soon!

+

P.S. Please consider adding this email address rohit@rohitpai.co.uk to your emails + contact list so that my emails won't get sent to span, thanks.

+
+
+
+ + + + EOD; + + return $this->sendMail($email, $body, "Hello from Rohit!"); + + } + + /** + * Send an email to the given email address + * @param string $email - Email address to send the email to + * @param string $body - Body of the email + * @param string $subject - Subject of the email + * @return string|string[] + */ + public function sendMail(string $email, string $body, string $subject): string|array + { + $mail = new PHPMailer(true); + try + { + $mail->isSMTP(); + $mail->Host = "smtp.hostinger.com"; + $mail->SMTPAuth = true; + $mail->Username = "rohit@rohitpai.co.uk"; + $mail->Password = getEmailPassword(); + $mail->SMTPSecure = "tls"; + $mail->Port = 587; + $mail->setFrom("rohit@rohitpai.co.uk", "Rohit Pai"); + $mail->addAddress($email); + $mail->isHTML(); + $mail->Subject = $subject; + $mail->Body = $body; + $mail->send(); + return "success"; + } + catch (Exception $e) + { + return array("errorMessage" => "Error, couldn't send email because of " . $e); + } + } + + /** + * @param string $email - Email to delete from the newsletter + * @return string - Success or error message + */ + public function deleteNewsletterEmail(string $email): string + { + $conn = dbConn(); + + $stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;"); + $stmtCheckEmail->bindParam(":email", $email); + $stmtCheckEmail->execute(); + $result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "email not found"; + } + + $stmt = $conn->prepare("DELETE FROM newsletter WHERE email = :email;"); + $stmt->bindParam(":email", $email); + $stmt->execute();; + + return "success"; + } + + /** + * @param string $subject - Subject of the newsletter + * @param string $message - Message content + * @return array|string - Success or error message + */ + public function sendNewsletter(string $subject, string $message): array|string + { + $conn = dbConn(); + $stmtEmails = $conn->prepare("SELECT email FROM newsletter;"); + $stmtEmails->execute(); + $emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC); + $msg = ""; + + $body = << + + + + Rohit Pai's blog + + + +
+
+

a surprise hello from rohit

+
+ +
+
+
+

$subject

+
+ $message +
+
+
+ + + + EOD; + + + foreach ($emails as $email) + { + $msg = $this->sendMail($email["email"], $body, $subject); + if (is_array($msg)) + { + return $msg; + } + } + + return $msg; + } } \ No newline at end of file diff --git a/dist/api/blog/blogRoutes.php b/dist/api/blog/blogRoutes.php index 975052c..320c9cf 100644 --- a/dist/api/blog/blogRoutes.php +++ b/dist/api/blog/blogRoutes.php @@ -269,12 +269,38 @@ class blogRoutes implements routesInterface return $response; }); + $app->delete("/blog/newsletter/{email}", function (Request $request, Response $response, $args) + { + if ($args["email"] == null) + { + $response->getBody()->write(json_encode(array("error" => "Please provide an email"))); + return $response->withStatus(400); + } + + $message = $this->blogData->deleteNewsletterEmail($args["email"]); + + if ($message === "email not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, email not found"))); + return $response->withStatus(404); + } + + if ($message === "error") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, something went wrong"))); + return $response->withStatus(500); + } + + return $response; + }); + $app->post("/blog/post", function (Request $request, Response $response) { $data = $request->getParsedBody(); $files = $request->getUploadedFiles(); - $headerImg = $files["headerImg"]; - if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"])) + if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"])) { // uh oh sent some empty data $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); @@ -288,6 +314,11 @@ class blogRoutes implements routesInterface return $response->withStatus(400); } + if (array_key_exists("headerImg", $files)) + { + $headerImg = $files["headerImg"]; + } + if (empty($files["headerImg"])) { $headerImg = null; @@ -339,19 +370,66 @@ class blogRoutes implements routesInterface if (empty($files)) { // uh oh sent some empty data - $response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent")))); + $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); return $response->withStatus(400); } $message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]); if (!is_array($message)) { - $response->getBody()->write(json_encode(array("error" => array("message" => $message)))); + $response->getBody()->write(json_encode(array("error" => $message))); return $response->withStatus(500); } $response->getBody()->write(json_encode($message)); return $response->withStatus(201); }); + + $app->post("/blog/newsletter", function (Request $request, Response $response) + { + $data = $request->getParsedBody(); + if (empty($data["subject"]) || empty($data["message"])) + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); + return $response->withStatus(400); + } + + $message = $this->blogData->sendNewsletter(strtolower($data["subject"]), $data["message"]); + if (is_array($message)) + { + $response->getBody()->write(json_encode(array("error" => "Error, something went wrong"))); + return $response->withStatus(500); + } + + $response->getBody()->write(json_encode(array("message" => "Message sent"))); + return $response->withStatus(201); + }); + + $app->post("/blog/newsletter/{email}", function (Request $request, Response $response, $args) + { + if ($args["email"] == null) + { + $response->getBody()->write(json_encode(array("error" => "Please provide an email"))); + return $response->withStatus(400); + } + + $message = $this->blogData->addNewsletterEmail($args["email"]); + if ($message === "Email already exists") + { + $response->getBody()->write(json_encode(array("message" => "exists"))); + return $response->withStatus(409); + } + + if (is_array($message) || !$message || $message === "error") + { + $response->getBody()->write(json_encode(array("message" => "Something went wrong"))); + return $response->withStatus(500); + } + + $response->getBody()->write(json_encode(array("message" => "Thanks for signing up!"))); + return $response->withStatus(201); + }); + } } \ No newline at end of file diff --git a/dist/api/project/projectData.php b/dist/api/project/projectData.php index 8a81b2c..fb1c631 100644 --- a/dist/api/project/projectData.php +++ b/dist/api/project/projectData.php @@ -5,6 +5,7 @@ namespace api\project; use api\utils\imgUtils; use PDO; use Psr\Http\Message\UploadedFileInterface; +use function api\utils\dbConn; require_once __DIR__ . "/../utils/config.php"; require_once __DIR__ . "/../utils/imgUtils.php"; diff --git a/dist/api/timeline/timelineData.php b/dist/api/timeline/timelineData.php index f646e5e..9bb40e4 100644 --- a/dist/api/timeline/timelineData.php +++ b/dist/api/timeline/timelineData.php @@ -3,6 +3,7 @@ namespace api\timeline; use PDO; +use function api\utils\dbConn; require_once __DIR__ . "/../utils/config.php"; diff --git a/dist/api/user/userData.php b/dist/api/user/userData.php index d63d785..0e42372 100644 --- a/dist/api/user/userData.php +++ b/dist/api/user/userData.php @@ -4,6 +4,8 @@ namespace api\user; use Firebase\JWT\JWT; use PDO; +use function api\utils\dbConn; +use function api\utils\getSecretKey; require_once __DIR__ . "/../utils/config.php"; diff --git a/dist/api/utils/middleware.php b/dist/api/utils/middleware.php index 14d7627..71c851c 100644 --- a/dist/api/utils/middleware.php +++ b/dist/api/utils/middleware.php @@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; use Slim\Psr7\Response; +use Throwable; use Tuupola\Middleware\JwtAuthentication; use Tuupola\Middleware\JwtAuthentication\RequestMethodRule; use Tuupola\Middleware\JwtAuthentication\RequestPathRule; @@ -84,8 +85,8 @@ class middleware $app->add(new JwtAuthentication([ "rules" => [ new RequestPathRule([ - "path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"], - "ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"] + "path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"], + "ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"] ]), new RequestMethodRule([ "ignore" => ["OPTIONS", "GET"] @@ -133,8 +134,27 @@ class middleware return $response; } }); - $app->addErrorMiddleware(true, true, true); + + $errorMiddleware = $app->addErrorMiddleware(true, true, true); + + + $errorHandler = $errorMiddleware->getDefaultErrorHandler(); + + $errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception, + bool $displayErrorDetails, + bool $logErrors, + bool $logErrorDetails + ) use ($app, $errorHandler) + { + $statusCode = $exception->getCode() ?: 500; + + // Create a JSON response with the error message + $response = $app->getResponseFactory()->createResponse($statusCode); + $response->getBody()->write(json_encode(['error' => $exception->getMessage()])); + + return $response; + }); } } \ No newline at end of file diff --git a/dist/api/utils/user/userData.php b/dist/api/utils/user/userData.php new file mode 100644 index 0000000..0e42372 --- /dev/null +++ b/dist/api/utils/user/userData.php @@ -0,0 +1,142 @@ +prepare("SELECT * FROM users WHERE username = :username"); + $stmt->bindParam(":username", $username); + $stmt->execute(); + + // set the resulting array to associative + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if ($result) + { + if (password_verify($password, $result[0]["password"])) + { + return true; + } + return false; + } + return false; + } + + /** + * Create a JWT token + * @param $username string - Username + * @return string - JWT token + */ + public function createToken(string $username): string + { + $now = time(); + $future = strtotime('+2 day', $now); + $secretKey = getSecretKey(); + $payload = [ + "jti" => $username, + "iat" => $now, + "exp" => $future + ]; + + return JWT::encode($payload, $secretKey, "HS256"); + } + + /** + * Check if email is already in use + * @param string $email - Email to check + * @return bool - True if email exists, false if not + */ + public function checkEmail(string $email): bool + { + $conn = dbConn(); + $stmt = $conn->prepare("SELECT * FROM users WHERE email = :email"); + $stmt->bindParam(":email", $email); + $stmt->execute(); + + // set the resulting array to associative + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if ($result) + { + return true; + } + return false; + } + + /** + * Send a verification email to the userData + * @param $email - email address of the userData + * @return string - verification code + */ + public function sendResetEmail($email): string + { + //generate a random token and email the address + $token = uniqid("rpe-"); + $headers1 = "From: noreply@rohitpai.co.uk\r\n"; + $headers1 .= "MIME-Version: 1.0\r\n"; + $headers1 .= "Content-Type: text/html; charset=UTF-8\r\n"; + + $message = " + + + + + + + Document + + +

Reset Password Verification Code

+
+

Please enter the following code to reset your password: $token

+ + + "; + + mail($email, "Reset Password Verification Code", $message, $headers1); + return $token; + } + + /** + * Change password for an email with new password + * @param $email string Email + * @param $password string Password + * @return bool - true if the password was changed, false if not + */ + public function changePassword(string $email, string $password): bool + { + $conn = dbConn(); + $stmt = $conn->prepare("UPDATE users SET password = :password WHERE email = :email"); + $newPwd = password_hash($password, PASSWORD_BCRYPT); + $stmt->bindParam(":password", $newPwd); + $stmt->bindParam(":email", $email); + + if ($stmt->execute()) + { + return true; + } + return false; + } + + +} \ No newline at end of file diff --git a/dist/api/utils/user/userRoutes.php b/dist/api/utils/user/userRoutes.php new file mode 100644 index 0000000..3471929 --- /dev/null +++ b/dist/api/utils/user/userRoutes.php @@ -0,0 +1,168 @@ +user = new userData(); + $this->createRoutes($app); + } + + /** + * creates the routes for the user + * @param App $app - the slim app used to create the routes + * @return void - returns nothing + */ + public function createRoutes(App $app): void + { + $app->post("/user/login", function (Request $request, Response $response) + { + // get request data + $data = $request->getParsedBody(); + + if (empty($data["username"]) || empty($data["password"])) + { + // uh oh user sent empty data + return $response->withStatus(400); + } + + if ($this->user->checkUser($data["username"], $data["password"])) + { + // yay, user is logged in + $_SESSION["token"] = $this->user->createToken($data["username"]); + $_SESSION["username"] = $data["username"]; + + $inactive = 60 * 60 * 48; // 2 days + $_SESSION["timeout"] = time() + $inactive; + + $response->getBody()->write(json_encode(array("token" => $_SESSION["token"]))); + return $response; + } + $response->getBody()->write(json_encode(array("error" => "Unauthorised"))); + return $response->withStatus(401); + }); + + $app->get("/user/logout", function (Request $request, Response $response) + { + session_unset(); + return $response; + }); + + $app->get("/user/isLoggedIn", function (Request $request, Response $response) + { + if (empty($_SESSION["token"]) && empty($_SESSION["username"])) + { + // uh oh user not logged in + return $response->withStatus(401); + } + + $inactive = 60 * 60 * 48; // 2 days + $sessionLife = time() - $_SESSION["timeout"]; + if ($sessionLife > $inactive) + { + // uh oh user session expired + session_destroy(); + return $response->withStatus(401); + } + + if (empty($_SESSION["token"])) + { + // user is logged in but no token was created + $_SESSION["token"] = $this->user->createToken($_SESSION["username"]); + return $response->withStatus(201); + } + + $response->getBody()->write(json_encode(array("token" => $_SESSION["token"]))); + return $response; + + }); + + $app->get("/user/checkResetEmail/{email}", function (Request $request, Response $response, array $args) + { + if (empty($args["email"])) + { + // uh oh sent empty data + return $response->withStatus(400); + } + + if ($this->user->checkEmail($args["email"])) + { + // yay email does exist + $_SESSION["resetToken"] = $this->user->sendResetEmail($args["email"]); + $_SESSION["resetEmail"] = $args["email"]; + return $response; + } + return $response->withStatus(404); + }); + + $app->get("/user/resendEmail", function (Request $request, Response $response) + { + if (empty($_SESSION["resetToken"])) + { + // uh oh not authorized to resend email + return $response->withStatus(401); + } + + $_SESSION["resetToken"] = $this->user->sendResetEmail($_SESSION["resetEmail"]); + return $response; + }); + + $app->get("/user/checkResetCode/{code}", function (Request $request, Response $response, array $args) + { + if (empty($args["code"])) + { + // uh oh sent empty data + return $response->withStatus(400); + } + + if ($_SESSION["resetToken"] === $args["code"]) + { + // yay, code code matches + return $response; + } + + return $response->withStatus(401); + }); + + $app->post("/user/changePassword", function (Request $request, Response $response) + { + if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"])) + { + // uh oh not authorized to change password + return $response->withStatus(401); + } + + $data = $request->getParsedBody(); + if (empty($data["password"])) + { + // uh oh sent empty data + return $response->withStatus(400); + } + + if ($this->user->changePassword($_SESSION["resetEmail"], $data["password"])) + { + // yay, password changed + unset($_SESSION["resetToken"]); + unset($_SESSION["resetEmail"]); + return $response->withStatus(201); + } + + return $response->withStatus(500); + }); + } +} \ No newline at end of file diff --git a/dist/blog/css/main.css b/dist/blog/css/main.css index becadfa..1e726ca 100644 --- a/dist/blog/css/main.css +++ b/dist/blog/css/main.css @@ -1 +1 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}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}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-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}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 78%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,button.btn,form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}form input[type=submit]{padding:1.1em 2em}a.btn:hover,button.btn:hover form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btn:hover::after,a.btn:hover::before{visibility:hidden}a.btnPrimary,button.btnPrimary,form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline,button.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled],button.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover,button.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,button.btnPrimary:hover,form input[type=submit]:hover{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}a.btn:active,button.btn:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]){height:3em}form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}form .formControl.passwordControl{display:block}form input[type=submit]{align-self:flex-start}div.menu input:not([type=submit]),form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:.3125em solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:.5em;-moz-border-radius:.5em;border-radius:.5em;padding:0 .5em}form .formControl textarea{padding:.5em}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.menu input:not([type=submit]):focus,div.menu input:not([type=submit]):hover,form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:.3125em solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}form .formControl .checkContainer input:checked~.checkmark:after{display:block}form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}a{color:#000;text-decoration:none;text-transform:lowercase}a.link{padding:0 .5em}a.link::after,a.link::before{visibility:hidden;position:absolute;margin-top:1px}a.link::before{content:' <';margin-left:-.5em}a.link::after{content:'> '}a.link:hover::after,a.link:hover::before{visibility:visible}header{background:#6a6a6a url(../../imgs/hero.jpg) no-repeat bottom;background-size:cover;height:40%;color:#fff;backdrop-filter:grayscale(100%);position:relative}nav{display:flex;flex-direction:row;justify-content:space-between;padding:.25em;position:fixed;top:0;width:100%;transition:background-color .4s ease-in;color:#fff;z-index:100000000000000000000000000}nav.scrolled{background-color:var(--navBack)}nav #nav-check{display:none}nav .nav-btn{display:none}nav h1{margin:0}nav a{text-decoration:none;color:#fff}nav>h1{margin-left:.4em}nav ul{display:flex;flex-direction:row;gap:1em;margin:0 .5em 0 0;justify-content:flex-end;align-items:flex-end}nav ul li{list-style:none}nav ul li span{visibility:hidden}nav ul li .active::after,nav ul li .active::before{visibility:visible}header div{display:flex;flex-direction:column;justify-content:center;align-items:center;padding-top:10em}header div .btn{margin:2em 0}header div button{background:0 0;border:none;display:inline-block;text-align:center;text-decoration:none;font-size:2rem;cursor:pointer}i.fa-chevron-down{color:hsla(0,0%,67%,.58);font-size:3.75em;margin:1.5rem 0}div h1 span{visibility:visible;animation:caret 1s steps(1) infinite}@keyframes caret{50%{visibility:hidden}}@media screen and (max-width:75em){nav{display:block;height:50px;width:100%;background-color:var(--navBack);position:fixed;top:0;padding:0}nav a h1{margin-left:1ch}nav .nav-btn{display:inline-block;position:absolute;right:75px;top:0}nav ul{position:fixed;display:block;width:100%;background-color:#333;transition:all .4s ease-in;overflow-y:hidden;padding-left:.5em;margin-top:7px}nav ul li a{display:block;width:100%;transform:translateX(-30px);transition:all .4s ease-in;opacity:0}.nav-btn label{display:inline-block;cursor:pointer;width:60px;height:50px;position:fixed;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.5s ease-in;-moz-transition:.5s ease-in;-o-transition:.5s ease-in;transition:.5s ease-in}.nav-btn label span{display:block;position:absolute;height:5px;width:100%;background-color:#fff;opacity:1;right:0;top:20px;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.25s ease-in;-moz-transition:.25s ease-in;-o-transition:.25s ease-in;transition:.25s ease-in}nav #nav-check:not(:checked)~ul{height:auto;max-height:0}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(1){top:8px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(2){top:23px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(3){top:38px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:checked~.nav-btn label,nav .nav-btn label:hover{background-color:rgba(-1,0,0,.3)}nav #nav-check:checked~ul{max-height:50vh;overflow-y:hidden}nav #nav-check:checked~ul li a{opacity:1;transform:translateX(0)}nav #nav-check:checked~ul li:nth-child(1) a{transition-delay:.15s}nav #nav-check:checked~ul li:nth-child(2) a{transition-delay:.25s}nav #nav-check:checked~ul li:nth-child(3) a{transition-delay:.35s}nav #nav-check:checked~ul li:nth-child(4) a{transition-delay:.45s}nav #nav-check:checked~ul li:nth-child(5) a{transition-delay:.55s}nav #nav-check:checked~.nav-btn label span:first-child{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}nav #nav-check:checked~.nav-btn label span:nth-child(2){width:0;opacity:0}nav #nav-check:checked~.nav-btn label span:last-child{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}}footer{background-color:var(--primaryDefault);margin-top:5em;padding:2em;display:flex;color:#fff}footer .spacer{width:100%;margin-right:auto}footer .nav{width:100%;margin-right:auto;display:flex;flex-direction:row;justify-content:center;align-items:center}footer .nav ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;justify-content:space-around}footer .nav ul a{color:#fff}footer p{margin:auto;width:100%;text-align:center}footer .button{margin-left:auto;width:100%;text-align:center}footer .button button{border:5px solid #fff;background:0 0;font-size:3em;padding:.5rem 1rem;width:2em;color:#fff;-webkit-border-radius:.25em;-moz-border-radius:.25em;border-radius:.25em;cursor:pointer}.profile{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;max-width:70%}svg{width:2em;fill:var(--primaryDefault);font-size:2em}footer{margin-top:0}section#individualPost{display:flex;flex-direction:row;justify-content:flex-start;align-items:stretch}div.byLine{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;gap:1em}div.byLine h3:last-child{border-left:2px solid var(--mutedBlack);padding-left:1em;display:flex;flex-direction:row;justify-content:center;align-items:center;gap:1em}div.byLine h3:last-child a{padding:0 1em}div.cover{width:100%;height:20rem;background-position:center;background-size:cover;border-radius:10px;box-shadow:0 4px 2px 0 var(--mutedBlack)}div.mainContent{border-right:5px solid var(--mutedGrey);min-height:100%;width:85%;display:flex;flex-direction:column;justify-content:space-between;align-items:stretch}article{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 2em}article a{padding:0 1em}article a::after,article a::before{visibility:hidden;position:absolute;margin-top:1px}article a::before{content:' <';margin-left:-.5em}article a::after{content:'>'}article a:hover::after,article a:hover::before{visibility:visible}article h1{margin-bottom:.5em}article h3{margin-top:0}aside.sideContent{display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-end;width:15%;align-self:flex-start}div.authorInfo{display:grid;grid-template-columns:2fr 1fr;grid-template-rows:repeat(4,auto);padding-left:1em;padding-top:.5em;border-bottom:5px solid var(--mutedGrey);width:100%}div.authorInfo .picture{display:flex;flex-direction:column;justify-content:center;align-items:center;grid-row:span 3}div.authorInfo h3{grid-column:span 2}div.feeds,div.newsletter,div.otherPosts{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 1em 1em;border-bottom:5px solid var(--mutedGrey);width:100%}div.feeds a,div.otherPosts a{padding:.5em 1em}div.newsletter form input[type=submit]{margin-top:1em;padding:.5em 1em}div.feeds .icons{display:flex;flex-direction:row;justify-content:space-around;align-items:flex-start;gap:.5em;flex-wrap:wrap-reverse}div.feeds h2{margin-bottom:0}div.feeds i.fa-solid.fa-rss{font-size:2em}div.feeds img.atom,div.feeds img.json{width:2em}div.categories{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 1em 1em;width:100%}.image img,.image_resized img{max-width:100%;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;box-shadow:0 4px 2px 0 var(--mutedBlack)}.image{justify-self:center;align-self:center}.image-style-side{justify-self:flex-end;align-self:flex-end}section.comments{padding:0 2em 2em}.banner{max-width:30%;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:.625rem;-moz-border-radius:.625rem;border-radius:.625rem;border:2px solid var(--mutedGrey)}h2{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--headingFS);line-height:2.5625rem;text-transform:lowercase}h3{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:2.1875rem}div.menu{width:100%;border-bottom:5px solid var(--mutedGrey)}div.menu input:not([type=submit]){width:auto;border-top-right-radius:0;border-bottom-right-radius:0}div.menu>ul{list-style:none;display:flex;flex-direction:row;justify-content:space-around}div.menu ul li{display:flex;flex-direction:row}div.menu ul li button.btn{padding:initial;border-radius:0 .5em .5em 0}div.menu ul li input:not([type=submit]):focus+button.btn,div.menu ul li:focus button.btn,div.menu ul li:hover button.btn{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}div.menu ul li:focus input:not([type=submit]),div.menu ul li:hover input:not([type=submit]){border:.3215em solid var(--primaryHover)}section.largePost{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start;gap:2em;width:100%;padding:0 5em 1em}section.largePost:not(:last-child){border-bottom:5px solid var(--mutedGrey)}section.largePost .outerContent{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-around;align-items:center;gap:1em}section.largePost .outerContent>.content,section.largePost .outerContent>img{width:50%}section.largePost .outerContent .postContent{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start}section.largePost .outerContent .postContent h2{align-self:center}section.largePost .outerContent .postContent a{align-self:flex-end}#main .error{display:table;width:100%;height:100vh;text-align:center}.fof{display:table-cell;vertical-align:middle}main>h1{padding-left:3em}section.catPosts .largePost{margin-bottom:3em}section.categories{display:flex;flex-direction:row;justify-content:center;align-items:center;flex-wrap:wrap;width:100%;margin-bottom:5em}section.categories .btnContainer{flex-basis:33.3333333%;display:flex;flex-direction:row;justify-content:center;align-items:center;flex-wrap:wrap}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.token a{color:inherit}div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}.prism-previewer,.prism-previewer:after,.prism-previewer:before{position:absolute;pointer-events:none}.prism-previewer,.prism-previewer:after{left:50%}.prism-previewer{margin-top:-48px;width:32px;height:32px;margin-left:-16px;z-index:10;opacity:0;-webkit-transition:opacity .25s;-o-transition:opacity .25s;transition:opacity .25s}.prism-previewer.flipped{margin-top:0;margin-bottom:-48px}.prism-previewer:after,.prism-previewer:before{content:'';position:absolute;pointer-events:none}.prism-previewer:before{top:-5px;right:-5px;left:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer:after{top:100%;width:0;height:0;margin:5px 0 0 -7px;border:7px solid transparent;border-color:rgba(255,0,0,0);border-top-color:#fff}.prism-previewer.flipped:after{top:auto;bottom:100%;margin-top:0;margin-bottom:5px;border-top-color:rgba(255,0,0,0);border-bottom-color:#fff}.prism-previewer.active{opacity:1}.prism-previewer-angle:before{border-radius:50%;background:#fff}.prism-previewer-angle:after{margin-top:4px}.prism-previewer-angle svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-angle[data-negative] svg{-webkit-transform:scaleX(-1) rotate(-90deg);-moz-transform:scaleX(-1) rotate(-90deg);-ms-transform:scaleX(-1) rotate(-90deg);-o-transform:scaleX(-1) rotate(-90deg);transform:scaleX(-1) rotate(-90deg)}.prism-previewer-angle circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500}.prism-previewer-gradient{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px;width:64px;margin-left:-32px}.prism-previewer-gradient:before{content:none}.prism-previewer-gradient div{position:absolute;top:-5px;left:-5px;right:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer-color{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px}.prism-previewer-color:before{background-color:inherit;background-clip:padding-box}.prism-previewer-easing{margin-top:-76px;margin-left:-30px;width:60px;height:60px;background:#333}.prism-previewer-easing.flipped{margin-bottom:-116px}.prism-previewer-easing svg{width:60px;height:60px}.prism-previewer-easing circle{fill:#2d3438;stroke:#fff}.prism-previewer-easing path{fill:none;stroke:#fff;stroke-linecap:round;stroke-width:4}.prism-previewer-easing line{stroke:#fff;stroke-opacity:.5;stroke-width:2}@-webkit-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-o-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-moz-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}.prism-previewer-time:before{border-radius:50%;background:#fff}.prism-previewer-time:after{margin-top:4px}.prism-previewer-time svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-time circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500;stroke-dashoffset:0;-webkit-animation:prism-previewer-time linear infinite 3s;-moz-animation:prism-previewer-time linear infinite 3s;-o-animation:prism-previewer-time linear infinite 3s;animation:prism-previewer-time linear infinite 3s}.token.punctuation.brace-hover,.token.punctuation.brace-selected{outline:solid 1px}.rainbow-braces .token.punctuation.brace-level-1,.rainbow-braces .token.punctuation.brace-level-5,.rainbow-braces .token.punctuation.brace-level-9{color:#e50;opacity:1}.rainbow-braces .token.punctuation.brace-level-10,.rainbow-braces .token.punctuation.brace-level-2,.rainbow-braces .token.punctuation.brace-level-6{color:#0b3;opacity:1}.rainbow-braces .token.punctuation.brace-level-11,.rainbow-braces .token.punctuation.brace-level-3,.rainbow-braces .token.punctuation.brace-level-7{color:#26f;opacity:1}.rainbow-braces .token.punctuation.brace-level-12,.rainbow-braces .token.punctuation.brace-level-4,.rainbow-braces .token.punctuation.brace-level-8{color:#e0e;opacity:1}pre.diff-highlight>code .token.deleted:not(.prefix),pre>code.diff-highlight .token.deleted:not(.prefix){background-color:rgba(255,0,0,.1);color:inherit;display:block}pre.diff-highlight>code .token.inserted:not(.prefix),pre>code.diff-highlight .token.inserted:not(.prefix){background-color:rgba(0,255,128,.1);color:inherit;display:block}.policy{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;padding-left:5em;padding-bottom:5em}.policy h3{font-weight:700}@media screen and (max-width:90em){div.mainContent{width:80%}aside.sideContent{width:20%}}@media screen and (max-width:75em){div.menuBar .menu ul{flex-direction:column;align-items:center;padding-left:1.25em}div.menuBar .menu ul li input[type=search]{width:100%}section.largePost{padding:1em}section.largePost .outerContent{flex-direction:column;justify-content:center;align-items:center}.banner{max-width:50%}section.largePost .outerContent>.content,section.largePost .outerContent>img{width:90%}section#individualPost{flex-direction:column-reverse;justify-content:center;align-items:center}div.mainContent{width:100%;border-right:none}aside.sideContent{width:100%;flex-direction:row;align-items:stretch;justify-content:space-around;border-bottom:5px solid var(--mutedGrey);flex-wrap:wrap}aside.sideContent>div.authorInfo{grid-template-columns:4fr 1fr 1fr 1fr;flex-basis:100%}aside.sideContent>div.authorInfo .picture{grid-row:span 3}div.byLine{flex-direction:column;align-items:flex-start}div.byLine h3:last-child{border-left:none;padding-left:0}aside.sideContent>div.authorInfo h3{grid-column-start:2;grid-column-end:end;grid-row:3}aside.sideContent>div.categories{flex-basis:100%}aside.sideContent>div.otherPosts{border-bottom:5px solid var(--mutedGrey)}div.feeds,div.newsletter,div.otherPosts{justify-content:space-between;width:50%}aside.sideContent>div{border-right:5px solid var(--mutedGrey);flex-basis:50%}aside.sideContent>div:last-child{border-right:none}main>h1{padding-left:1em}}@media screen and (max-width:55em){.banner{max-width:75%}aside.sideContent>div.authorInfo{grid-template-columns:2fr 1fr 1fr;grid-template-rows:1fr 1fr}aside.sideContent>div.authorInfo h3{grid-row:2}}@media screen and (max-width:30em){.profile{max-width:50%}section#individualPost{flex-direction:column}aside.sideContent{flex-direction:column;align-items:center;border-bottom:none}aside.sideContent>div.authorInfo{grid-template-columns:1fr 1fr 1fr;grid-template-rows:repeat(3,auto);border-top:5px solid var(--mutedGrey)}aside.sideContent>div.authorInfo .picture{grid-row:1;grid-column:span 3}aside.sideContent>div.authorInfo h3{grid-column:span 3}div.feeds,div.newsletter,div.otherPosts{width:100%}} \ No newline at end of file +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}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}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-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}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 75%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 1);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,button.btn,div.form input[type=submit],form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}button.btn{padding:1.2em 2.2em}div.form input[type=submit],form input[type=submit]{padding:1.1em 2em}a.btn:hover,button.btn:hover,div.form input[type=submit]:hover,form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btn:hover::after,a.btn:hover::before{visibility:hidden}a.btnPrimary,button.btnPrimary,div.form input[type=submit],form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline,button.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled],button.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover,button.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,button.btnPrimary:hover,div.form input[type=submit]:hover,form input[type=submit]:hover{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}a.btn:active,button.btn:active,div.form input[type=submit]:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}div.form .formControl input:not([type=submit]).invalid:invalid,form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}div.form .formControl input:not([type=submit]).invalid:invalid:focus,div.form .formControl textarea.invalid:invalid:focus,form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.form .formControl input:not([type=submit]),form .formControl input:not([type=submit]){height:3em}div.form .formControl,form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}div.form .formControl.passwordControl,form .formControl.passwordControl{display:block}div.form input[type=submit],form input[type=submit]{align-self:flex-start}div.form .formControl .ck.ck-editor__main .ck-content,div.form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,div.form .formControl input:not([type=submit]),div.menu input:not([type=submit]),form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:.3125em solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:.5em;-moz-border-radius:.5em;border-radius:.5em;padding:0 .5em}div.form .formControl textarea,form .formControl textarea{padding:.5em}div.form .formControl input:not([type=submit]).invalid:invalid,form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}div.form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.form .formControl input:not([type=submit]):focus,div.form .formControl input:not([type=submit]):hover,div.menu input:not([type=submit]):focus,div.menu input:not([type=submit]):hover,form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:.3125em solid var(--primaryHover)}div.form .formControl input:not([type=submit]),form .formControl input:not([type=submit]){height:3em}div.form .formControl i.fa-eye,form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}div.form .formControl .checkContainer,form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.form .formControl .checkContainer input,form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}div.form .formControl .checkContainer .checkmark,form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}div.form .formControl .checkContainer:hover input~.checkmark,form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}div.form .formControl .checkContainer input:checked~.checkmark,form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}div.form .formControl .checkContainer input:checked:hover~.checkmark,form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}div.form .formControl .checkContainer .checkmark:after,form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}div.form .formControl .checkContainer input:checked~.checkmark:after,form .formControl .checkContainer input:checked~.checkmark:after{display:block}div.form .formControl .checkContainer .checkmark:after,form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}a{color:#000;text-decoration:none;text-transform:lowercase}a.link{padding:0 .5em}a.link::after,a.link::before{visibility:hidden;position:absolute;margin-top:1px}a.link::before{content:' <';margin-left:-.5em}a.link::after{content:'> '}a.link:hover::after,a.link:hover::before{visibility:visible}div.error,div.success{color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em}div.error{background:var(--errorDefault)}div.success{background-color:var(--primaryHover)}div.error button,div.success button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.error.hidden,div.success.hidden{opacity:0;visibility:hidden;height:0;margin:0;padding:0}div.error button:hover,div.success button:hover{text-shadow:-1px 2px var(--mutedBlack)}header{background:#6a6a6a url(../../imgs/hero.jpg) no-repeat bottom;background-size:cover;height:40%;color:#fff;backdrop-filter:grayscale(100%);position:relative}nav{display:flex;flex-direction:row;justify-content:space-between;padding:.25em;position:fixed;top:0;width:100%;transition:background-color .4s ease-in;color:#fff;z-index:100000000000000000000000000}nav.scrolled{background-color:var(--navBack)}nav #nav-check{display:none}nav .nav-btn{display:none}nav h1{margin:0}nav a{text-decoration:none;color:#fff}nav>h1{margin-left:.4em}nav ul{display:flex;flex-direction:row;gap:1em;margin:0 .5em 0 0;justify-content:flex-end;align-items:flex-end}nav ul li{list-style:none}nav ul li span{visibility:hidden}nav ul li .active::after,nav ul li .active::before{visibility:visible}header div{display:flex;flex-direction:column;justify-content:center;align-items:center;padding-top:10em}header div .btn{margin:2em 0}header div button{background:0 0;border:none;display:inline-block;text-align:center;text-decoration:none;font-size:2rem;cursor:pointer}i.fa-chevron-down{color:hsla(0,0%,67%,.58);font-size:3.75em;margin:1.5rem 0}div h1 span{visibility:visible;animation:caret 1s steps(1) infinite}@keyframes caret{50%{visibility:hidden}}@media screen and (max-width:75em){nav{display:block;height:50px;width:100%;background-color:var(--navBack);position:fixed;top:0;padding:0}nav a h1{margin-left:1ch}nav .nav-btn{display:inline-block;position:absolute;right:75px;top:0}nav ul{position:fixed;display:block;width:100%;background-color:#333;transition:all .4s ease-in;overflow-y:hidden;padding-left:.5em;margin-top:7px}nav ul li a{display:block;width:100%;transform:translateX(-30px);transition:all .4s ease-in;opacity:0}.nav-btn label{display:inline-block;cursor:pointer;width:60px;height:50px;position:fixed;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.5s ease-in;-moz-transition:.5s ease-in;-o-transition:.5s ease-in;transition:.5s ease-in}.nav-btn label span{display:block;position:absolute;height:5px;width:100%;background-color:#fff;opacity:1;right:0;top:20px;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.25s ease-in;-moz-transition:.25s ease-in;-o-transition:.25s ease-in;transition:.25s ease-in}nav #nav-check:not(:checked)~ul{height:auto;max-height:0}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(1){top:8px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(2){top:23px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(3){top:38px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:checked~.nav-btn label,nav .nav-btn label:hover{background-color:rgba(-1,0,0,.3)}nav #nav-check:checked~ul{max-height:50vh;overflow-y:hidden}nav #nav-check:checked~ul li a{opacity:1;transform:translateX(0)}nav #nav-check:checked~ul li:nth-child(1) a{transition-delay:.15s}nav #nav-check:checked~ul li:nth-child(2) a{transition-delay:.25s}nav #nav-check:checked~ul li:nth-child(3) a{transition-delay:.35s}nav #nav-check:checked~ul li:nth-child(4) a{transition-delay:.45s}nav #nav-check:checked~ul li:nth-child(5) a{transition-delay:.55s}nav #nav-check:checked~.nav-btn label span:first-child{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}nav #nav-check:checked~.nav-btn label span:nth-child(2){width:0;opacity:0}nav #nav-check:checked~.nav-btn label span:last-child{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}}footer{background-color:var(--primaryDefault);margin-top:5em;padding:2em;display:flex;color:#fff}footer .spacer{width:100%;margin-right:auto}footer .nav{width:100%;margin-right:auto;display:flex;flex-direction:row;justify-content:center;align-items:center}footer .nav ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;justify-content:space-around}footer .nav ul a{color:#fff}footer p{margin:auto;width:100%;text-align:center}footer .button{margin-left:auto;width:100%;text-align:center}footer .button button{border:5px solid #fff;background:0 0;font-size:3em;padding:.5rem 1rem;width:2em;color:#fff;-webkit-border-radius:.25em;-moz-border-radius:.25em;border-radius:.25em;cursor:pointer}.profile{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;max-width:70%}svg{width:2em;fill:var(--primaryDefault);font-size:2em}footer{margin-top:0}section#individualPost{display:flex;flex-direction:row;justify-content:flex-start;align-items:stretch}div.byLine{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;gap:1em}div.byLine h3:last-child{border-left:2px solid var(--mutedBlack);padding-left:1em;display:flex;flex-direction:row;justify-content:center;align-items:center;gap:1em}div.byLine h3:last-child a{padding:0 1em}div.cover{width:100%;height:20rem;background-position:center;background-size:cover;border-radius:10px;box-shadow:0 4px 2px 0 var(--mutedBlack)}div.mainContent{border-right:5px solid var(--mutedGrey);min-height:100%;width:85%;display:flex;flex-direction:column;justify-content:space-between;align-items:stretch}article{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 2em}article a{padding:0 1em}article a::after,article a::before{visibility:hidden;position:absolute;margin-top:1px}article a::before{content:' <';margin-left:-.5em}article a::after{content:'>'}article a:hover::after,article a:hover::before{visibility:visible}article h1{margin-bottom:.5em}article h3{margin-top:0}aside.sideContent{display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-end;width:15%;align-self:flex-start}div.authorInfo{display:grid;grid-template-columns:2fr 1fr;grid-template-rows:repeat(4,auto);padding-left:1em;padding-top:.5em;border-bottom:5px solid var(--mutedGrey);width:100%}div.authorInfo .picture{display:flex;flex-direction:column;justify-content:center;align-items:center;grid-row:span 3}div.authorInfo h3{grid-column:span 2}div.feeds,div.newsletter,div.otherPosts{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 1em 1em;border-bottom:5px solid var(--mutedGrey);width:100%}div.feeds a,div.otherPosts a{padding:.5em 1em}div.newsletter div.form input[type=submit]{margin-top:1em;padding:.5em 1em}div.feeds .icons{display:flex;flex-direction:row;justify-content:space-around;align-items:flex-start;gap:.5em;flex-wrap:wrap-reverse}div.feeds h2{margin-bottom:0}div.feeds i.fa-solid.fa-rss{font-size:2em}div.feeds img.atom,div.feeds img.json{width:2em}div.categories{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 1em 1em;width:100%}.image img,.image_resized img{max-width:100%;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;box-shadow:0 4px 2px 0 var(--mutedBlack)}.image{justify-self:center;align-self:center}.image-style-side{justify-self:flex-end;align-self:flex-end}section.comments{padding:0 2em 2em}.banner{max-width:30%;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:.625rem;-moz-border-radius:.625rem;border-radius:.625rem;border:2px solid var(--mutedGrey)}h2{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--headingFS);line-height:2.5625rem;text-transform:lowercase}h3{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:2.1875rem}div.menu{width:100%;border-bottom:5px solid var(--mutedGrey)}div.menu input:not([type=submit]){width:auto;border-top-right-radius:0;border-bottom-right-radius:0}div.menu>ul{list-style:none;display:flex;flex-direction:row;justify-content:space-around}div.menu ul li{display:flex;flex-direction:row}div.menu ul li button.btn{padding:initial;border-radius:0 .5em .5em 0}div.menu ul li input:not([type=submit]):focus+button.btn,div.menu ul li:focus button.btn,div.menu ul li:hover button.btn{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}div.menu ul li:focus input:not([type=submit]),div.menu ul li:hover input:not([type=submit]){border:.3215em solid var(--primaryHover)}section.largePost{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start;gap:2em;width:100%;padding:0 5em 1em}section.largePost:not(:last-child){border-bottom:5px solid var(--mutedGrey)}section.largePost .outerContent{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-around;align-items:center;gap:1em}section.largePost .outerContent>.content,section.largePost .outerContent>img{width:50%}section.largePost .outerContent .postContent{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start}section.largePost .outerContent .postContent h2{align-self:center}section.largePost .outerContent .postContent a{align-self:flex-end}#main .errorFof{display:table;width:100%;height:100vh;text-align:center}.fof{display:table-cell;vertical-align:middle}main>h1{padding-left:3em}section.catPosts .largePost{margin-bottom:3em}section.categories{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;flex-wrap:wrap;width:100%;margin-bottom:5em;row-gap:1em}section.categories .btnContainer{flex-basis:33.3333333%;display:flex;flex-direction:row;justify-content:center;align-items:center;flex-wrap:wrap}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.token a{color:inherit}div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}.prism-previewer,.prism-previewer:after,.prism-previewer:before{position:absolute;pointer-events:none}.prism-previewer,.prism-previewer:after{left:50%}.prism-previewer{margin-top:-48px;width:32px;height:32px;margin-left:-16px;z-index:10;opacity:0;-webkit-transition:opacity .25s;-o-transition:opacity .25s;transition:opacity .25s}.prism-previewer.flipped{margin-top:0;margin-bottom:-48px}.prism-previewer:after,.prism-previewer:before{content:'';position:absolute;pointer-events:none}.prism-previewer:before{top:-5px;right:-5px;left:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer:after{top:100%;width:0;height:0;margin:5px 0 0 -7px;border:7px solid transparent;border-color:rgba(255,0,0,0);border-top-color:#fff}.prism-previewer.flipped:after{top:auto;bottom:100%;margin-top:0;margin-bottom:5px;border-top-color:rgba(255,0,0,0);border-bottom-color:#fff}.prism-previewer.active{opacity:1}.prism-previewer-angle:before{border-radius:50%;background:#fff}.prism-previewer-angle:after{margin-top:4px}.prism-previewer-angle svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-angle[data-negative] svg{-webkit-transform:scaleX(-1) rotate(-90deg);-moz-transform:scaleX(-1) rotate(-90deg);-ms-transform:scaleX(-1) rotate(-90deg);-o-transform:scaleX(-1) rotate(-90deg);transform:scaleX(-1) rotate(-90deg)}.prism-previewer-angle circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500}.prism-previewer-gradient{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px;width:64px;margin-left:-32px}.prism-previewer-gradient:before{content:none}.prism-previewer-gradient div{position:absolute;top:-5px;left:-5px;right:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer-color{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px}.prism-previewer-color:before{background-color:inherit;background-clip:padding-box}.prism-previewer-easing{margin-top:-76px;margin-left:-30px;width:60px;height:60px;background:#333}.prism-previewer-easing.flipped{margin-bottom:-116px}.prism-previewer-easing svg{width:60px;height:60px}.prism-previewer-easing circle{fill:#2d3438;stroke:#fff}.prism-previewer-easing path{fill:none;stroke:#fff;stroke-linecap:round;stroke-width:4}.prism-previewer-easing line{stroke:#fff;stroke-opacity:.5;stroke-width:2}@-webkit-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-o-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-moz-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}.prism-previewer-time:before{border-radius:50%;background:#fff}.prism-previewer-time:after{margin-top:4px}.prism-previewer-time svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-time circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500;stroke-dashoffset:0;-webkit-animation:prism-previewer-time linear infinite 3s;-moz-animation:prism-previewer-time linear infinite 3s;-o-animation:prism-previewer-time linear infinite 3s;animation:prism-previewer-time linear infinite 3s}.token.punctuation.brace-hover,.token.punctuation.brace-selected{outline:solid 1px}.rainbow-braces .token.punctuation.brace-level-1,.rainbow-braces .token.punctuation.brace-level-5,.rainbow-braces .token.punctuation.brace-level-9{color:#e50;opacity:1}.rainbow-braces .token.punctuation.brace-level-10,.rainbow-braces .token.punctuation.brace-level-2,.rainbow-braces .token.punctuation.brace-level-6{color:#0b3;opacity:1}.rainbow-braces .token.punctuation.brace-level-11,.rainbow-braces .token.punctuation.brace-level-3,.rainbow-braces .token.punctuation.brace-level-7{color:#26f;opacity:1}.rainbow-braces .token.punctuation.brace-level-12,.rainbow-braces .token.punctuation.brace-level-4,.rainbow-braces .token.punctuation.brace-level-8{color:#e0e;opacity:1}pre.diff-highlight>code .token.deleted:not(.prefix),pre>code.diff-highlight .token.deleted:not(.prefix){background-color:rgba(255,0,0,.1);color:inherit;display:block}pre.diff-highlight>code .token.inserted:not(.prefix),pre>code.diff-highlight .token.inserted:not(.prefix){background-color:rgba(0,255,128,.1);color:inherit;display:block}.policy{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;padding-left:5em;padding-bottom:5em}.policy h3{font-weight:700}.modal-container{display:block;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.4);box-sizing:border-box}.modal-container.hidden{display:none}.modal{position:fixed;right:0;bottom:0;width:40%;max-width:550px;height:auto;max-height:70vh;overflow:auto;margin:1.25em;padding:1.25em;box-sizing:border-box}.modal-content{background-color:#ddd;margin:auto;padding:20px;border:5px solid var(--primaryHover);width:100%;box-shadow:0 6px 4px 0 var(--mutedBlack)}@media screen and (max-width:90em){div.mainContent{width:80%}aside.sideContent{width:20%}}@media screen and (max-width:75em){div.menuBar .menu ul{flex-direction:column;align-items:center;padding-left:1.25em}div.menuBar .menu ul li input[type=search]{width:100%}section.largePost{padding:1em}section.largePost .outerContent{flex-direction:column;justify-content:center;align-items:center}.banner{max-width:50%}section.largePost .outerContent>.content,section.largePost .outerContent>img{width:90%}section#individualPost{flex-direction:column-reverse;justify-content:center;align-items:center}div.mainContent{width:100%;border-right:none}aside.sideContent{width:100%;flex-direction:row;align-items:stretch;justify-content:space-around;border-bottom:5px solid var(--mutedGrey);flex-wrap:wrap}aside.sideContent>div.authorInfo{grid-template-columns:4fr 1fr 1fr 1fr;flex-basis:100%}aside.sideContent>div.authorInfo .picture{grid-row:span 3}div.byLine{flex-direction:column;align-items:flex-start}div.byLine h3:last-child{border-left:none;padding-left:0}aside.sideContent>div.authorInfo h3{grid-column-start:2;grid-column-end:end;grid-row:3}aside.sideContent>div.categories{flex-basis:100%}aside.sideContent>div.otherPosts{border-bottom:5px solid var(--mutedGrey)}div.feeds,div.newsletter,div.otherPosts{justify-content:space-between;width:50%}aside.sideContent>div{border-right:5px solid var(--mutedGrey);flex-basis:50%}aside.sideContent>div:last-child{border-right:none}main>h1{padding-left:1em}}@media screen and (max-width:55em){.banner{max-width:75%}aside.sideContent>div.authorInfo{grid-template-columns:2fr 1fr 1fr;grid-template-rows:1fr 1fr}aside.sideContent>div.authorInfo h3{grid-row:2}}@media screen and (max-width:30em){.profile{max-width:50%}section#individualPost{flex-direction:column}aside.sideContent{flex-direction:column;align-items:center;border-bottom:none}aside.sideContent>div.authorInfo{grid-template-columns:1fr 1fr 1fr;grid-template-rows:repeat(3,auto);border-top:5px solid var(--mutedGrey)}aside.sideContent>div.authorInfo .picture{grid-row:1;grid-column:span 3}aside.sideContent>div.authorInfo h3{grid-column:span 3}div.feeds,div.newsletter,div.otherPosts{width:100%}} \ No newline at end of file diff --git a/dist/blog/index.html b/dist/blog/index.html index 104d134..e5bb6c6 100644 --- a/dist/blog/index.html +++ b/dist/blog/index.html @@ -1 +1 @@ -Rohit Pai - Blog

full stack developer

Contact Me
\ No newline at end of file +Rohit Pai - Blog

full stack developer

Contact Me
\ No newline at end of file diff --git a/dist/blog/js/index.js b/dist/blog/js/index.js index b4ff6cb..cda2cc6 100644 --- a/dist/blog/js/index.js +++ b/dist/blog/js/index.js @@ -1 +1 @@ -const scrollLimit=150;function goToURL(t){let e=t.split("/");if("/blog/"!==t&&"/blog"!==t)if("post"!==e[2]){if("category"===e[2])return e[3]?void loadPostsByCategory(e[e.length-1]):void loadAllCategories().catch((t=>console.log(t)));if("search"===e[2]&&e[3])loadSearchResults(e[e.length-1]);else{if("policy"===e[2]){if("privacy"===e[3])return void loadPrivacyPolicy();if("cookie"===e[3])return void loadCookiePolicy()}show404()}}else loadIndividualPost(e[e.length-1]).catch((t=>console.log(t)));else loadHomeContent()}function createFormattedDate(t){return new Date(t).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"})}function createLargePost(t){let e=document.createElement("div");e.classList.add("outerContent");let n=document.createElement("img");n.className="banner",n.src=t.headerImg,n.alt=t.title,e.appendChild(n);let o=document.createElement("div");o.classList.add("content");let a=document.createElement("div");a.classList.add("postContent");let i="";t.categories.split(", ").forEach((e=>{i+=`${e}`,t.categories.split(", ").length>1&&(i+=", ")})),i.endsWith(", ")&&(i=i.substring(0,i.length-2));let r=createFormattedDate(t.dateModified);return a.innerHTML=`\n

${t.title}

\n

Last updated: ${r} | ${i}

\n

${t.abstract}

\n See Post\n `,o.appendChild(a),e.appendChild(o),e}function loadHomeContent(){fetch("/api/blog/post").then((t=>t.json().then((t=>{for(let e=0;et.json())),await fetch("/api/blog/post/featured").then((t=>t.json()))]}function csvToArray(t){let e="",n=[""],o=0,a=!0,i=null;for(i of t)'"'===i?(a&&i===e&&(n[o]+=i),a=!a):","===i&&a?i=n[++o]="":"\n"===i&&a?("\r"===e&&(row[o]=row[o].slice(0,-1)),n=n[++r]=[i=""],o=0):n[o]+=i,e=i;return n}async function getCategories(){let t=await fetch("/api/blog/categories").then((t=>t.json())),e=[];return t.forEach((t=>e=e.concat(csvToArray(t.categories.replace(/\s*,\s*/g,","))))),[...new Set(e)]}function createCategories(t){let e="";return t.forEach((t=>e+=`${t}`)),e}function createButtonCategories(t){let e="";return t.forEach((t=>t.forEach((t=>e+=`${t}`)))),e}async function createSideContent(){let t=await getLatestAndFeaturedPosts(),e=t[0],n=t[1],o=createCategories(await getCategories()),a=document.createElement("aside");return a.classList.add("sideContent"),a.innerHTML=`\n
\n
\n My professional picture taken in brighton near \n                                        north street at night wearing a beige jacket and checkered shirt\n

Rohit Pai

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n

Avid Full Stack Dev | Uni of Notts Grad | Amateur Blogger

\n
\n \n \n \n
\n\t\t\t\t\t\t\t\t

feeds

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tAtom\n\t\t\t\t\t\t\t\t\tJSON\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n
\n

categories

\n ${o}\n
\n \n `,a}function createMetaTag(t,e,n){let o=document.querySelector(`meta[name="${t}"], meta[property="${t}"]`);if(!o){let o=document.createElement("meta");return o.setAttribute(t.includes("name")?"name":"property",t),o.setAttribute(e,n),void document.head.appendChild(o)}o.setAttribute(e,n)}function insertMetaTags(t){document.querySelector('meta[name="description"]').setAttribute("content",t.abstract),createMetaTag("twitter:title","content",t.title),createMetaTag("twitter:description","content",t.abstract),createMetaTag("twitter:image","content",t.headerImg),createMetaTag("og:title","content",t.title),createMetaTag("og:description","content",t.abstract),createMetaTag("og:image","content",t.headerImg),createMetaTag("og:url","content",window.location.href),createMetaTag("og:type","content","blog"),createMetaTag("og:site_name","content","Rohit Pai's Blog"),createMetaTag("og:locale","content","en_GB");let e=document.querySelector('meta[name="keywords"]'),n=e.getAttribute("content");n+=`, ${t.keywords}`,e.setAttribute("content",n)}async function loadIndividualPost(t){document.title="Rohit Pai - "+decodeURI(t),await fetch(`/api/blog/post/${t}`).then((async e=>{e.ok?await e.json().then((async e=>{insertMetaTags(e);let n=document.createElement("section");n.classList.add("post"),n.id="individualPost";let o=document.createElement("div");o.classList.add("mainContent");let a=document.createElement("article");a.innerHTML=`\n

${e.title}

\n \n
\n ${e.body}\n `;let i=document.createElement("section");i.classList.add("comments"),i.innerHTML='

Comments

\n
\n ',o.appendChild(a),o.appendChild(i);let r=await createSideContent();n.appendChild(o),n.appendChild(r),document.querySelector("#main").appendChild(n);var s,c;s=document,(c=s.createElement("script")).src="https://rohitpaiportfolio.disqus.com/embed.js",c.setAttribute("data-timestamp",+new Date),s.body.appendChild(c)})):show404()}))}async function loadAllCategories(){document.title="Rohit Pai - Categories";let t=await getCategories(),e=document.querySelector("#main"),n=document.createElement("section");n.classList.add("categories"),n.id="allCategories";let o=document.createElement("h1");o.innerHTML="Categories",e.appendChild(o);for(let e of t){let t=document.createElement("div");t.classList.add("btnContainer");let o=document.createElement("a");o.classList.add("btn"),o.classList.add("btnPrimary"),o.innerHTML=e,o.href=`/blog/category/${e}`,t.appendChild(o),n.appendChild(t)}e.appendChild(n)}function loadPostsByCategory(t){document.title="Rohit Pai - "+decodeURI(t),fetch(`/api/blog/categories/${t}`).then((e=>e.json().then((e=>{let n=document.querySelector("#main"),o=document.createElement("section");o.classList.add("catPosts"),o.id="postsByCategory";let a=document.createElement("h1");a.innerHTML=decodeURI(t),n.appendChild(a);for(let t=0;tt.json().then((t=>{let e=document.querySelector("#main"),n=document.createElement("section");n.classList.add("catPosts"),n.id="searchResults";let o=document.createElement("h1");o.innerHTML="Search Results",e.appendChild(o);for(let e=0;e\n\t\t\t
\n\n\t\t\t

Sources of Information and Tracking Technologies

\n\t\t\t

\n\t\t\t\tI collect information that you directly provide to me, such as when you submit a form or send me a message with your information. I may also receive information about you from my partners, including but not limited to distribution partners, data services, and marketing firms. I may combine this information with other information I collect from or about you. In these cases, my Privacy Policy governs the handling of the combined information. I and my partners may collect the information noted in this privacy policy using cookies, web beacons, pixels, and other similar technologies. These technologies are used for authentication, to store your preferences or progress, for analytics, and for advertising and analytics. Cookies are small text files stored on your computer. You can set your browser to reject cookies altogether, to reject my cookies in particular, or to delete cookies. However, this may cause some or all of my Services not to function on your computer or device.\n\t\t\t

\n\t\t\t
\n\t\t\t

How I Use Your Information

\n\t\t\t

\n\t\t\t\tI use information I collect about you to provide, maintain, and improve my Services and other interactions I have with you. For example, I use the information collected to:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • Facilitate and improve your online experience;
  • \n\t\t\t\t
  • Provide and deliver products and services, perform authentication, process transactions and returns, and send you related information, including confirmations, receipts, invoices, customer experience surveys, and product or Services-related notices;
  • \n\t\t\t\t
  • Process and deliver promotions;
  • \n\t\t\t\t
  • Respond to your comments and questions and provide customer service;
  • \n\t\t\t\t
  • If you have indicated to me that you wish to receive notifications or promotional messages;
  • \n\t\t\t\t
  • Detect, investigate and prevent fraudulent transactions and other illegal activities and protect my rights and property and others;
  • \n\t\t\t\t
  • Comply with my legal and financial obligations;
  • \n\t\t\t\t
  • Monitor and analyze trends, usage, and activities;
  • \n\t\t\t\t
  • Provide and allow my partners to provide advertising and marketing targeted toward your interests.
  • \n\t\t\t
\n\t\t\t
\n\t\t\t

How I May Share Information

\n\t\t\t

\n\t\t\t\tI may share your Personal Information in the following situations:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • \n\t\t\t\t\tThird Party Services Providers.\n\t\t\t\t\tI may share data with service providers, vendors, contractors, or agents who complete transactions or perform services on my behalf, such as those that assist me with my business and internal operations like shipping and delivery, payment processing, fraud prevention, customer service, gift cards, experiences, personalization, marketing, and advertising;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tChange in Business.\n\t\t\t\t\tI may share data in connection with a corporate business transaction, such as a merger or acquisition of all or a portion of my business to another company, joint venture, corporate reorganization, insolvency or bankruptcy, financing or sale of company assets;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tTo Comply with Law.\n\t\t\t\t\tI may share data to facilitate legal process from lawful requests by public authorities, including to meet national security or law enforcement demands as permitted by law.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tWith Your Consent.\n\t\t\t\t\tI may share data with third parties when I have your consent.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tWith Advertising and Analytics Partners.\n\t\t\t\t\tSee the section entitled “Advertising and Analytics” below.\n\t\t\t\t
  • \n\t\t\t
\n\t\t\t
\n\t\t\t

Advertising and Analytics

\n\t\t\t

\n\t\t\t\tI use advertising and analytics technologies to better understand your online activity on my Services to provide personalized products and services that may interest you. I may allow third-party companies, including ad networks, to serve advertisements, provide other advertising services, and/or collect certain information when you visit my website. Third-party companies may use pseudonymized personal data (e.g., click stream information, browser type, time and date, subject of advertisements clicked or scrolled over) during your visit to this website in order to provide advertisements about goods and services likely to be of interest to you, on this website and others. To learn more about Interest-Based Advertising or to opt-out of this type of advertising, you can visit AboutAds.info/choices or www.networkadvertising.org/choices. Some third-party companies may also use non-cookie technologies, such as statistical IDs. Please keep in mind that your web browser may not permit you to block the use of these non-cookie technologies, and those browser settings that block cookies may have no effect on such techniques. If the third-party company uses the non-cookie technologies for interest-based advertising, you can opt out at www.networkadvertising.org/choices. Please note the industry opt out only applies to use for interest-based advertising and may not apply to use for analytics or attribution. Some websites have “do not track” features that allow you to tell a website not to track you. These features are not all uniform. I do not currently respond to those signals.\n\t\t\t

\n\t\t\t
\n\t\t\t

Data Security

\n\t\t\t

\n\t\t\t\tI implement commercially reasonable security measures designed to protect your information. Despite my best efforts, however, no security measures are completely impenetrable.\n\t\t\t

\n\t\t\t
\n\t\t\t

Data Retention

\n\t\t\t

\n\t\t\t\tI store the information I collect about you for as long as necessary for the purpose(s) for which I collected it or for other legitimate business purposes, including to meet my legal, regulatory, or other compliance obligations.\n\t\t\t

\n\t\t\t
\n\n\t\t\t

EU Privacy Rights

\n\n\t\t\t

Individuals located in certain countries, including the European Economic Area (EEA) and the United Kingdom, have certain statutory rights under the General Data Protection Regulation (GDPR) in relation to their personal data.

\n\t\t\t

To the extent information I collect is associated with an identified or identifiable natural person and is protected as personal data under GDPR, it is referred to in this Privacy Policy as “Personal Data”.

\n\t\t\t

\n\t\t\t\tData Subject Access Requests\n\t\t\t

\n\t\t\t

\n\t\t\t\tSubject to any exemptions provided by law, you may have the right to request:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • \n\t\t\t\t\ta copy of the Personal Data I hold about you;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tto correct the Personal Data I hold about you;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tto delete your Account or Personal Data;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tto object to processing of your Personal Data for certain purposes;\n\t\t\t\t
  • \n\t\t\t
\n\n\t\t\t

\n\t\t\t\tTo access your privacy rights, send me an email at rohit@rohitpai.co.uk.\n\t\t\t

\n\t\t\t

\n\t\t\t\tI will generally process requests within one month. I may need to request specific information from you to help me confirm your identity and/or the jurisdiction in which you reside. If your request is complicated or if you have made a large number of requests, it may take me longer. I will let you know if I need longer than one month to respond.\n\t\t\t

\n\t\t\t

\n\t\t\t\tLegal Bases For Processing Personal Data\n\t\t\t

\n\t\t\t

\n\t\t\t\tI may process your Personal Data under applicable data protection law on the following legal grounds:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • \n\t\t\t\t\tContractual Necessity:\n\t\t\t\t\tI may process your Personal Data to enter into or perform a contract with you.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tConsent:\n\t\t\t\t\twhere you have provided consent to process your Personal Data. You may withdraw your consent at any time.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tLegitimate interest:\n\t\t\t\t\tI process your Personal Data to provide my Services to you such as to provide my online user experience, communicate with you, provide customer service, market, analyze and improve my business, and to protect my Services.\n\t\t\t\t
  • \n\t\t\t
\n\t\t\t
\n\t\t\t

Age Limitations

\n\t\t\t

\n\t\t\t\tMy Service is intended for adults ages 18 years and above. I do not knowingly collect personally identifiable information from children. If you are a parent or legal guardian and think your child under 13 has given me information, please email or write to me at the address listed at the end of this Privacy Policy. Please mark your inquiries “COPPA Information Request.”\n\t\t\t

\n\t\t\t
\n\t\t\t

Changes to this Privacy Policy

\n\t\t\t

\n\t\t\t\tRohit Pai may change this Privacy Policy from time to time. I encourage you to visit this page to stay informed. If the changes are material, I may provide you additional notice to your email address or through my Services. Your continued use of the Services indicates your acceptance of the modified Privacy Policy.\n\t\t\t

\n\t\t\t
\n\t\t\t

Newsletters

\n\t\t\t

\n\t\t\t\tYou can opt in to receive my marketing emails and/or newsletters by below. I may still send you transactional messages, which include Services-related communications and responses to your questions.\n\t\t\t

\n\t\t\t
\n\t\t\t

Storage of Information in the United States

\n\t\t\t

\n\t\t\t\tInformation I maintain may be stored both within and outside of the United States. If you live outside of the United States, you understand and agree that I may transfer your information to the United States, and that U.S. laws may not afford the same level of protection as those in your country.\n\t\t\t

\n\t\t\t
\n\t\t\t

Contact Me

\n\t\t\t

\n\t\t\t\tIf you have questions, comments, or concerns about this Privacy Policy, you may contact me at:\n\t\t\t

\n\t\t\tContact\n\t\t\trohit@rohitpai.co.uk\n\t\t\n\t\t'}function loadCookiePolicy(){document.querySelector("#main").innerHTML='\n\t\t
\n\t\t\t

Cookies Policy

\n\t\t\t

I only use functional cookies for the blog which includes PHP Session ID, disqus and maybe share this. I think that these are functional cookies, if you don\'t, you\'re welcome to exit the site or tell me by emailing me through the email address below, or the contact form on the contact page.

\n\t\t\t
\n\t\t\trohit@rohitpai.co.uk\n\t\t\t
\n\t\t\tcontact\n\t\t
\n\t'}function show404(){document.querySelector("#main").innerHTML='\n
\n
\n

Blog post, Category or page not found

\n See all blog posts\n
\n
\n '}document.addEventListener("DOMContentLoaded",(()=>{goToURL(window.location.pathname)})),window.addEventListener("popstate",(t=>{goToURL(window.history.state)})),window.onscroll=()=>{document.body.scrollTop>=150||document.documentElement.scrollTop>=150?document.querySelector("nav").classList.add("scrolled"):document.querySelector("nav").classList.remove("scrolled")},document.querySelector("#searchBtn").addEventListener("click",(t=>{let e=document.querySelector("#searchField").value;e.length>0&&(window.history.pushState(null,null,`/blog/search/${e}`),document.querySelector("#searchField").value="",document.querySelector("#main").innerHTML="",goToURL(`/blog/search/${e}`))})),document.querySelector("#searchField").addEventListener("keyup",(t=>{"Enter"===t.key&&document.querySelector("#searchBtn").click()})); \ No newline at end of file +const scrollLimit=150;function goToURL(t){let e=t.split("/");if("accepted"===localStorage.getItem("cookiePopup")&&document.querySelector("#cookiePopup").classList.add("hidden"),"/blog/"!==t&&"/blog"!==t)if("post"!==e[2]){if("category"===e[2])return e[3]?void loadPostsByCategory(e[e.length-1]):void loadAllCategories().catch((t=>console.log(t)));if("search"===e[2]&&e[3])loadSearchResults(e[e.length-1]);else{if("policy"===e[2]){if("privacy"===e[3])return void loadPrivacyPolicy();if("cookie"===e[3])return void loadCookiePolicy()}show404()}}else loadIndividualPost(e[e.length-1]).catch((t=>console.log(t)));else loadHomeContent()}function submitNewsletter(){fetch(`/api/blog/newsletter/${document.querySelector("#email").value}`,{method:"POST",headers:{"Content-Type":"application/json"}}).then((t=>t.json().then((e=>(document.querySelector("#newsletterMessage").classList.remove("hidden"),e.message.includes("exists")?(document.querySelector("#newsletterMessage").classList.add("error"),document.querySelector("#newsletterMessage").classList.remove("success"),void(document.querySelector("#newsletterMessage div").innerHTML="You've already signed up you silly goose!")):t.ok?(document.querySelector("#newsletterMessage div").innerHTML=e.message,void document.querySelector("#newsletterMessage").classList.add("success")):(document.querySelector("#newsletterMessage").classList.add("error"),document.querySelector("#newsletterMessage").classList.remove("success"),void(document.querySelector("#newsletterMessage div").innerHTML=e.error)))))))}function createFormattedDate(t){return new Date(t).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"})}function createLargePost(t){let e=document.createElement("div");e.classList.add("outerContent");let o=document.createElement("img");o.className="banner",o.src=t.headerImg,o.alt=t.title,e.appendChild(o);let n=document.createElement("div");n.classList.add("content");let a=document.createElement("div");a.classList.add("postContent");let i="";t.categories.split(", ").forEach((e=>{i+=`${e}`,t.categories.split(", ").length>1&&(i+=", ")})),i.endsWith(", ")&&(i=i.substring(0,i.length-2));let r=createFormattedDate(t.dateModified);return a.innerHTML=`\n

${t.title}

\n

Last updated: ${r} | ${i}

\n

${t.abstract}

\n See Post\n `,n.appendChild(a),e.appendChild(n),e}function loadHomeContent(){fetch("/api/blog/post").then((t=>t.json().then((t=>{for(let e=0;et.json())),await fetch("/api/blog/post/featured").then((t=>t.json()))]}function csvToArray(t){let e="",o=[""],n=0,a=!0,i=null;for(i of t)'"'===i?(a&&i===e&&(o[n]+=i),a=!a):","===i&&a?i=o[++n]="":"\n"===i&&a?("\r"===e&&(row[n]=row[n].slice(0,-1)),o=o[++r]=[i=""],n=0):o[n]+=i,e=i;return o}async function getCategories(){let t=await fetch("/api/blog/categories").then((t=>t.json())),e=[];return t.forEach((t=>e=e.concat(csvToArray(t.categories.replace(/\s*,\s*/g,","))))),[...new Set(e)]}function createCategories(t){let e="";return t.forEach((t=>e+=`${t}`)),e}function createButtonCategories(t){let e="";return t.forEach((t=>t.forEach((t=>e+=`${t}`)))),e}async function createSideContent(){let t=await getLatestAndFeaturedPosts(),e=t[0],o=t[1],n=createCategories(await getCategories()),a=document.createElement("aside");return a.classList.add("sideContent"),a.innerHTML=`\n
\n
\n My professional picture taken in brighton near \n                                        north street at night wearing a beige jacket and checkered shirt\n

Rohit Pai

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n

Avid Full Stack Dev | Uni of Notts Grad | Amateur Blogger

\n
\n \n \n \n
\n\t\t\t\t\t\t\t\t

feeds

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tAtom\n\t\t\t\t\t\t\t\t\tJSON\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n
\n

categories

\n ${n}\n
\n \n `,a}function createMetaTag(t,e,o){let n=document.querySelector(`meta[name="${t}"], meta[property="${t}"]`);if(!n){let n=document.createElement("meta");return n.setAttribute(t.includes("name")?"name":"property",t),n.setAttribute(e,o),void document.head.appendChild(n)}n.setAttribute(e,o)}function insertMetaTags(t){document.querySelector('meta[name="description"]').setAttribute("content",t.abstract),createMetaTag("twitter:title","content",t.title),createMetaTag("twitter:description","content",t.abstract),createMetaTag("twitter:image","content",t.headerImg),createMetaTag("og:title","content",t.title),createMetaTag("og:description","content",t.abstract),createMetaTag("og:image","content",t.headerImg),createMetaTag("og:url","content",window.location.href),createMetaTag("og:type","content","blog"),createMetaTag("og:site_name","content","Rohit Pai's Blog"),createMetaTag("og:locale","content","en_GB");let e=document.querySelector('meta[name="keywords"]'),o=e.getAttribute("content");o+=`, ${t.keywords}`,e.setAttribute("content",o)}async function loadIndividualPost(t){document.title="Rohit Pai - "+decodeURI(t),await fetch(`/api/blog/post/${t}`).then((async e=>{e.ok?await e.json().then((async e=>{insertMetaTags(e);let o=document.createElement("section");o.classList.add("post"),o.id="individualPost";let n=document.createElement("div");n.classList.add("mainContent");let a=document.createElement("article");a.innerHTML=`\n

${e.title}

\n \n
\n ${e.body}\n `;let i=document.createElement("section");i.classList.add("comments"),i.innerHTML='

Comments

\n
\n ',n.appendChild(a),n.appendChild(i);let r=await createSideContent();o.appendChild(n),o.appendChild(r),document.querySelector("#main").appendChild(o);var s,c;s=document,(c=s.createElement("script")).src="https://rohitpaiportfolio.disqus.com/embed.js",c.setAttribute("data-timestamp",+new Date),s.body.appendChild(c)})):show404()}))}async function loadAllCategories(){document.title="Rohit Pai - Categories";let t=await getCategories(),e=document.querySelector("#main"),o=document.createElement("section");o.classList.add("categories"),o.id="allCategories";let n=document.createElement("h1");n.innerHTML="Categories",e.appendChild(n);for(let e of t){let t=document.createElement("div");t.classList.add("btnContainer");let n=document.createElement("a");n.classList.add("btn"),n.classList.add("btnPrimary"),n.innerHTML=e,n.href=`/blog/category/${e}`,t.appendChild(n),o.appendChild(t)}e.appendChild(o)}function loadPostsByCategory(t){document.title="Rohit Pai - "+decodeURI(t),fetch(`/api/blog/categories/${t}`).then((e=>e.json().then((e=>{let o=document.querySelector("#main"),n=document.createElement("section");n.classList.add("catPosts"),n.id="postsByCategory";let a=document.createElement("h1");a.innerHTML=decodeURI(t),o.appendChild(a);for(let t=0;tt.json().then((t=>{let e=document.querySelector("#main"),o=document.createElement("section");o.classList.add("catPosts"),o.id="searchResults";let n=document.createElement("h1");n.innerHTML="Search Results",e.appendChild(n);for(let e=0;e\n\t\t\t
\n\n\t\t\t

Sources of Information and Tracking Technologies

\n\t\t\t

\n\t\t\t\tI collect information that you directly provide to me, such as when you submit a form or send me a message with your information. I may also receive information about you from my partners, including but not limited to distribution partners, data services, and marketing firms. I may combine this information with other information I collect from or about you. In these cases, my Privacy Policy governs the handling of the combined information. I and my partners may collect the information noted in this privacy policy using cookies, web beacons, pixels, and other similar technologies. These technologies are used for authentication, to store your preferences or progress, for analytics, and for advertising and analytics. Cookies are small text files stored on your computer. You can set your browser to reject cookies altogether, to reject my cookies in particular, or to delete cookies. However, this may cause some or all of my Services not to function on your computer or device.\n\t\t\t

\n\t\t\t
\n\t\t\t

How I Use Your Information

\n\t\t\t

\n\t\t\t\tI use information I collect about you to provide, maintain, and improve my Services and other interactions I have with you. For example, I use the information collected to:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • Facilitate and improve your online experience;
  • \n\t\t\t\t
  • Provide and deliver products and services, perform authentication, process transactions and returns, and send you related information, including confirmations, receipts, invoices, customer experience surveys, and product or Services-related notices;
  • \n\t\t\t\t
  • Process and deliver promotions;
  • \n\t\t\t\t
  • Respond to your comments and questions and provide customer service;
  • \n\t\t\t\t
  • If you have indicated to me that you wish to receive notifications or promotional messages;
  • \n\t\t\t\t
  • Detect, investigate and prevent fraudulent transactions and other illegal activities and protect my rights and property and others;
  • \n\t\t\t\t
  • Comply with my legal and financial obligations;
  • \n\t\t\t\t
  • Monitor and analyze trends, usage, and activities;
  • \n\t\t\t\t
  • Provide and allow my partners to provide advertising and marketing targeted toward your interests.
  • \n\t\t\t
\n\t\t\t
\n\t\t\t

How I May Share Information

\n\t\t\t

\n\t\t\t\tI may share your Personal Information in the following situations:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • \n\t\t\t\t\tThird Party Services Providers.\n\t\t\t\t\tI may share data with service providers, vendors, contractors, or agents who complete transactions or perform services on my behalf, such as those that assist me with my business and internal operations like shipping and delivery, payment processing, fraud prevention, customer service, gift cards, experiences, personalization, marketing, and advertising;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tChange in Business.\n\t\t\t\t\tI may share data in connection with a corporate business transaction, such as a merger or acquisition of all or a portion of my business to another company, joint venture, corporate reorganization, insolvency or bankruptcy, financing or sale of company assets;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tTo Comply with Law.\n\t\t\t\t\tI may share data to facilitate legal process from lawful requests by public authorities, including to meet national security or law enforcement demands as permitted by law.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tWith Your Consent.\n\t\t\t\t\tI may share data with third parties when I have your consent.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tWith Advertising and Analytics Partners.\n\t\t\t\t\tSee the section entitled “Advertising and Analytics” below.\n\t\t\t\t
  • \n\t\t\t
\n\t\t\t
\n\t\t\t

Advertising and Analytics

\n\t\t\t

\n\t\t\t\tI use advertising and analytics technologies to better understand your online activity on my Services to provide personalized products and services that may interest you. I may allow third-party companies, including ad networks, to serve advertisements, provide other advertising services, and/or collect certain information when you visit my website. Third-party companies may use pseudonymized personal data (e.g., click stream information, browser type, time and date, subject of advertisements clicked or scrolled over) during your visit to this website in order to provide advertisements about goods and services likely to be of interest to you, on this website and others. To learn more about Interest-Based Advertising or to opt-out of this type of advertising, you can visit AboutAds.info/choices or www.networkadvertising.org/choices. Some third-party companies may also use non-cookie technologies, such as statistical IDs. Please keep in mind that your web browser may not permit you to block the use of these non-cookie technologies, and those browser settings that block cookies may have no effect on such techniques. If the third-party company uses the non-cookie technologies for interest-based advertising, you can opt out at www.networkadvertising.org/choices. Please note the industry opt out only applies to use for interest-based advertising and may not apply to use for analytics or attribution. Some websites have “do not track” features that allow you to tell a website not to track you. These features are not all uniform. I do not currently respond to those signals.\n\t\t\t

\n\t\t\t
\n\t\t\t

Data Security

\n\t\t\t

\n\t\t\t\tI implement commercially reasonable security measures designed to protect your information. Despite my best efforts, however, no security measures are completely impenetrable.\n\t\t\t

\n\t\t\t
\n\t\t\t

Data Retention

\n\t\t\t

\n\t\t\t\tI store the information I collect about you for as long as necessary for the purpose(s) for which I collected it or for other legitimate business purposes, including to meet my legal, regulatory, or other compliance obligations.\n\t\t\t

\n\t\t\t
\n\n\t\t\t

EU Privacy Rights

\n\n\t\t\t

Individuals located in certain countries, including the European Economic Area (EEA) and the United Kingdom, have certain statutory rights under the General Data Protection Regulation (GDPR) in relation to their personal data.

\n\t\t\t

To the extent information I collect is associated with an identified or identifiable natural person and is protected as personal data under GDPR, it is referred to in this Privacy Policy as “Personal Data”.

\n\t\t\t

\n\t\t\t\tData Subject Access Requests\n\t\t\t

\n\t\t\t

\n\t\t\t\tSubject to any exemptions provided by law, you may have the right to request:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • \n\t\t\t\t\ta copy of the Personal Data I hold about you;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tto correct the Personal Data I hold about you;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tto delete your Account or Personal Data;\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tto object to processing of your Personal Data for certain purposes;\n\t\t\t\t
  • \n\t\t\t
\n\n\t\t\t

\n\t\t\t\tTo access your privacy rights, send me an email at rohit@rohitpai.co.uk.\n\t\t\t

\n\t\t\t

\n\t\t\t\tI will generally process requests within one month. I may need to request specific information from you to help me confirm your identity and/or the jurisdiction in which you reside. If your request is complicated or if you have made a large number of requests, it may take me longer. I will let you know if I need longer than one month to respond.\n\t\t\t

\n\t\t\t

\n\t\t\t\tLegal Bases For Processing Personal Data\n\t\t\t

\n\t\t\t

\n\t\t\t\tI may process your Personal Data under applicable data protection law on the following legal grounds:\n\t\t\t

\n\t\t\t
    \n\t\t\t\t
  • \n\t\t\t\t\tContractual Necessity:\n\t\t\t\t\tI may process your Personal Data to enter into or perform a contract with you.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tConsent:\n\t\t\t\t\twhere you have provided consent to process your Personal Data. You may withdraw your consent at any time.\n\t\t\t\t
  • \n\t\t\t\t
  • \n\t\t\t\t\tLegitimate interest:\n\t\t\t\t\tI process your Personal Data to provide my Services to you such as to provide my online user experience, communicate with you, provide customer service, market, analyze and improve my business, and to protect my Services.\n\t\t\t\t
  • \n\t\t\t
\n\t\t\t
\n\t\t\t

Age Limitations

\n\t\t\t

\n\t\t\t\tMy Service is intended for adults ages 18 years and above. I do not knowingly collect personally identifiable information from children. If you are a parent or legal guardian and think your child under 13 has given me information, please email or write to me at the address listed at the end of this Privacy Policy. Please mark your inquiries “COPPA Information Request.”\n\t\t\t

\n\t\t\t
\n\t\t\t

Changes to this Privacy Policy

\n\t\t\t

\n\t\t\t\tRohit Pai may change this Privacy Policy from time to time. I encourage you to visit this page to stay informed. If the changes are material, I may provide you additional notice to your email address or through my Services. Your continued use of the Services indicates your acceptance of the modified Privacy Policy.\n\t\t\t

\n\t\t\t
\n\t\t\t

Newsletters

\n\t\t\t

\n\t\t\t\tYou can opt in to receive my marketing emails and/or newsletters by below. I may still send you transactional messages, which include Services-related communications and responses to your questions.\n\t\t\t

\n\t\t\t
\n\t\t\t

Storage of Information in the United States

\n\t\t\t

\n\t\t\t\tInformation I maintain may be stored both within and outside of the United States. If you live outside of the United States, you understand and agree that I may transfer your information to the United States, and that U.S. laws may not afford the same level of protection as those in your country.\n\t\t\t

\n\t\t\t
\n\t\t\t

Contact Me

\n\t\t\t

\n\t\t\t\tIf you have questions, comments, or concerns about this Privacy Policy, you may contact me at:\n\t\t\t

\n\t\t\tContact\n\t\t\trohit@rohitpai.co.uk\n\t\t\n\t\t'}function loadCookiePolicy(){document.querySelector("#main").innerHTML='\n\t\t
\n\t\t\t

Cookies Policy

\n\t\t\t

I only use functional cookies for the blog which includes PHP Session ID, disqus. a cookie to disable the cookie popup, and maybe share this. I think that these are functional cookies, if you don\'t, you\'re welcome to exit the site or tell me by emailing me through the email address below, or the contact form on the contact section of my main website.

\n\t\t\t
\n\t\t\trohit@rohitpai.co.uk\n\t\t\t
\n\t\t\tcontact\n\t\t
\n\t'}function show404(){document.querySelector("#main").innerHTML='\n
\n
\n

Blog post, Category or page not found

\n See all blog posts\n
\n
\n '}document.addEventListener("DOMContentLoaded",(()=>{goToURL(window.location.pathname)})),window.addEventListener("popstate",(t=>{goToURL(window.history.state)})),window.onscroll=()=>{document.body.scrollTop>=150||document.documentElement.scrollTop>=150?document.querySelector("nav").classList.add("scrolled"):document.querySelector("nav").classList.remove("scrolled")},document.querySelector("#searchBtn").addEventListener("click",(t=>{let e=document.querySelector("#searchField").value;e.length>0&&(window.history.pushState(null,null,`/blog/search/${e}`),document.querySelector("#searchField").value="",document.querySelector("#main").innerHTML="",goToURL(`/blog/search/${e}`))})),document.querySelector("#searchField").addEventListener("keyup",(t=>{"Enter"===t.key&&document.querySelector("#searchBtn").click()})),document.querySelector("#cookieAccept").addEventListener("click",(t=>{document.querySelector("#cookiePopup").classList.add("hidden"),localStorage.setItem("cookiePopup","accepted")})); \ No newline at end of file diff --git a/dist/css/main.css b/dist/css/main.css index 85a44fb..857220b 100644 --- a/dist/css/main.css +++ b/dist/css/main.css @@ -1 +1 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}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}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-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}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 78%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,button.btn,form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}form input[type=submit]{padding:1.1em 2em}a.btn:hover,button.btn:hover form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btn:hover::after,a.btn:hover::before{visibility:hidden}a.btnPrimary,button.btnPrimary,form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline,button.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled],button.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover,button.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,button.btnPrimary:hover,form input[type=submit]:hover{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}a.btn:active,button.btn:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]){height:3em}form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}form .formControl.passwordControl{display:block}form input[type=submit]{align-self:flex-start}div.menu input:not([type=submit]),form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:.3125em solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:.5em;-moz-border-radius:.5em;border-radius:.5em;padding:0 .5em}form .formControl textarea{padding:.5em}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.menu input:not([type=submit]):focus,div.menu input:not([type=submit]):hover,form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:.3125em solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}form .formControl .checkContainer input:checked~.checkmark:after{display:block}form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}a{color:#000;text-decoration:none;text-transform:lowercase}a.link{padding:0 .5em}a.link::after,a.link::before{visibility:hidden;position:absolute;margin-top:1px}a.link::before{content:' <';margin-left:-.5em}a.link::after{content:'> '}a.link:hover::after,a.link:hover::before{visibility:visible}header{background:#6a6a6a url(../imgs/hero.jpg) no-repeat bottom;background-size:cover;height:40%;color:#fff;backdrop-filter:grayscale(100%);position:relative}nav{display:flex;flex-direction:row;justify-content:space-between;padding:.25em;position:fixed;top:0;width:100%;transition:background-color .4s ease-in;color:#fff;z-index:100000000000000000000000000}nav.scrolled{background-color:var(--navBack)}nav #nav-check{display:none}nav .nav-btn{display:none}nav h1{margin:0}nav a{text-decoration:none;color:#fff}nav>h1{margin-left:.4em}nav ul{display:flex;flex-direction:row;gap:1em;margin:0 .5em 0 0;justify-content:flex-end;align-items:flex-end}nav ul li{list-style:none}nav ul li span{visibility:hidden}nav ul li .active::after,nav ul li .active::before{visibility:visible}header div{display:flex;flex-direction:column;justify-content:center;align-items:center;padding-top:10em}header div .btn{margin:2em 0}header div button{background:0 0;border:none;display:inline-block;text-align:center;text-decoration:none;font-size:2rem;cursor:pointer}i.fa-chevron-down{color:hsla(0,0%,67%,.58);font-size:3.75em;margin:1.5rem 0}div h1 span{visibility:visible;animation:caret 1s steps(1) infinite}@keyframes caret{50%{visibility:hidden}}@media screen and (max-width:75em){nav{display:block;height:50px;width:100%;background-color:var(--navBack);position:fixed;top:0;padding:0}nav a h1{margin-left:1ch}nav .nav-btn{display:inline-block;position:absolute;right:75px;top:0}nav ul{position:fixed;display:block;width:100%;background-color:#333;transition:all .4s ease-in;overflow-y:hidden;padding-left:.5em;margin-top:7px}nav ul li a{display:block;width:100%;transform:translateX(-30px);transition:all .4s ease-in;opacity:0}.nav-btn label{display:inline-block;cursor:pointer;width:60px;height:50px;position:fixed;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.5s ease-in;-moz-transition:.5s ease-in;-o-transition:.5s ease-in;transition:.5s ease-in}.nav-btn label span{display:block;position:absolute;height:5px;width:100%;background-color:#fff;opacity:1;right:0;top:20px;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.25s ease-in;-moz-transition:.25s ease-in;-o-transition:.25s ease-in;transition:.25s ease-in}nav #nav-check:not(:checked)~ul{height:auto;max-height:0}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(1){top:8px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(2){top:23px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(3){top:38px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:checked~.nav-btn label,nav .nav-btn label:hover{background-color:rgba(-1,0,0,.3)}nav #nav-check:checked~ul{max-height:50vh;overflow-y:hidden}nav #nav-check:checked~ul li a{opacity:1;transform:translateX(0)}nav #nav-check:checked~ul li:nth-child(1) a{transition-delay:.15s}nav #nav-check:checked~ul li:nth-child(2) a{transition-delay:.25s}nav #nav-check:checked~ul li:nth-child(3) a{transition-delay:.35s}nav #nav-check:checked~ul li:nth-child(4) a{transition-delay:.45s}nav #nav-check:checked~ul li:nth-child(5) a{transition-delay:.55s}nav #nav-check:checked~.nav-btn label span:first-child{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}nav #nav-check:checked~.nav-btn label span:nth-child(2){width:0;opacity:0}nav #nav-check:checked~.nav-btn label span:last-child{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}}section#about{margin-bottom:5rem}section#about div{padding:.1em 5em}section#curriculumVitae{background-color:var(--primaryDefault);color:#fff;padding:2em 0}section#curriculumVitae .cvGrid{display:flex;flex-direction:row;padding:0 1.5rem;flex-wrap:wrap}section#curriculumVitae .cvGrid>div{width:45%;display:flex;flex-direction:column;min-height:100%}section#curriculumVitae .cvGrid h2{text-align:center}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae #work{margin:0 auto 0 8rem}section#curriculumVitae .timeline:before{content:"";position:absolute;height:100%;border:4px var(--timelineItemBrdr) solid;right:194px;top:0}section#curriculumVitae .timeline:after{content:"";display:table;clear:both}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:0 1rem;width:50%;position:relative;background-color:var(--primaryHover)}.timelineItem:after,section#curriculumVitae .timelineItem:before{content:'';position:absolute}section#curriculumVitae .timelineItem:before{content:'';right:-20px;top:calc(50% - 5px);border-style:solid;border-color:var(--timelineItemBrdr) var(--timelineItemBrdr) transparent transparent;border-width:20px;transform:rotate(45deg)}section#curriculumVitae .timelineItem:nth-child(2n){margin-left:21em}section#curriculumVitae .timelineItem:nth-child(2n):before{right:auto;left:-20px;border-color:transparent transparent var(--timelineItemBrdr) var(--timelineItemBrdr)}section#curriculumVitae .timelineItem h3{font-weight:400}section#curriculumVitae .timelineItem span{color:#e5e5e5}section#projects{display:flex;flex-direction:row;padding:0 2.5rem;border-bottom:2px solid var(--mutedGrey)}section#projects .mainProj,section#projects .otherProj{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}section#projects .mainProj{border-right:2px solid var(--mutedGrey);padding:0 2.5em 5em 0}section#allProjects img,section#projects img{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:block;margin:1em auto}section#projects .mainProj img{width:100%;max-width:40rem}section#projects .mainProj .flexRow{display:flex;flex-direction:row;gap:4em}section#projects .mainProj .flexCol{display:flex;flex-direction:column;gap:2.5em}section#projects .otherProj>a{margin:5rem 0}section#projects .otherProj>div{display:flex;flex-direction:column;gap:2em}section#allProjects #otherProj .oProjItem,section#projects .otherProj>div .oProjItem{display:flex;justify-content:center;align-items:center;flex-direction:row;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:.75em 1em}section#projects .otherProj>div .oProjItem{margin:0 auto}section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:row-reverse}section#projects .otherProj .oProjItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .oProjItem .flexCol div:nth-child(2){display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .flexCol div:nth-child(2) .btn{padding:.25em .5em}section#allProjects{display:flex;justify-content:center;align-items:center;flex-direction:column;gap:5em}section#allProjects #mainProj{display:flex;flex-direction:column;justify-content:center;align-items:center;border:1px solid var(--grey);box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1.5em 2em;margin:3em 2.5rem 0;width:50%}section#allProjects #mainProj img{width:100%;max-width:30rem}section#allProjects #otherProj{display:flex;flex-direction:row;justify-content:space-between;align-items:stretch;flex-wrap:wrap;gap:2rem;border-top:2px solid var(--grey);padding:5em 2.5rem 0}section#allProjects #otherProj .oProjItem{flex-direction:column;width:30%;height:auto}section#allProjects #otherProj img{width:100%;max-width:20rem}section#contact{display:flex;flex-direction:row;padding:0 2.5em}div#findMe,div#sayHello{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}div#findMe .findMeContainer{display:flex;flex-direction:row;justify-content:space-around;align-items:center;gap:2em;width:100%;height:100%;margin:5em 0}div#findMe .findMeContainer .profile{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%}div#findMe .socialIcons{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:2em}div#findMe .socialIcons div{display:flex;flex-direction:column;gap:1.5em}div#findMe .socialIcons div svg{width:2.5em;fill:var(--primaryDefault);font-size:2em}div#findMe .socialIcons div a:hover svg{fill:var(--primaryHover)}div#sayHello #contactForm{display:flex;flex-direction:column;justify-content:center;align-items:center}#contactForm .flName{display:flex;flex-direction:row;gap:1em;width:100%}div.message{background:var(--primaryDefault);color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em;margin-bottom:1em}div.message.error{background:var(--errorDefault)}div.message button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.message.hidden{opacity:0;visibility:hidden;height:0}div.message button:hover{text-shadow:-1px 2px var(--mutedBlack)}footer{background-color:var(--primaryDefault);margin-top:5em;padding:2em;display:flex;color:#fff}footer .spacer{width:100%;margin-right:auto}footer .nav{width:100%;margin-right:auto;display:flex;flex-direction:row;justify-content:center;align-items:center}footer .nav ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;justify-content:space-around}footer .nav ul a{color:#fff}footer p{margin:auto;width:100%;text-align:center}footer .button{margin-left:auto;width:100%;text-align:center}footer .button button{border:5px solid #fff;background:0 0;font-size:3em;padding:.5rem 1rem;width:2em;color:#fff;-webkit-border-radius:.25em;-moz-border-radius:.25em;border-radius:.25em;cursor:pointer}@media screen and (max-width:90em){section#curriculumVitae .cvGrid{flex-direction:column;justify-content:center;align-items:center}section#curriculumVitae .cvGrid>div{width:100%}section#curriculumVitae .cvGrid>div:first-child{padding-bottom:2.5em;margin-bottom:2.5em;border-bottom:5px #fff solid}section#curriculumVitae .cvGrid h2{margin-left:5em}section#curriculumVitae .cvGrid .timeline{margin:0 auto}}@media screen and (max-width:75em){section#about,section#curriculumVitae h1{padding:0 1em}section#about div{padding:.1em 2.5em}section#curriculumVitae .cvGrid{padding:0}section#projects{flex-direction:column;justify-content:center;align-items:center}section#projects .mainProj{border-right:0;padding:0;width:100%;margin:0 5em}section#projects .mainProj img{padding:0 1em}section#projects .mainProj .flexRow{flex-direction:column;margin:0 2.5em}section#projects .mainProj .flexCol{flex-direction:row;justify-content:center;align-items:center}section#projects .otherProj{width:100%}section#projects .otherProj .btn{width:10em;text-align:center}section#projects .otherProj>div .oProjItem,section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){justify-content:center;margin-left:0;margin-bottom:1em}section#projects .otherProj>a{margin-left:3em;margin-right:3em;text-align:center}section#allProjects #otherProj .oProjItem{width:45%}section#contact{flex-direction:column;justify-content:center;align-items:center}div#findMe,div#sayHello{width:100%}div#findMe .findMeContainer{flex-direction:column;justify-content:center}div#findMe .socialIcons{flex-direction:row}div#findMe .socialIcons div{flex-direction:row}}@media screen and (max-width:55em){section#curriculumVitae .cvGrid .timeline,section#curriculumVitae .cvGrid .timeline#work{margin:0 auto;width:100%}section#curriculumVitae .timeline:before{border:none}section#curriculumVitae .timelineItem,section#curriculumVitae .timelineItem:nth-child(2n){width:95%;padding:0;margin:0 auto}section#curriculumVitae .timelineItem:before{right:unset;left:unset;border:none}section#allProjects #mainProj{width:auto}section#allProjects #otherProj{flex-direction:column;align-items:center}section#allProjects #otherProj .oProjItem{width:auto}div#findMe .socialIcons{flex-direction:column}#contactForm .flName{flex-direction:column;gap:0;width:100%}}@media screen and (max-width:31em){section#about,section#curriculumVitae h1{padding:0 1em}header div h1{text-align:center;height:5.125rem}section#about div{padding:.1em 1em}section#projects .mainProj .flexCol{flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){flex-direction:column;justify-content:center;align-items:center}} \ No newline at end of file +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}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}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-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}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 75%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 1);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,button.btn,div.form input[type=submit],form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}button.btn{padding:1.2em 2.2em}div.form input[type=submit],form input[type=submit]{padding:1.1em 2em}a.btn:hover,button.btn:hover,div.form input[type=submit]:hover,form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btn:hover::after,a.btn:hover::before{visibility:hidden}a.btnPrimary,button.btnPrimary,div.form input[type=submit],form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline,button.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled],button.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover,button.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,button.btnPrimary:hover,div.form input[type=submit]:hover,form input[type=submit]:hover{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}a.btn:active,button.btn:active,div.form input[type=submit]:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}div.form .formControl input:not([type=submit]).invalid:invalid,form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}div.form .formControl input:not([type=submit]).invalid:invalid:focus,div.form .formControl textarea.invalid:invalid:focus,form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.form .formControl input:not([type=submit]),form .formControl input:not([type=submit]){height:3em}div.form .formControl,form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}div.form .formControl.passwordControl,form .formControl.passwordControl{display:block}div.form input[type=submit],form input[type=submit]{align-self:flex-start}div.form .formControl .ck.ck-editor__main .ck-content,div.form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,div.form .formControl input:not([type=submit]),div.menu input:not([type=submit]),form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:.3125em solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:.5em;-moz-border-radius:.5em;border-radius:.5em;padding:0 .5em}div.form .formControl textarea,form .formControl textarea{padding:.5em}div.form .formControl input:not([type=submit]).invalid:invalid,form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}div.form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.form .formControl input:not([type=submit]):focus,div.form .formControl input:not([type=submit]):hover,div.menu input:not([type=submit]):focus,div.menu input:not([type=submit]):hover,form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:.3125em solid var(--primaryHover)}div.form .formControl input:not([type=submit]),form .formControl input:not([type=submit]){height:3em}div.form .formControl i.fa-eye,form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}div.form .formControl .checkContainer,form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.form .formControl .checkContainer input,form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}div.form .formControl .checkContainer .checkmark,form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}div.form .formControl .checkContainer:hover input~.checkmark,form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}div.form .formControl .checkContainer input:checked~.checkmark,form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}div.form .formControl .checkContainer input:checked:hover~.checkmark,form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}div.form .formControl .checkContainer .checkmark:after,form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}div.form .formControl .checkContainer input:checked~.checkmark:after,form .formControl .checkContainer input:checked~.checkmark:after{display:block}div.form .formControl .checkContainer .checkmark:after,form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}a{color:#000;text-decoration:none;text-transform:lowercase}a.link{padding:0 .5em}a.link::after,a.link::before{visibility:hidden;position:absolute;margin-top:1px}a.link::before{content:' <';margin-left:-.5em}a.link::after{content:'> '}a.link:hover::after,a.link:hover::before{visibility:visible}div.error,div.success{color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em}div.error{background:var(--errorDefault)}div.success{background-color:var(--primaryHover)}div.error button,div.success button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.error.hidden,div.success.hidden{opacity:0;visibility:hidden;height:0;margin:0;padding:0}div.error button:hover,div.success button:hover{text-shadow:-1px 2px var(--mutedBlack)}header{background:#6a6a6a url(../imgs/hero.jpg) no-repeat bottom;background-size:cover;height:40%;color:#fff;backdrop-filter:grayscale(100%);position:relative}nav{display:flex;flex-direction:row;justify-content:space-between;padding:.25em;position:fixed;top:0;width:100%;transition:background-color .4s ease-in;color:#fff;z-index:100000000000000000000000000}nav.scrolled{background-color:var(--navBack)}nav #nav-check{display:none}nav .nav-btn{display:none}nav h1{margin:0}nav a{text-decoration:none;color:#fff}nav>h1{margin-left:.4em}nav ul{display:flex;flex-direction:row;gap:1em;margin:0 .5em 0 0;justify-content:flex-end;align-items:flex-end}nav ul li{list-style:none}nav ul li span{visibility:hidden}nav ul li .active::after,nav ul li .active::before{visibility:visible}header div{display:flex;flex-direction:column;justify-content:center;align-items:center;padding-top:10em}header div .btn{margin:2em 0}header div button{background:0 0;border:none;display:inline-block;text-align:center;text-decoration:none;font-size:2rem;cursor:pointer}i.fa-chevron-down{color:hsla(0,0%,67%,.58);font-size:3.75em;margin:1.5rem 0}div h1 span{visibility:visible;animation:caret 1s steps(1) infinite}@keyframes caret{50%{visibility:hidden}}@media screen and (max-width:75em){nav{display:block;height:50px;width:100%;background-color:var(--navBack);position:fixed;top:0;padding:0}nav a h1{margin-left:1ch}nav .nav-btn{display:inline-block;position:absolute;right:75px;top:0}nav ul{position:fixed;display:block;width:100%;background-color:#333;transition:all .4s ease-in;overflow-y:hidden;padding-left:.5em;margin-top:7px}nav ul li a{display:block;width:100%;transform:translateX(-30px);transition:all .4s ease-in;opacity:0}.nav-btn label{display:inline-block;cursor:pointer;width:60px;height:50px;position:fixed;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.5s ease-in;-moz-transition:.5s ease-in;-o-transition:.5s ease-in;transition:.5s ease-in}.nav-btn label span{display:block;position:absolute;height:5px;width:100%;background-color:#fff;opacity:1;right:0;top:20px;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.25s ease-in;-moz-transition:.25s ease-in;-o-transition:.25s ease-in;transition:.25s ease-in}nav #nav-check:not(:checked)~ul{height:auto;max-height:0}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(1){top:8px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(2){top:23px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(3){top:38px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:checked~.nav-btn label,nav .nav-btn label:hover{background-color:rgba(-1,0,0,.3)}nav #nav-check:checked~ul{max-height:50vh;overflow-y:hidden}nav #nav-check:checked~ul li a{opacity:1;transform:translateX(0)}nav #nav-check:checked~ul li:nth-child(1) a{transition-delay:.15s}nav #nav-check:checked~ul li:nth-child(2) a{transition-delay:.25s}nav #nav-check:checked~ul li:nth-child(3) a{transition-delay:.35s}nav #nav-check:checked~ul li:nth-child(4) a{transition-delay:.45s}nav #nav-check:checked~ul li:nth-child(5) a{transition-delay:.55s}nav #nav-check:checked~.nav-btn label span:first-child{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}nav #nav-check:checked~.nav-btn label span:nth-child(2){width:0;opacity:0}nav #nav-check:checked~.nav-btn label span:last-child{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}}section#about{margin-bottom:5rem}section#about div{padding:.1em 5em}section#curriculumVitae{background-color:var(--primaryDefault);color:#fff;padding:2em 0}section#curriculumVitae .cvGrid{display:flex;flex-direction:row;padding:0 1.5rem;flex-wrap:wrap}section#curriculumVitae .cvGrid>div{width:45%;display:flex;flex-direction:column;min-height:100%}section#curriculumVitae .cvGrid h2{text-align:center}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae #work{margin:0 auto 0 8rem}section#curriculumVitae .timeline:before{content:"";position:absolute;height:100%;border:4px var(--timelineItemBrdr) solid;right:194px;top:0}section#curriculumVitae .timeline:after{content:"";display:table;clear:both}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:0 1rem;width:50%;position:relative;background-color:var(--primaryHover)}.timelineItem:after,section#curriculumVitae .timelineItem:before{content:'';position:absolute}section#curriculumVitae .timelineItem:before{content:'';right:-20px;top:calc(50% - 5px);border-style:solid;border-color:var(--timelineItemBrdr) var(--timelineItemBrdr) transparent transparent;border-width:20px;transform:rotate(45deg)}section#curriculumVitae .timelineItem:nth-child(2n){margin-left:21em}section#curriculumVitae .timelineItem:nth-child(2n):before{right:auto;left:-20px;border-color:transparent transparent var(--timelineItemBrdr) var(--timelineItemBrdr)}section#curriculumVitae .timelineItem h3{font-weight:400}section#curriculumVitae .timelineItem span{color:#e5e5e5}section#projects{display:flex;flex-direction:row;padding:0 2.5rem;border-bottom:2px solid var(--mutedGrey)}section#projects .mainProj,section#projects .otherProj{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}section#projects .mainProj{border-right:2px solid var(--mutedGrey);padding:0 2.5em 5em 0}section#allProjects img,section#projects img{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:block;margin:1em auto}section#projects .mainProj img{width:100%;max-width:40rem}section#projects .mainProj .flexRow{display:flex;flex-direction:row;gap:4em}section#projects .mainProj .flexCol{display:flex;flex-direction:column;gap:2.5em}section#projects .otherProj>a{margin:5rem 0}section#projects .otherProj>div{display:flex;flex-direction:column;gap:2em}section#allProjects #otherProj .oProjItem,section#projects .otherProj>div .oProjItem{display:flex;justify-content:center;align-items:center;flex-direction:row;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:.75em 1em}section#projects .otherProj>div .oProjItem{margin:0 auto}section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:row-reverse}section#projects .otherProj .oProjItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .oProjItem .flexCol div:nth-child(2){display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .flexCol div:nth-child(2) .btn{padding:.25em .5em}section#allProjects{display:flex;justify-content:center;align-items:center;flex-direction:column;gap:5em}section#allProjects #mainProj{display:flex;flex-direction:column;justify-content:center;align-items:center;border:1px solid var(--grey);box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1.5em 2em;margin:3em 2.5rem 0;width:50%}section#allProjects #mainProj img{width:100%;max-width:30rem}section#allProjects #otherProj{display:flex;flex-direction:row;justify-content:space-between;align-items:stretch;flex-wrap:wrap;gap:2rem;border-top:2px solid var(--grey);padding:5em 2.5rem 0}section#allProjects #otherProj .oProjItem{flex-direction:column;width:30%;height:auto}section#allProjects #otherProj img{width:100%;max-width:20rem}section#contact{display:flex;flex-direction:row;padding:0 2.5em}div#findMe,div#sayHello{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}div#findMe .findMeContainer{display:flex;flex-direction:row;justify-content:space-around;align-items:center;gap:2em;width:100%;height:100%;margin:5em 0}div#findMe .findMeContainer .profile{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%}div#findMe .socialIcons{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:2em}div#findMe .socialIcons div{display:flex;flex-direction:column;gap:1.5em}div#findMe .socialIcons div svg{width:2.5em;fill:var(--primaryDefault);font-size:2em}div#findMe .socialIcons div a:hover svg{fill:var(--primaryHover)}div#sayHello #contactForm{display:flex;flex-direction:column;justify-content:center;align-items:center}#contactForm .flName{display:flex;flex-direction:row;gap:1em;width:100%}div.message{background:var(--primaryDefault);color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em;margin-bottom:1em}div.message.error{background:var(--errorDefault)}div.message button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.message.hidden{opacity:0;visibility:hidden;height:0}div.message button:hover{text-shadow:-1px 2px var(--mutedBlack)}footer{background-color:var(--primaryDefault);margin-top:5em;padding:2em;display:flex;color:#fff}footer .spacer{width:100%;margin-right:auto}footer .nav{width:100%;margin-right:auto;display:flex;flex-direction:row;justify-content:center;align-items:center}footer .nav ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;justify-content:space-around}footer .nav ul a{color:#fff}footer p{margin:auto;width:100%;text-align:center}footer .button{margin-left:auto;width:100%;text-align:center}footer .button button{border:5px solid #fff;background:0 0;font-size:3em;padding:.5rem 1rem;width:2em;color:#fff;-webkit-border-radius:.25em;-moz-border-radius:.25em;border-radius:.25em;cursor:pointer}@media screen and (max-width:90em){section#curriculumVitae .cvGrid{flex-direction:column;justify-content:center;align-items:center}section#curriculumVitae .cvGrid>div{width:100%}section#curriculumVitae .cvGrid>div:first-child{padding-bottom:2.5em;margin-bottom:2.5em;border-bottom:5px #fff solid}section#curriculumVitae .cvGrid h2{margin-left:5em}section#curriculumVitae .cvGrid .timeline{margin:0 auto}}@media screen and (max-width:75em){section#about,section#curriculumVitae h1{padding:0 1em}section#about div{padding:.1em 2.5em}section#curriculumVitae .cvGrid{padding:0}section#projects{flex-direction:column;justify-content:center;align-items:center}section#projects .mainProj{border-right:0;padding:0;width:100%;margin:0 5em}section#projects .mainProj img{padding:0 1em}section#projects .mainProj .flexRow{flex-direction:column;margin:0 2.5em}section#projects .mainProj .flexCol{flex-direction:row;justify-content:center;align-items:center}section#projects .otherProj{width:100%}section#projects .otherProj .btn{width:10em;text-align:center}section#projects .otherProj>div .oProjItem,section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){justify-content:center;margin-left:0;margin-bottom:1em}section#projects .otherProj>a{margin-left:3em;margin-right:3em;text-align:center}section#allProjects #otherProj .oProjItem{width:45%}section#contact{flex-direction:column;justify-content:center;align-items:center}div#findMe,div#sayHello{width:100%}div#findMe .findMeContainer{flex-direction:column;justify-content:center}div#findMe .socialIcons{flex-direction:row}div#findMe .socialIcons div{flex-direction:row}}@media screen and (max-width:55em){section#curriculumVitae .cvGrid .timeline,section#curriculumVitae .cvGrid .timeline#work{margin:0 auto;width:100%}section#curriculumVitae .timeline:before{border:none}section#curriculumVitae .timelineItem,section#curriculumVitae .timelineItem:nth-child(2n){width:95%;padding:0;margin:0 auto}section#curriculumVitae .timelineItem:before{right:unset;left:unset;border:none}section#allProjects #mainProj{width:auto}section#allProjects #otherProj{flex-direction:column;align-items:center}section#allProjects #otherProj .oProjItem{width:auto}div#findMe .socialIcons{flex-direction:column}#contactForm .flName{flex-direction:column;gap:0;width:100%}}@media screen and (max-width:31em){section#about,section#curriculumVitae h1{padding:0 1em}header div h1{text-align:center;height:5.125rem}section#about div{padding:.1em 1em}section#projects .mainProj .flexCol{flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){flex-direction:column;justify-content:center;align-items:center}} \ No newline at end of file diff --git a/dist/editor/css/main.css b/dist/editor/css/main.css index e18bf12..99a64df 100644 --- a/dist/editor/css/main.css +++ b/dist/editor/css/main.css @@ -1 +1 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}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}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-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}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 78%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,button.btn,form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}form input[type=submit]{padding:1.1em 2em}a.btn:hover,button.btn:hover form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btn:hover::after,a.btn:hover::before{visibility:hidden}a.btnPrimary,button.btnPrimary,form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline,button.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled],button.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover,button.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,button.btnPrimary:hover,form input[type=submit]:hover{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}a.btn:active,button.btn:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]){height:3em}form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}form .formControl.passwordControl{display:block}form input[type=submit]{align-self:flex-start}div.menu input:not([type=submit]),form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:.3125em solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:.5em;-moz-border-radius:.5em;border-radius:.5em;padding:0 .5em}form .formControl textarea{padding:.5em}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.menu input:not([type=submit]):focus,div.menu input:not([type=submit]):hover,form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:.3125em solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}form .formControl .checkContainer input:checked~.checkmark:after{display:block}form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}a{color:#000;text-decoration:none;text-transform:lowercase}a.link::after,a.link::before{visibility:hidden;position:absolute;margin-top:1px}a.link::before{content:'<';margin-left:-.5em}a.link::after{content:'>'}a.link:hover::after,a.link:hover::before{visibility:visible}h1{text-transform:none}body,html{height:100%}main.login{height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;background-image:radial-gradient(var(--primaryDefault),#597226)}div.container{flex-direction:column;justify-content:center;align-items:center;background-color:#fff;padding:2em 5em;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-transform:translateX(-150vw);-moz-transform:translateX(-150vw);-ms-transform:translateX(-150vw);-o-transform:translateX(-150vw);transform:translateX(-150vw);-webkit-transition:transform .4s ease-in-out;-moz-transition:transform .4s ease-in-out;-ms-transition:transform .4s ease-in-out;-o-transition:transform .4s ease-in-out;transition:transform .4s ease-in-out;overflow:hidden}div.container.shown{-webkit-transform:translateX(0);-moz-transform:translateX(0);-ms-transform:translateX(0);-o-transform:translateX(0);transform:translateX(0)}div.container form{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}div#login #password{font-family:Verdana,serif;letter-spacing:.125em}div#login input[type=submit]{margin:0}div.error,div.success{color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em}div.error{background:var(--errorDefault)}div.success{background-color:var(--primaryHover)}div.error button,div.success button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.error.hidden,div.success.hidden{opacity:0;visibility:hidden;height:0;margin:0;padding:0}div.error button:hover,div.success button:hover{text-shadow:-1px 2px var(--mutedBlack)}div.btnContainer{width:100%;display:flex;flex-direction:row;justify-content:space-between;align-items:center}div.btnContainer a:not(.btn){color:#000}nav{font-size:var(--headingFS)}nav.sideNav{height:100%;width:250px;z-index:1;position:fixed;top:0;left:0;background-color:var(--primaryHover);overflow-x:hidden;-webkit-transition:.5s;-moz-transition:.5s;-ms-transition:.5s;-o-transition:.5s;transition:.5s;padding-top:60px}nav.sideNav ul li{list-style:none}nav.sideNav a{padding:8px 8px 8px 0;text-decoration:none;color:#fff;display:block;-webkit-transition:.3s;-moz-transition:.3s;-ms-transition:.3s;-o-transition:.3s;transition:.3s}nav.sideNav .closeBtn{position:absolute;top:0;right:25px;margin-left:50px;font-size:var(--titleFS);display:none}nav.sideNav ul li span{visibility:hidden}nav.sideNav ul li a.active span,nav.sideNav ul li a:hover span{visibility:visible}nav.sideNav ul li.dropdown ul{transition:max-height ease-out .4s;max-height:0;overflow:hidden}nav.sideNav ul li.dropdown ul.active{transition:max-height ease-in .4s;max-height:15rem}nav.sideNav ul li.dropdown ul li{margin-left:-1rem}span#navOpen{font-size:var(--titleFS);cursor:pointer}main.editor{margin-left:250px}.title{display:flex;flex-direction:column;justify-content:center;align-items:center}#navOpen{visibility:hidden;padding:.25em 0 0 .25em;align-self:flex-start}textarea{resize:none}main.editor section{margin:0 2em}input[type=submit]{margin-top:2em}.delete,.edit{border:none;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;outline:0;background-color:var(--primaryDefault);color:#fff;cursor:pointer}.timelineHeader{font-weight:400}div.editorContainer,div.projectsGrid{display:flex;flex-direction:row;justify-content:center;align-items:flex-start;gap:2em;margin-bottom:.5em}div.editorContainer>*,div.projectsGrid>*{width:45%}main.editor section{display:none;flex-direction:column}section#curriculumVitae{display:flex}div.modifyBtnContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;margin-bottom:.5em;width:100%}div.companyAreaContainer,div.dateContainer{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;gap:1em;margin-bottom:.5em}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae .timelineItem,section#projects .projItem{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1rem;position:relative}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);color:#fff;background-color:var(--primaryHover)}section#curriculumVitae .timelineItem.editing{color:#000;border:5px solid var(--primaryDefault);padding:.5em}section#curriculumVitae .timelineItem.editing{background-color:#fff}form div.gradeContainer.formControl{display:flex;flex-direction:row;justify-content:flex-start;align-items:center}section#curriculumVitae form.timelineItem:not(.editing) .delete,section#curriculumVitae form.timelineItem:not(.editing) .edit{color:var(--primaryHover);background-color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.dateContainer{display:none}section#curriculumVitae form.timelineItem.editing .timelineHeader{display:none}section#curriculumVitae form.timelineItem.editing div.gradeContainer.formControl{gap:1em;margin-bottom:.5em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText,section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl input,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input,section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{outline:0;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl>*,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl>*{color:#e5e5e5}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText{color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input{padding:0 .25em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) div.formControl .courseText{padding:0}section#curriculumVitae form.timelineItem:not(.editing) input[type=submit]{display:none}.courseText{resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer input{width:30%}section#projects #projList .projItem{display:flex;justify-content:center;align-items:center;flex-direction:column;margin:0 auto;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack)}section#projects #projList{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}section#projects #projList .projItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .projItem .linkContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .linkContainer .btn{padding:.25em .5em}section#projects #isMainProject{width:auto}section#projects form.projItem.editing div.linkContainer,section#projects form.projItem.editing img.displayedImage,section#projects form.projItem:not(.editing) div.formControl.gitContainer,section#projects form.projItem:not(.editing) div.formControl.imageContainer,section#projects form.projItem:not(.editing) div.formControl.isMainProject,section#projects form.projItem:not(.editing) div.formControl.viewProjContainer,section#projects form.projItem:not(.editing) input[type=submit]{display:none}section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{font-size:1.17em;font-weight:700}section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{color:#000}section#addPost form,section#editPost form{margin:auto 4rem}form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar{border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom:2px solid var(--mutedGrey);box-shadow:0 3px 4px var(--mutedBlack)}form .formControl .ck.ck-editor__main .ck-content{border-top-right-radius:0;border-top-left-radius:0;border-top:inherit}form .formControl .ck-editor__editable{min-height:400px}section#editPost{justify-content:center}section#editPost h2{align-self:flex-start}section#editPost table{border-collapse:collapse;border-style:hidden;align-self:center;margin-bottom:5em}section#editPost table td,th{border:1px solid var(--mutedGrey);text-align:left;padding:8px;min-width:10rem}section#editPost form{margin-bottom:2em}@media only screen and (max-width:75em){nav.sideNav .closeBtn{display:block}} \ No newline at end of file +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}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}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-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}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 75%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 1);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,button.btn,div.form input[type=submit],form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}button.btn{padding:1.2em 2.2em}div.form input[type=submit],form input[type=submit]{padding:1.1em 2em}a.btn:hover,button.btn:hover,div.form input[type=submit]:hover,form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btn:hover::after,a.btn:hover::before{visibility:hidden}a.btnPrimary,button.btnPrimary,div.form input[type=submit],form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline,button.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled],button.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover,button.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,button.btnPrimary:hover,div.form input[type=submit]:hover,form input[type=submit]:hover{background:var(--primaryHover);border:.3215em solid var(--primaryHover)}a.btn:active,button.btn:active,div.form input[type=submit]:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}div.form .formControl input:not([type=submit]).invalid:invalid,form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}div.form .formControl input:not([type=submit]).invalid:invalid:focus,div.form .formControl textarea.invalid:invalid:focus,form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.form .formControl input:not([type=submit]),form .formControl input:not([type=submit]){height:3em}div.form .formControl,form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}div.form .formControl.passwordControl,form .formControl.passwordControl{display:block}div.form input[type=submit],form input[type=submit]{align-self:flex-start}div.form .formControl .ck.ck-editor__main .ck-content,div.form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,div.form .formControl input:not([type=submit]),div.menu input:not([type=submit]),form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:.3125em solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:.5em;-moz-border-radius:.5em;border-radius:.5em;padding:0 .5em}div.form .formControl textarea,form .formControl textarea{padding:.5em}div.form .formControl input:not([type=submit]).invalid:invalid,form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:.3125em solid var(--errorDefault)}div.form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:.3125em solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}div.form .formControl input:not([type=submit]):focus,div.form .formControl input:not([type=submit]):hover,div.menu input:not([type=submit]):focus,div.menu input:not([type=submit]):hover,form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:.3125em solid var(--primaryHover)}div.form .formControl input:not([type=submit]),form .formControl input:not([type=submit]){height:3em}div.form .formControl i.fa-eye,form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}div.form .formControl .checkContainer,form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.form .formControl .checkContainer input,form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}div.form .formControl .checkContainer .checkmark,form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}div.form .formControl .checkContainer:hover input~.checkmark,form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}div.form .formControl .checkContainer input:checked~.checkmark,form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}div.form .formControl .checkContainer input:checked:hover~.checkmark,form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}div.form .formControl .checkContainer .checkmark:after,form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}div.form .formControl .checkContainer input:checked~.checkmark:after,form .formControl .checkContainer input:checked~.checkmark:after{display:block}div.form .formControl .checkContainer .checkmark:after,form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}a{color:#000;text-decoration:none;text-transform:lowercase}a.link{padding:0 .5em}a.link::after,a.link::before{visibility:hidden;position:absolute;margin-top:1px}a.link::before{content:' <';margin-left:-.5em}a.link::after{content:'> '}a.link:hover::after,a.link:hover::before{visibility:visible}div.error,div.success{color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em}div.error{background:var(--errorDefault)}div.success{background-color:var(--primaryHover)}div.error button,div.success button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.error.hidden,div.success.hidden{opacity:0;visibility:hidden;height:0;margin:0;padding:0}div.error button:hover,div.success button:hover{text-shadow:-1px 2px var(--mutedBlack)}h1{text-transform:none}body,html{height:100%}main.login{height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;background-image:radial-gradient(var(--primaryDefault),#597226)}div.container{flex-direction:column;justify-content:center;align-items:center;background-color:#fff;padding:2em 5em;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-transform:translateX(-150vw);-moz-transform:translateX(-150vw);-ms-transform:translateX(-150vw);-o-transform:translateX(-150vw);transform:translateX(-150vw);-webkit-transition:transform .4s ease-in-out;-moz-transition:transform .4s ease-in-out;-ms-transition:transform .4s ease-in-out;-o-transition:transform .4s ease-in-out;transition:transform .4s ease-in-out;overflow:hidden}div.container.shown{-webkit-transform:translateX(0);-moz-transform:translateX(0);-ms-transform:translateX(0);-o-transform:translateX(0);transform:translateX(0)}div.container form{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}div#login #password{font-family:Verdana,serif;letter-spacing:.125em}div#login input[type=submit]{margin:0}div.btnContainer{width:100%;display:flex;flex-direction:row;justify-content:space-between;align-items:center}div.btnContainer a:not(.btn){color:#000}nav{font-size:var(--headingFS)}nav.sideNav{height:100%;width:250px;z-index:1;position:fixed;top:0;left:0;background-color:var(--primaryHover);overflow-x:hidden;-webkit-transition:.5s;-moz-transition:.5s;-ms-transition:.5s;-o-transition:.5s;transition:.5s;padding-top:60px}nav.sideNav ul li{list-style:none}nav.sideNav a{padding:8px 8px 8px 0;text-decoration:none;color:#fff;display:block;-webkit-transition:.3s;-moz-transition:.3s;-ms-transition:.3s;-o-transition:.3s;transition:.3s}nav.sideNav .closeBtn{position:absolute;top:0;right:25px;margin-left:50px;font-size:var(--titleFS);display:none}nav.sideNav ul li span{visibility:hidden}nav.sideNav ul li a.active span,nav.sideNav ul li a:hover span{visibility:visible}nav.sideNav ul li.dropdown ul{transition:max-height ease-out .4s;max-height:0;overflow:hidden}nav.sideNav ul li.dropdown ul.active{transition:max-height ease-in .4s;max-height:20rem}nav.sideNav ul li.dropdown ul li{margin-left:-1rem}span#navOpen{font-size:var(--titleFS);cursor:pointer}main.editor{margin-left:250px}.title{display:flex;flex-direction:column;justify-content:center;align-items:center}#navOpen{visibility:hidden;padding:.25em 0 0 .25em;align-self:flex-start}textarea{resize:none}main.editor section{margin:0 2em}input[type=submit]{margin-top:2em}.delete,.edit{border:none;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;outline:0;background-color:var(--primaryDefault);color:#fff;cursor:pointer}.timelineHeader{font-weight:400}div.editorContainer,div.projectsGrid{display:flex;flex-direction:row;justify-content:center;align-items:flex-start;gap:2em;margin-bottom:.5em}div.editorContainer>*,div.projectsGrid>*{width:45%}main.editor section{display:none;flex-direction:column}section#curriculumVitae{display:flex}div.modifyBtnContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;margin-bottom:.5em;width:100%}div.companyAreaContainer,div.dateContainer{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;gap:1em;margin-bottom:.5em}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae .timelineItem,section#projects .projItem{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1rem;position:relative}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);color:#fff;background-color:var(--primaryHover)}section#curriculumVitae .timelineItem.editing{color:#000;border:5px solid var(--primaryDefault);padding:.5em}section#curriculumVitae .timelineItem.editing{background-color:#fff}form div.gradeContainer.formControl{display:flex;flex-direction:row;justify-content:flex-start;align-items:center}section#curriculumVitae form.timelineItem:not(.editing) .delete,section#curriculumVitae form.timelineItem:not(.editing) .edit{color:var(--primaryHover);background-color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.dateContainer{display:none}section#curriculumVitae form.timelineItem.editing .timelineHeader{display:none}section#curriculumVitae form.timelineItem.editing div.gradeContainer.formControl{gap:1em;margin-bottom:.5em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText,section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl input,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input,section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{outline:0;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl>*,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl>*{color:#e5e5e5}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText{color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input{padding:0 .25em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) div.formControl .courseText{padding:0}section#curriculumVitae form.timelineItem:not(.editing) input[type=submit]{display:none}.courseText{resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer input{width:30%}section#projects #projList .projItem{display:flex;justify-content:center;align-items:center;flex-direction:column;margin:0 auto;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack)}section#projects #projList{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}section#projects #projList .projItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .projItem .linkContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .linkContainer .btn{padding:.25em .5em}section#projects #isMainProject{width:auto}section#projects form.projItem.editing div.linkContainer,section#projects form.projItem.editing img.displayedImage,section#projects form.projItem:not(.editing) div.formControl.gitContainer,section#projects form.projItem:not(.editing) div.formControl.imageContainer,section#projects form.projItem:not(.editing) div.formControl.isMainProject,section#projects form.projItem:not(.editing) div.formControl.viewProjContainer,section#projects form.projItem:not(.editing) input[type=submit]{display:none}section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{font-size:1.17em;font-weight:700}section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{color:#000}section#addPost form,section#editPost form{margin:auto 4rem}form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar{border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom:2px solid var(--mutedGrey);box-shadow:0 3px 4px var(--mutedBlack)}form .formControl .ck.ck-editor__main .ck-content{border-top-right-radius:0;border-top-left-radius:0;border-top:inherit}form .formControl .ck-editor__editable{min-height:400px}section#editPost{justify-content:center}section#editPost h2{align-self:flex-start}section#editPost table{border-collapse:collapse;border-style:hidden;align-self:center;margin-bottom:5em}section#editPost table td,th{border:1px solid var(--mutedGrey);text-align:left;padding:8px;min-width:10rem}section#editPost form{margin-bottom:2em}section#newsletter form{margin:0 5em}@media only screen and (max-width:75em){nav.sideNav .closeBtn{display:block}} \ No newline at end of file diff --git a/dist/editor/editor.html b/dist/editor/editor.html index 89aea41..25d5b16 100644 --- a/dist/editor/editor.html +++ b/dist/editor/editor.html @@ -1 +1 @@ -Editor

Editor

curriculum vitae

Education

Work

projects

add post

edit post

TitleDate CreatedDate ModifiedAction
\ No newline at end of file +Editor

Editor

curriculum vitae

Education

Work

projects

add post

edit post

TitleDate CreatedDate ModifiedAction

newsletter

\ No newline at end of file diff --git a/dist/editor/js/editor.js b/dist/editor/js/editor.js index a88e6e3..99f46f2 100644 --- a/dist/editor/js/editor.js +++ b/dist/editor/js/editor.js @@ -1 +1 @@ -let dateOptions={month:"short",year:"numeric"},textareaLoaded=!1,editors={},posts=null;const smallPaddingElements=["figcaption","li"];function goToPage(e){document.querySelectorAll(".editor section").forEach((t=>{t.style.display="none",t.id===e&&(t.style.display="flex")}))}function viewToPlainText(e){let t="";if(e.is("$text")||e.is("$textProxy"))t=e.data;else if(e.is("element","br"))t="\n";else{let a=null;for(const l of e.getChildren()){const e=viewToPlainText(l);a&&(a.is("containerElement")||l.is("containerElement"))&&(smallPaddingElements.includes(a.name)||smallPaddingElements.includes(l.name)?t+="\n":t+="\n\n"),t+=e,a=l}}return t}function addActiveClass(e){document.querySelectorAll("nav.sideNav ul li a").forEach((t=>{t.classList.remove("active"),t.id===e&&t.classList.add("active")}))}function editProjectItem(e){document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}proj`).toggleAttribute("disabled"),document.querySelector(`#info${e}proj`).toggleAttribute("disabled")}function createEditors(...e){e.forEach((e=>{ClassicEditor.create(document.querySelector(`#${e}`),{placeholder:"Write something amazing...",simpleUpload:{uploadUrl:"/api/blog/uploadPostImage",headers:{Authorization:"Bearer "+localStorage.getItem("token")}},style:{definitions:[{name:"Button Primary",element:"a",classes:["btn","btnPrimary"]},{name:"Button Primary",element:"button",classes:["btn","btnPrimary"]}]},codeBlock:{languages:[{language:"plaintext",label:"Plain text"},{language:"abap",label:"ABAP"},{language:"abnf",label:"ABNF"},{language:"actionscript",label:"ActionScript"},{language:"ada",label:"Ada"},{language:"agda",label:"Agda"},{language:"al",label:"AL"},{language:"antlr4",label:"ANTLR4"},{language:"apacheconf",label:"Apache Configuration"},{language:"apex",label:"Apex"},{language:"apl",label:"APL"},{language:"applescript",label:"AppleScript"},{language:"aql",label:"AQL"},{language:"arduino",label:"Arduino"},{language:"arff",label:"ARFF"},{language:"asciidoc",label:"AsciiDoc"},{language:"aspnet",label:"ASP.NET (C#)"},{language:"asm6502",label:"6502 Assembly"},{language:"autohotkey",label:"AutoHotkey"},{language:"autoit",label:"AutoIt"},{language:"bash",label:"Bash"},{language:"basic",label:"BASIC"},{language:"batch",label:"Batch"},{language:"bbcode",label:"BBcode"},{language:"bison",label:"Bison"},{language:"bnf",label:"BNF"},{language:"brainfuck",label:"Brainfuck"},{language:"brightscript",label:"BrightScript"},{language:"bro",label:"Bro"},{language:"c",label:"C"},{language:"concurnas",label:"Concurnas"},{language:"csharp",label:"C#"},{language:"cpp",label:"C++"},{language:"cil",label:"CIL"},{language:"clojure",label:"Clojure"},{language:"cmake",label:"CMake"},{language:"coffeescript",label:"CoffeeScript"},{language:"concurnas",label:"Concurnas"},{language:"crystal",label:"Crystal"},{language:"css-extras",label:"CSS Extras"},{language:"css",label:"CSS"},{language:"d",label:"D"},{language:"dart",label:"Dart"},{language:"dax",label:"DAX"},{language:"dhall",label:"Dhall"},{language:"diff",label:"Diff"},{language:"django",label:"Django/Jinja2"},{language:"dns-zone-file",label:"DNS zone file"},{language:"docker",label:"Docker"},{language:"ebnf",label:"EBNF"},{language:"editorconfig",label:"EditorConfig"},{language:"eiffel",label:"Eiffel"},{language:"ejs",label:"EJS"},{language:"elixir",label:"Elixir"},{language:"elm",label:"Elm"},{language:"etlua",label:"Embedded Lua"},{language:"erb",label:"ERB"},{language:"erlang",label:"Erlang"},{language:"excel-formula",label:"Excel Formula"},{language:"fsharp",label:"F#"},{language:"factor",label:"Factor"},{language:"firestore-security-rules",label:"Firestore security rules"},{language:"flow",label:"Flow"},{language:"fortran",label:"Fortran"},{language:"ftl",label:"FreeMarker Template Language"},{language:"gcode",label:"G-code"},{language:"gdscript",label:"GDScript"},{language:"gedcom",label:"GEDCOM"},{language:"gherkin",label:"Gherkin"},{language:"git",label:"Git"},{language:"glsl",label:"GLSL"},{language:"gml",label:"GameMaker Language"},{language:"go",label:"Go"},{language:"graphql",label:"GraphQL"},{language:"groovy",label:"Groovy"},{language:"haml",label:"Haml"},{language:"handlebars",label:"Handlebars"},{language:"haskell",label:"Haskell"},{language:"haxe",label:"Haxe"},{language:"hcl",label:"HCL"},{language:"hlsl",label:"HLSL"},{language:"http",label:"HTTP"},{language:"hpkp",label:"HTTP Public-Key-Pins"},{language:"hsts",label:"HTTP Strict-Transport-Security"},{language:"ichigojam",label:"IchigoJam"},{language:"icon",label:"Icon"},{language:"ignore",label:"Ignore"},{language:"inform7",label:"Inform 7"},{language:"ini",label:"Ini"},{language:"io",label:"Io"},{language:"j",label:"J"},{language:"java",label:"Java"},{language:"javadoc",label:"JavaDoc"},{language:"javadoclike",label:"JavaDoc-like"},{language:"javascript",label:"JavaScript"},{language:"javastacktrace",label:"Java stack trace"},{language:"jolie",label:"Jolie"},{language:"jq",label:"JQ"},{language:"js-extras",label:"JS Extras"},{language:"js-templates",label:"JS Templates"},{language:"jsdoc",label:"JSDoc"},{language:"json",label:"JSON"},{language:"json5",label:"JSON5"},{language:"jsonp",label:"JSONP"},{language:"jsstacktrace",label:"JS stack trace"},{language:"jsx",label:"React JSX"},{language:"julia",label:"Julia"},{language:"keyman",label:"Keyman"},{language:"kotlin",label:"Kotlin"},{language:"latex",label:"LaTeX"},{language:"latte",label:"Latte"},{language:"less",label:"Less"},{language:"lilypond",label:"LilyPond"},{language:"liquid",label:"Liquid"},{language:"lisp",label:"Lisp"},{language:"livescript",label:"LiveScript"},{language:"llvm",label:"LLVM IR"},{language:"log",label:"Log file"},{language:"lolcode",label:"LOLCODE"},{language:"lua",label:"Lua"},{language:"makefile",label:"Makefile"},{language:"markdown",label:"Markdown"},{language:"markup-templating",label:"Markup templating"},{language:"matlab",label:"MATLAB"},{language:"mel",label:"MEL"},{language:"mizar",label:"Mizar"},{language:"mongodb",label:"MongoDB"},{language:"monkey",label:"Monkey"},{language:"moonscript",label:"MoonScript"},{language:"n1ql",label:"N1QL"},{language:"n4js",label:"N4JS"},{language:"nand2tetris-hdl",label:"Nand To Tetris HDL"},{language:"nasm",label:"NASM"},{language:"neon",label:"NEON"},{language:"nginx",label:"nginx"},{language:"nim",label:"Nim"},{language:"nix",label:"Nix"},{language:"nsis",label:"NSIS"},{language:"objectivec",label:"Objective-C"},{language:"ocaml",label:"OCaml"},{language:"opencl",label:"OpenCL"},{language:"oz",label:"Oz"},{language:"parigp",label:"PARI/GP"},{language:"parser",label:"Parser"},{language:"pascal",label:"Pascal"},{language:"pascaligo",label:"Pascaligo"},{language:"pcaxis",label:"PC-Axis"},{language:"peoplecode",label:"PeopleCode"},{language:"perl",label:"Perl"},{language:"php",label:"PHP"},{language:"phpdoc",label:"PHPDoc"},{language:"php-extras",label:"PHP Extras"},{language:"plsql",label:"PL/SQL"},{language:"powerquery",label:"PowerQuery"},{language:"powershell",label:"PowerShell"},{language:"processing",label:"Processing"},{language:"prolog",label:"Prolog"},{language:"properties",label:".properties"},{language:"protobuf",label:"Protocol Buffers"},{language:"pug",label:"Pug"},{language:"puppet",label:"Puppet"},{language:"pure",label:"Pure"},{language:"purebasic",label:"PureBasic"},{language:"python",label:"Python"},{language:"q",label:"Q (kdb+ database)"},{language:"qml",label:"QML"},{language:"qore",label:"Qore"},{language:"r",label:"R"},{language:"racket",label:"Racket"},{language:"jsx",label:"React JSX"},{language:"tsx",label:"React TSX"},{language:"reason",label:"Reason"},{language:"regex",label:"Regex"},{language:"renpy",label:"Ren'py"},{language:"rest",label:"reST (reStructuredText)"},{language:"rip",label:"Rip"},{language:"roboconf",label:"Roboconf"},{language:"robotframework",label:"Robot Framework"},{language:"ruby",label:"Ruby"},{language:"rust",label:"Rust"},{language:"sas",label:"SAS"},{language:"sass",label:"Sass (Sass)"},{language:"scss",label:"Sass (Scss)"},{language:"scala",label:"Scala"},{language:"scheme",label:"Scheme"},{language:"shell-session",label:"Shell session"},{language:"smali",label:"Smali"},{language:"smalltalk",label:"Smalltalk"},{language:"smarty",label:"Smarty"},{language:"solidity",label:"Solidity (Ethereum)"},{language:"solution-file",label:"Solution file"},{language:"soy",label:"Soy (Closure Template)"},{language:"sparql",label:"SPARQL"},{language:"splunk-spl",label:"Splunk SPL"},{language:"sqf",label:"SQF: Status Quo Function (Arma 3)"},{language:"sql",label:"SQL"},{language:"stan",label:"Stan"},{language:"stata",label:"Stata"},{language:"step21",label:"STEP Part 21"},{language:"stylus",label:"Stylus"},{language:"swift",label:"Swift"},{language:"tap",label:"TAP"},{language:"tcl",label:"Tcl"},{language:"textile",label:"Textile"},{language:"toml",label:"TOML"},{language:"tt2",label:"Template Toolkit 2"},{language:"turtle",label:"Turtle"},{language:"twig",label:"Twig"},{language:"typescript",label:"TypeScript"},{language:"t4-cs",label:"T4 Text Templates (C#)"},{language:"t4-vb",label:"T4 Text Templates (VB)"},{language:"t4-templating",label:"T4 templating"},{language:"unrealscript",label:"UnrealScript"},{language:"vala",label:"Vala"},{language:"vbnet",label:"VB.Net"},{language:"velocity",label:"Velocity"},{language:"verilog",label:"Verilog"},{language:"vhdl",label:"VHDL"},{language:"vim",label:"vim"},{language:"visual-basic",label:"Visual Basic"},{language:"warpscript",label:"WarpScript"},{language:"wasm",label:"WebAssembly"},{language:"wiki",label:"Wiki markup"},{language:"xeora",label:"Xeora"},{language:"xojo",label:"Xojo (REALbasic)"},{language:"xquery",label:"XQuery"},{language:"yaml",label:"YAML"},{language:"zephir",label:"Zephir"}]}}).then((t=>{editors[e]=t})).catch((e=>{console.error("Oops, something went wrong!"),console.error("Please, report the following error on https://github.com/ckeditor/ckeditor5/issues with the build id and the error stack trace:"),console.warn("Build id: 1eo8ioyje2om-vgar4aghypdm"),console.error(e)}))}))}function editPostItem(e){posts.forEach((t=>{t.ID===e&&(document.querySelector("#editPostTitle").value=t.title,document.querySelector("#editIsFeatured").checked=1===t.featured,document.querySelector("#editPostCategories").value=t.categories,document.querySelector("#editPostAbstract").value=t.abstract,editors.CKEditorEditPost.setData(t.body),document.querySelector("#editPostForm input[type='submit']").id=e)}))}function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function showSuccessMessage(e,t){document.querySelector(`#${t}Success`).classList.remove("hidden"),document.querySelector(`#${t}Success div`).innerText=e}function editCVItem(e){if(textareaLoaded=!1,document.querySelector(`#timelineItem${e}`).classList.toggle("editing"),e.includes("e"))return document.querySelector(`#grade${e}`).toggleAttribute("disabled"),void document.querySelector(`#course${e}`).toggleAttribute("disabled");document.querySelector(`#companyName${e}`).toggleAttribute("disabled"),document.querySelector(`#area${e}`).toggleAttribute("disabled"),document.querySelector(`#jobTitle${e}`).toggleAttribute("disabled")}function addEduData(e,t,a,l,o,n=!1){let r=e+"e",i=document.createElement("form");i.id="timelineItem"+r,i.classList.add("timelineItem"),i.onsubmit=t=>updateEduItem(e,t),i.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${new Date(a).toLocaleString("en-gb",dateOptions)}

\n
\n \n \n
\n
\n \n
\n \n \n \n `,n?document.querySelector("#edu").prepend(i):document.getElementById("edu").appendChild(i)}function addWorkData(e,t,a,l,o,n,r=!1){let i=e+"w",d=document.createElement("form");d.id="timelineItem"+i,d.classList.add("timelineItem"),d.onsubmit=t=>updateWorkItem(e,t),d.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${"Present"===a?"Present":new Date(a).toLocaleString("en-gb",dateOptions)}

\n
\n \n -\n \n
\n
\n \n
\n \n \n \n\t`,r?document.querySelector("#work").prepend(d):document.getElementById("work").appendChild(d)}function updateEduItem(e,t){t.preventDefault();let a={};a.dateFrom=document.querySelector(`#dateFrom${e}e`).value,a.dateTo=document.querySelector(`#dateTo${e}e`).value,a.grade=document.querySelector(`#grade${e}e`).value,a.course=document.querySelector(`#course${e}e`).value,fetch("/api/timelineData/edu/"+e,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}e`).innerHTML=new Date(document.querySelector(`#dateFrom${e}e`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}e`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}e`).classList.toggle("editing"),document.querySelector(`#grade${e}e`).setAttribute("disabled",""),void document.querySelector(`#course${e}e`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#eduError${e}e`).classList.remove("hidden"),document.querySelector(`#eduError${e}e div`).innerHTML=t.error})):window.location.href="./"}))}function updateWorkItem(e,t){t.preventDefault();let a={};a.dateFrom=document.querySelector(`#dateFrom${e}w`).value,a.dateTo=document.querySelector(`#dateTo${e}w`).value,a.companyName=document.querySelector(`#companyName${e}w`).value,a.area=document.querySelector(`#area${e}w`).value,a.title=document.querySelector(`#jobTitle${e}w`).value,fetch("/api/timelineData/work/"+e,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}w`).innerHTML=new Date(document.querySelector(`#dateFrom${e}w`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}w`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}w`).classList.toggle("editing"),document.querySelector(`#companyName${e}w`).setAttribute("disabled",""),document.querySelector(`#area${e}w`).setAttribute("disabled",""),void document.querySelector(`#jobTitle${e}w`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#workError${e}w`).classList.remove("hidden"),document.querySelector(`#workError${e}w div`).innerHTML=t.error})):window.location.href="./"}))}function deleteEduItem(e){fetch("/api/timelineData/edu/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}e`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function deleteWorkItem(e){fetch("/api/timelineData/work/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}w`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function updateProjectItem(e,t){t.preventDefault();let a={};a.title=document.querySelector(`#title${e}`).value,a.isMainProject=document.querySelector(`#isMainProject${e}`).checked?"true":"false",a.information=document.querySelector(`#info${e}`).value,a.projectLink=document.querySelector(`#viewProj${e}`).value,a.gitLink=document.querySelector(`#git${e}`).value;let l=new FormData;l.append("img",document.querySelector(`#img${e}`).files[0]),fetch("/api/projectData/"+e,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return"undefined"===l.get("img")?("true"===a.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),void document.querySelector(`#info${e}`).setAttribute("disabled","")):(console.log("updating image"),fetch("/api/projectImage/"+e,{method:"POST",body:l,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==t.status?t.json().then((t=>{document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=t.error})):window.location.href="./"})).then((t=>t.json().then((a=>{if(t.ok)return"true"===a.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),document.querySelector(`#info${e}`).setAttribute("disabled",""),void(document.querySelector(`#projectImage${e}`).src=a.imgLocation);401!==t.status?(document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=a.error):window.location.href="./"}))))}function deleteProjectItem(e){fetch("/api/projectData/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#projectItem${e}`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function addProject(e,t,a,l,o,n,r){let i=document.createElement("form"),d=e+"proj";if(i.id="projectItem"+e,i.classList.add("projItem"),i.onsubmit=t=>updateProjectItem(e,t),i.innerHTML=`\n
\n \n \n
\n image preivew of the project\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n \n \n \n `,"true"===t)return document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),void document.querySelector("#projList").prepend(i);document.querySelector("#projList").appendChild(i)}function deletePostItem(e){fetch("/api/blog/post/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#postInfo${e}`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function addPostInfo(e,t,a,l){let o=document.createElement("tr"),n=e+"post";o.id="postInfo"+e,o.innerHTML=`\n \n ${t}\n \n \n ${new Date(a).toLocaleDateString()}\n \n \n ${new Date(l).toLocaleDateString()}\n \n \n \n \n \n `,document.querySelector("#editPost table tbody").appendChild(o)}document.addEventListener("DOMContentLoaded",(()=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok||(window.location.href="./")})),document.querySelector("#dateFromE").max=(new Date).toISOString().split("T")[0],document.querySelector("#dateFromW").max=(new Date).toISOString().split("T")[0],fetch("/api/timelineData/edu").then((e=>e.json().then((t=>{if(e.ok)for(let e=0;ee.json().then((t=>{if(e.ok)for(let e=0;ee.json().then((t=>{e.ok?t.forEach((e=>{addProject(e.ID,1===e.isMainProject?"true":"false",""===e.imgLocation?"../imgs/placeholder.png":e.imgLocation,e.title,e.information,e.projectLink,e.gitLink)})):document.querySelector("#projList").innerHTML="No project data found"})))),fetch("/api/blog/post").then((e=>e.json().then((t=>{e.ok&&(posts=t,t.forEach((e=>{addPostInfo(e.ID,e.title,e.dateCreated,e.dateModified)})))})))),createEditors("CKEditorAddPost","CKEditorEditPost")})),document.querySelector("body").addEventListener("click",(()=>{if(textareaLoaded)return;const e=document.querySelectorAll("main.editor textarea");for(let t=0;t{e.target.style.height="0",e.target.style.height=e.target.scrollHeight+15+"px"};textareaLoaded=!0})),document.querySelector("#navOpen").addEventListener("click",(e=>{document.querySelector("nav.sideNav").style.removeProperty("width"),document.querySelector("main.editor").style.removeProperty("margin-left"),e.target.style.removeProperty("visibility")})),document.querySelector("#navClose").addEventListener("click",(()=>{document.querySelector("nav.sideNav").style.width="0",document.querySelector("main.editor").style.marginLeft="0",document.querySelector("#navOpen").style.visibility="visible"})),document.querySelector("#addEdu").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromE").value),t.append("dateTo",document.querySelector("#dateToE").value),t.append("grade",document.querySelector("#grade").value),t.append("course",document.querySelector("#courseTitle").value),fetch("/api/timelineData/edu",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((a=>{if(e.ok)return addEduData(a.ID,t.get("dateFrom"),t.get("dateTo"),t.get("grade"),t.get("course"),!0),void document.querySelector("#addEdu").reset();401!==e.status?showErrorMessage(a.error,"edu"):window.location.href="./"}))))})),document.querySelector("#addWork").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromW").value),t.append("dateTo",document.querySelector("#dateToW").value),t.append("companyName",document.querySelector("#company").value),t.append("area",document.querySelector("#area").value),t.append("title",document.querySelector("#jobTitle").value),fetch("/api/timelineData/work",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((a=>{if(e.ok){let e=""===t.get("dateTo")?"Present":t.get("dateTo ");return addWorkData(a.ID,t.get("dateFrom"),e,t.get("companyName"),t.get("area"),t.get("title"),!0),void document.querySelector("#addWork").reset()}401!==e.status?showErrorMessage(a.error,"work"):window.location.href="./"}))))})),document.querySelector("#addProj").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("title",document.querySelector("#projTitle").value),t.append("isMainProject",document.querySelector("#isMainProject").checked?"true":"false"),t.append("information",document.querySelector("#projInfo").value),t.append("projectLink",document.querySelector("#projLink").value?document.querySelector("#projLink").value:"N/A"),t.append("gitLink",document.querySelector("#gitLink").value);let a=new FormData;a.append("img",document.querySelector("#projImg").files[0]);let l=0;fetch("/api/projectData",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((o=>{if(e.ok)return"undefined"===a.get("img")?(addProject(o.ID,t.get("isMainProject"),"../imgs/placeholder.png",t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset()):(l=o.ID,fetch("/api/projectImage/"+o.ID,{method:"POST",body:a,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==e.status?showErrorMessage(o.error,"proj"):window.location.href="./"})).then((e=>e.json().then((a=>{if(e.ok)return addProject(l,t.get("isMainProject"),a.imgLocation,t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset();401!==e.status?showErrorMessage(a.error,"proj"):window.location.href="./"}))))))})),document.querySelector("#addPostForm").addEventListener("submit",(e=>{if(e.preventDefault(),""===editors.CKEditorAddPost.getData())return void showErrorMessage("Post body cannot be empty","addPost");let t=new FormData;t.append("title",document.querySelector("#postTitle").value),t.append("featured",document.querySelector("#isFeatured").checked?"1":"0"),t.append("abstract",document.querySelector("#postAbstract").value),t.append("body",editors.CKEditorAddPost.getData()),t.append("bodyText",viewToPlainText(editors.CKEditorAddPost.editing.view.document.getRoot())),t.append("dateCreated",(new Date).toISOString().slice(0,19).replace("T"," ")),t.append("categories",document.querySelector("#postCategories").value.toLowerCase()),t.append("headerImg",document.querySelector("#headerImg").files[0]),fetch("/api/blog/post",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((a=>{if(e.ok)return document.querySelector("#addPostForm").reset(),editors.CKEditorAddPost.setData(""),addPostInfo(a.ID,t.get("title"),t.get("dateCreated"),t.get("dateModified")),void showSuccessMessage("Post added successfully","addPost");401!==e.status?e.json().then((e=>showErrorMessage(e.error,"addPost"))):window.location.href="./"}))))})),document.querySelector("#editPostForm").addEventListener("submit",(e=>{e.preventDefault();let t=document.querySelector("#editPostForm input[type='submit']").id;if(""===t)return void showErrorMessage("Currently not editing any post","editPost");if(""===editors.CKEditorEditPost.getData())return void showErrorMessage("Post body cannot be empty","editPost");let a={};a.title=document.querySelector("#editPostTitle").value,a.featured=document.querySelector("#editIsFeatured").checked?"1":"0",a.abstract=document.querySelector("#editPostAbstract").value,a.body=editors.CKEditorEditPost.getData(),a.bodyText=viewToPlainText(editors.CKEditorEditPost.editing.view.document.getRoot()),a.dateModified=(new Date).toISOString().slice(0,19).replace("T"," "),a.categories=document.querySelector("#editPostCategories").value.toLowerCase();let l=new FormData;l.append("headerImg",document.querySelector("#editHeaderImg").files[0]),fetch("/api/blog/post/"+t,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>{if(e.ok)return"undefined"===l.get("headerImg")?(document.querySelector("#editPostForm").reset(),document.querySelector("#editPostForm input[type='submit']").id="",editors.CKEditorEditPost.setData(""),void showSuccessMessage("Post edited successfully","editPost")):fetch("/api/blog/headerImage/"+t,{method:"POST",body:l,headers:{Authorization:"Bearer "+localStorage.getItem("token")}});401!==e.status?e.json().then((e=>showErrorMessage(e.error,"editPost"))):window.location.href="./"})).then((e=>e.json().then((t=>{if(e.ok)return document.querySelector("#editPostForm").reset(),document.querySelector("#editPostForm input[type='submit']").id="",console.log(),editors.CKEditorEditPost.setData(""),void showSuccessMessage("Post edited successfully","editPost");401!==e.status?showErrorMessage(t.error.message,"editPost"):window.location.href="./"}))))})),document.querySelector("#goToCV").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToCV"),goToPage("curriculumVitae")})),document.querySelector("#goToProjects").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToProjects"),goToPage("projects")})),document.querySelector("#blog").addEventListener("click",(()=>{document.querySelector("nav.sideNav ul li.dropdown ul").classList.toggle("active"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-down"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-right")})),document.querySelector("#goToAddPost").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToAddPost"),goToPage("addPost"),document.querySelector("#blog").classList.add("active")})),document.querySelector("#goToEditPost").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToEditPost"),goToPage("editPost"),document.querySelector("#blog").classList.add("active")})),document.querySelector("#logout").addEventListener("click",(()=>{fetch("/api/user/logout").then((e=>{e.ok&&window.location.reload()}))})),document.querySelector("#eduError .close").addEventListener("click",(()=>document.querySelector("#eduError").classList.toggle("hidden"))),document.querySelector("#workError .close").addEventListener("click",(()=>document.querySelector("#workError").classList.toggle("hidden"))),document.querySelector("#projError .close").addEventListener("click",(()=>document.querySelector("#projError").classList.toggle("hidden"))),document.querySelector("#addPostError .close").addEventListener("click",(()=>document.querySelector("#addPostError").classList.toggle("hidden"))),document.querySelector("#addPostSuccess .close").addEventListener("click",(()=>document.querySelector("#addPostSuccess").classList.toggle("hidden"))),document.querySelector("#editPostError .close").addEventListener("click",(()=>document.querySelector("#editPostError").classList.toggle("hidden"))),document.querySelector("#editPostSuccess .close").addEventListener("click",(()=>document.querySelector("#editPostSuccess").classList.toggle("hidden"))); \ No newline at end of file +let dateOptions={month:"short",year:"numeric"},textareaLoaded=!1,editors={},posts=null;const smallPaddingElements=["figcaption","li"];function goToPage(e){document.querySelectorAll(".editor section").forEach((t=>{t.style.display="none",t.id===e&&(t.style.display="flex")}))}function viewToPlainText(e){let t="";if(e.is("$text")||e.is("$textProxy"))t=e.data;else if(e.is("element","br"))t="\n";else{let a=null;for(const l of e.getChildren()){const e=viewToPlainText(l);a&&(a.is("containerElement")||l.is("containerElement"))&&(smallPaddingElements.includes(a.name)||smallPaddingElements.includes(l.name)?t+="\n":t+="\n\n"),t+=e,a=l}}return t}function addActiveClass(e){document.querySelectorAll("nav.sideNav ul li a").forEach((t=>{t.classList.remove("active"),t.id===e&&t.classList.add("active")}))}function editProjectItem(e){document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}proj`).toggleAttribute("disabled"),document.querySelector(`#info${e}proj`).toggleAttribute("disabled")}function createEditors(...e){e.forEach((e=>{ClassicEditor.create(document.querySelector(`#${e}`),{placeholder:"Write something amazing...",simpleUpload:{uploadUrl:"/api/blog/uploadPostImage",headers:{Authorization:"Bearer "+localStorage.getItem("token")}},style:{definitions:[{name:"Button Primary",element:"a",classes:["btn","btnPrimary"]},{name:"Button Primary",element:"button",classes:["btn","btnPrimary"]}]},codeBlock:{languages:[{language:"plaintext",label:"Plain text"},{language:"abap",label:"ABAP"},{language:"abnf",label:"ABNF"},{language:"actionscript",label:"ActionScript"},{language:"ada",label:"Ada"},{language:"agda",label:"Agda"},{language:"al",label:"AL"},{language:"antlr4",label:"ANTLR4"},{language:"apacheconf",label:"Apache Configuration"},{language:"apex",label:"Apex"},{language:"apl",label:"APL"},{language:"applescript",label:"AppleScript"},{language:"aql",label:"AQL"},{language:"arduino",label:"Arduino"},{language:"arff",label:"ARFF"},{language:"asciidoc",label:"AsciiDoc"},{language:"aspnet",label:"ASP.NET (C#)"},{language:"asm6502",label:"6502 Assembly"},{language:"autohotkey",label:"AutoHotkey"},{language:"autoit",label:"AutoIt"},{language:"bash",label:"Bash"},{language:"basic",label:"BASIC"},{language:"batch",label:"Batch"},{language:"bbcode",label:"BBcode"},{language:"bison",label:"Bison"},{language:"bnf",label:"BNF"},{language:"brainfuck",label:"Brainfuck"},{language:"brightscript",label:"BrightScript"},{language:"bro",label:"Bro"},{language:"c",label:"C"},{language:"concurnas",label:"Concurnas"},{language:"csharp",label:"C#"},{language:"cpp",label:"C++"},{language:"cil",label:"CIL"},{language:"clojure",label:"Clojure"},{language:"cmake",label:"CMake"},{language:"coffeescript",label:"CoffeeScript"},{language:"concurnas",label:"Concurnas"},{language:"crystal",label:"Crystal"},{language:"css-extras",label:"CSS Extras"},{language:"css",label:"CSS"},{language:"d",label:"D"},{language:"dart",label:"Dart"},{language:"dax",label:"DAX"},{language:"dhall",label:"Dhall"},{language:"diff",label:"Diff"},{language:"django",label:"Django/Jinja2"},{language:"dns-zone-file",label:"DNS zone file"},{language:"docker",label:"Docker"},{language:"ebnf",label:"EBNF"},{language:"editorconfig",label:"EditorConfig"},{language:"eiffel",label:"Eiffel"},{language:"ejs",label:"EJS"},{language:"elixir",label:"Elixir"},{language:"elm",label:"Elm"},{language:"etlua",label:"Embedded Lua"},{language:"erb",label:"ERB"},{language:"erlang",label:"Erlang"},{language:"excel-formula",label:"Excel Formula"},{language:"fsharp",label:"F#"},{language:"factor",label:"Factor"},{language:"firestore-security-rules",label:"Firestore security rules"},{language:"flow",label:"Flow"},{language:"fortran",label:"Fortran"},{language:"ftl",label:"FreeMarker Template Language"},{language:"gcode",label:"G-code"},{language:"gdscript",label:"GDScript"},{language:"gedcom",label:"GEDCOM"},{language:"gherkin",label:"Gherkin"},{language:"git",label:"Git"},{language:"glsl",label:"GLSL"},{language:"gml",label:"GameMaker Language"},{language:"go",label:"Go"},{language:"graphql",label:"GraphQL"},{language:"groovy",label:"Groovy"},{language:"haml",label:"Haml"},{language:"handlebars",label:"Handlebars"},{language:"haskell",label:"Haskell"},{language:"haxe",label:"Haxe"},{language:"hcl",label:"HCL"},{language:"hlsl",label:"HLSL"},{language:"http",label:"HTTP"},{language:"hpkp",label:"HTTP Public-Key-Pins"},{language:"hsts",label:"HTTP Strict-Transport-Security"},{language:"ichigojam",label:"IchigoJam"},{language:"icon",label:"Icon"},{language:"ignore",label:"Ignore"},{language:"inform7",label:"Inform 7"},{language:"ini",label:"Ini"},{language:"io",label:"Io"},{language:"j",label:"J"},{language:"java",label:"Java"},{language:"javadoc",label:"JavaDoc"},{language:"javadoclike",label:"JavaDoc-like"},{language:"javascript",label:"JavaScript"},{language:"javastacktrace",label:"Java stack trace"},{language:"jolie",label:"Jolie"},{language:"jq",label:"JQ"},{language:"js-extras",label:"JS Extras"},{language:"js-templates",label:"JS Templates"},{language:"jsdoc",label:"JSDoc"},{language:"json",label:"JSON"},{language:"json5",label:"JSON5"},{language:"jsonp",label:"JSONP"},{language:"jsstacktrace",label:"JS stack trace"},{language:"jsx",label:"React JSX"},{language:"julia",label:"Julia"},{language:"keyman",label:"Keyman"},{language:"kotlin",label:"Kotlin"},{language:"latex",label:"LaTeX"},{language:"latte",label:"Latte"},{language:"less",label:"Less"},{language:"lilypond",label:"LilyPond"},{language:"liquid",label:"Liquid"},{language:"lisp",label:"Lisp"},{language:"livescript",label:"LiveScript"},{language:"llvm",label:"LLVM IR"},{language:"log",label:"Log file"},{language:"lolcode",label:"LOLCODE"},{language:"lua",label:"Lua"},{language:"makefile",label:"Makefile"},{language:"markdown",label:"Markdown"},{language:"markup-templating",label:"Markup templating"},{language:"matlab",label:"MATLAB"},{language:"mel",label:"MEL"},{language:"mizar",label:"Mizar"},{language:"mongodb",label:"MongoDB"},{language:"monkey",label:"Monkey"},{language:"moonscript",label:"MoonScript"},{language:"n1ql",label:"N1QL"},{language:"n4js",label:"N4JS"},{language:"nand2tetris-hdl",label:"Nand To Tetris HDL"},{language:"nasm",label:"NASM"},{language:"neon",label:"NEON"},{language:"nginx",label:"nginx"},{language:"nim",label:"Nim"},{language:"nix",label:"Nix"},{language:"nsis",label:"NSIS"},{language:"objectivec",label:"Objective-C"},{language:"ocaml",label:"OCaml"},{language:"opencl",label:"OpenCL"},{language:"oz",label:"Oz"},{language:"parigp",label:"PARI/GP"},{language:"parser",label:"Parser"},{language:"pascal",label:"Pascal"},{language:"pascaligo",label:"Pascaligo"},{language:"pcaxis",label:"PC-Axis"},{language:"peoplecode",label:"PeopleCode"},{language:"perl",label:"Perl"},{language:"php",label:"PHP"},{language:"phpdoc",label:"PHPDoc"},{language:"php-extras",label:"PHP Extras"},{language:"plsql",label:"PL/SQL"},{language:"powerquery",label:"PowerQuery"},{language:"powershell",label:"PowerShell"},{language:"processing",label:"Processing"},{language:"prolog",label:"Prolog"},{language:"properties",label:".properties"},{language:"protobuf",label:"Protocol Buffers"},{language:"pug",label:"Pug"},{language:"puppet",label:"Puppet"},{language:"pure",label:"Pure"},{language:"purebasic",label:"PureBasic"},{language:"python",label:"Python"},{language:"q",label:"Q (kdb+ database)"},{language:"qml",label:"QML"},{language:"qore",label:"Qore"},{language:"r",label:"R"},{language:"racket",label:"Racket"},{language:"jsx",label:"React JSX"},{language:"tsx",label:"React TSX"},{language:"reason",label:"Reason"},{language:"regex",label:"Regex"},{language:"renpy",label:"Ren'py"},{language:"rest",label:"reST (reStructuredText)"},{language:"rip",label:"Rip"},{language:"roboconf",label:"Roboconf"},{language:"robotframework",label:"Robot Framework"},{language:"ruby",label:"Ruby"},{language:"rust",label:"Rust"},{language:"sas",label:"SAS"},{language:"sass",label:"Sass (Sass)"},{language:"scss",label:"Sass (Scss)"},{language:"scala",label:"Scala"},{language:"scheme",label:"Scheme"},{language:"shell-session",label:"Shell session"},{language:"smali",label:"Smali"},{language:"smalltalk",label:"Smalltalk"},{language:"smarty",label:"Smarty"},{language:"solidity",label:"Solidity (Ethereum)"},{language:"solution-file",label:"Solution file"},{language:"soy",label:"Soy (Closure Template)"},{language:"sparql",label:"SPARQL"},{language:"splunk-spl",label:"Splunk SPL"},{language:"sqf",label:"SQF: Status Quo Function (Arma 3)"},{language:"sql",label:"SQL"},{language:"stan",label:"Stan"},{language:"stata",label:"Stata"},{language:"step21",label:"STEP Part 21"},{language:"stylus",label:"Stylus"},{language:"swift",label:"Swift"},{language:"tap",label:"TAP"},{language:"tcl",label:"Tcl"},{language:"textile",label:"Textile"},{language:"toml",label:"TOML"},{language:"tt2",label:"Template Toolkit 2"},{language:"turtle",label:"Turtle"},{language:"twig",label:"Twig"},{language:"typescript",label:"TypeScript"},{language:"t4-cs",label:"T4 Text Templates (C#)"},{language:"t4-vb",label:"T4 Text Templates (VB)"},{language:"t4-templating",label:"T4 templating"},{language:"unrealscript",label:"UnrealScript"},{language:"vala",label:"Vala"},{language:"vbnet",label:"VB.Net"},{language:"velocity",label:"Velocity"},{language:"verilog",label:"Verilog"},{language:"vhdl",label:"VHDL"},{language:"vim",label:"vim"},{language:"visual-basic",label:"Visual Basic"},{language:"warpscript",label:"WarpScript"},{language:"wasm",label:"WebAssembly"},{language:"wiki",label:"Wiki markup"},{language:"xeora",label:"Xeora"},{language:"xojo",label:"Xojo (REALbasic)"},{language:"xquery",label:"XQuery"},{language:"yaml",label:"YAML"},{language:"zephir",label:"Zephir"}]}}).then((t=>{editors[e]=t})).catch((e=>{console.error("Oops, something went wrong!"),console.error("Please, report the following error on https://github.com/ckeditor/ckeditor5/issues with the build id and the error stack trace:"),console.warn("Build id: 1eo8ioyje2om-vgar4aghypdm"),console.error(e)}))}))}function editPostItem(e){posts.forEach((t=>{t.ID===e&&(document.querySelector("#editPostTitle").value=t.title,document.querySelector("#editIsFeatured").checked=1===t.featured,document.querySelector("#editPostCategories").value=t.categories,document.querySelector("#editPostAbstract").value=t.abstract,editors.CKEditorEditPost.setData(t.body),document.querySelector("#editPostForm input[type='submit']").id=e)}))}function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function showSuccessMessage(e,t){document.querySelector(`#${t}Success`).classList.remove("hidden"),document.querySelector(`#${t}Success div`).innerText=e}function editCVItem(e){if(textareaLoaded=!1,document.querySelector(`#timelineItem${e}`).classList.toggle("editing"),e.includes("e"))return document.querySelector(`#grade${e}`).toggleAttribute("disabled"),void document.querySelector(`#course${e}`).toggleAttribute("disabled");document.querySelector(`#companyName${e}`).toggleAttribute("disabled"),document.querySelector(`#area${e}`).toggleAttribute("disabled"),document.querySelector(`#jobTitle${e}`).toggleAttribute("disabled")}function addEduData(e,t,a,l,o,n=!1){let r=e+"e",i=document.createElement("form");i.id="timelineItem"+r,i.classList.add("timelineItem"),i.onsubmit=t=>updateEduItem(e,t),i.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${new Date(a).toLocaleString("en-gb",dateOptions)}

\n
\n \n \n
\n
\n \n
\n \n \n \n `,n?document.querySelector("#edu").prepend(i):document.getElementById("edu").appendChild(i)}function addWorkData(e,t,a,l,o,n,r=!1){let i=e+"w",d=document.createElement("form");d.id="timelineItem"+i,d.classList.add("timelineItem"),d.onsubmit=t=>updateWorkItem(e,t),d.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${"Present"===a?"Present":new Date(a).toLocaleString("en-gb",dateOptions)}

\n
\n \n -\n \n
\n
\n \n
\n \n \n \n\t`,r?document.querySelector("#work").prepend(d):document.getElementById("work").appendChild(d)}function updateEduItem(e,t){t.preventDefault();let a={};a.dateFrom=document.querySelector(`#dateFrom${e}e`).value,a.dateTo=document.querySelector(`#dateTo${e}e`).value,a.grade=document.querySelector(`#grade${e}e`).value,a.course=document.querySelector(`#course${e}e`).value,fetch("/api/timelineData/edu/"+e,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}e`).innerHTML=new Date(document.querySelector(`#dateFrom${e}e`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}e`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}e`).classList.toggle("editing"),document.querySelector(`#grade${e}e`).setAttribute("disabled",""),void document.querySelector(`#course${e}e`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#eduError${e}e`).classList.remove("hidden"),document.querySelector(`#eduError${e}e div`).innerHTML=t.error})):window.location.href="./"}))}function updateWorkItem(e,t){t.preventDefault();let a={};a.dateFrom=document.querySelector(`#dateFrom${e}w`).value,a.dateTo=document.querySelector(`#dateTo${e}w`).value,a.companyName=document.querySelector(`#companyName${e}w`).value,a.area=document.querySelector(`#area${e}w`).value,a.title=document.querySelector(`#jobTitle${e}w`).value,fetch("/api/timelineData/work/"+e,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}w`).innerHTML=new Date(document.querySelector(`#dateFrom${e}w`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}w`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}w`).classList.toggle("editing"),document.querySelector(`#companyName${e}w`).setAttribute("disabled",""),document.querySelector(`#area${e}w`).setAttribute("disabled",""),void document.querySelector(`#jobTitle${e}w`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#workError${e}w`).classList.remove("hidden"),document.querySelector(`#workError${e}w div`).innerHTML=t.error})):window.location.href="./"}))}function deleteEduItem(e){fetch("/api/timelineData/edu/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}e`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function deleteWorkItem(e){fetch("/api/timelineData/work/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}w`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function updateProjectItem(e,t){t.preventDefault();let a={};a.title=document.querySelector(`#title${e}`).value,a.isMainProject=document.querySelector(`#isMainProject${e}`).checked?"true":"false",a.information=document.querySelector(`#info${e}`).value,a.projectLink=document.querySelector(`#viewProj${e}`).value,a.gitLink=document.querySelector(`#git${e}`).value;let l=new FormData;l.append("img",document.querySelector(`#img${e}`).files[0]),fetch("/api/projectData/"+e,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return"undefined"===l.get("img")?("true"===a.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),void document.querySelector(`#info${e}`).setAttribute("disabled","")):(console.log("updating image"),fetch("/api/projectImage/"+e,{method:"POST",body:l,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==t.status?t.json().then((t=>{document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=t.error})):window.location.href="./"})).then((t=>t.json().then((a=>{if(t.ok)return"true"===a.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),document.querySelector(`#info${e}`).setAttribute("disabled",""),void(document.querySelector(`#projectImage${e}`).src=a.imgLocation);401!==t.status?(document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=a.error):window.location.href="./"}))))}function deleteProjectItem(e){fetch("/api/projectData/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#projectItem${e}`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function addProject(e,t,a,l,o,n,r){let i=document.createElement("form"),d=e+"proj";if(i.id="projectItem"+e,i.classList.add("projItem"),i.onsubmit=t=>updateProjectItem(e,t),i.innerHTML=`\n
\n \n \n
\n image preivew of the project\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n \n \n \n `,"true"===t)return document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),void document.querySelector("#projList").prepend(i);document.querySelector("#projList").appendChild(i)}function deletePostItem(e){fetch("/api/blog/post/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#postInfo${e}`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function addPostInfo(e,t,a,l){let o=document.createElement("tr"),n=e+"post";o.id="postInfo"+e,o.innerHTML=`\n \n ${t}\n \n \n ${new Date(a).toLocaleDateString()}\n \n \n ${new Date(l).toLocaleDateString()}\n \n \n \n \n \n `,document.querySelector("#editPost table tbody").appendChild(o)}document.addEventListener("DOMContentLoaded",(()=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok||(window.location.href="./")})),document.querySelector("#dateFromE").max=(new Date).toISOString().split("T")[0],document.querySelector("#dateFromW").max=(new Date).toISOString().split("T")[0],fetch("/api/timelineData/edu").then((e=>e.json().then((t=>{if(e.ok)for(let e=0;ee.json().then((t=>{if(e.ok)for(let e=0;ee.json().then((t=>{e.ok?t.forEach((e=>{addProject(e.ID,1===e.isMainProject?"true":"false",""===e.imgLocation?"../imgs/placeholder.png":e.imgLocation,e.title,e.information,e.projectLink,e.gitLink)})):document.querySelector("#projList").innerHTML="No project data found"})))),fetch("/api/blog/post").then((e=>e.json().then((t=>{e.ok&&(posts=t,t.forEach((e=>{addPostInfo(e.ID,e.title,e.dateCreated,e.dateModified)})))})))),createEditors("CKEditorAddPost","CKEditorEditPost","CKEditorNewsletter")})),document.querySelector("body").addEventListener("click",(()=>{if(textareaLoaded)return;const e=document.querySelectorAll("main.editor textarea");for(let t=0;t{e.target.style.height="0",e.target.style.height=e.target.scrollHeight+15+"px"};textareaLoaded=!0})),document.querySelector("#navOpen").addEventListener("click",(e=>{document.querySelector("nav.sideNav").style.removeProperty("width"),document.querySelector("main.editor").style.removeProperty("margin-left"),e.target.style.removeProperty("visibility")})),document.querySelector("#navClose").addEventListener("click",(()=>{document.querySelector("nav.sideNav").style.width="0",document.querySelector("main.editor").style.marginLeft="0",document.querySelector("#navOpen").style.visibility="visible"})),document.querySelector("#addEdu").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromE").value),t.append("dateTo",document.querySelector("#dateToE").value),t.append("grade",document.querySelector("#grade").value),t.append("course",document.querySelector("#courseTitle").value),fetch("/api/timelineData/edu",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((a=>{if(e.ok)return addEduData(a.ID,t.get("dateFrom"),t.get("dateTo"),t.get("grade"),t.get("course"),!0),void document.querySelector("#addEdu").reset();401!==e.status?showErrorMessage(a.error,"edu"):window.location.href="./"}))))})),document.querySelector("#addWork").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromW").value),t.append("dateTo",document.querySelector("#dateToW").value),t.append("companyName",document.querySelector("#company").value),t.append("area",document.querySelector("#area").value),t.append("title",document.querySelector("#jobTitle").value),fetch("/api/timelineData/work",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((a=>{if(e.ok){let e=""===t.get("dateTo")?"Present":t.get("dateTo ");return addWorkData(a.ID,t.get("dateFrom"),e,t.get("companyName"),t.get("area"),t.get("title"),!0),void document.querySelector("#addWork").reset()}401!==e.status?showErrorMessage(a.error,"work"):window.location.href="./"}))))})),document.querySelector("#addProj").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("title",document.querySelector("#projTitle").value),t.append("isMainProject",document.querySelector("#isMainProject").checked?"true":"false"),t.append("information",document.querySelector("#projInfo").value),t.append("projectLink",document.querySelector("#projLink").value?document.querySelector("#projLink").value:"N/A"),t.append("gitLink",document.querySelector("#gitLink").value);let a=new FormData;a.append("img",document.querySelector("#projImg").files[0]);let l=0;fetch("/api/projectData",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((o=>{if(e.ok)return"undefined"===a.get("img")?(addProject(o.ID,t.get("isMainProject"),"../imgs/placeholder.png",t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset()):(l=o.ID,fetch("/api/projectImage/"+o.ID,{method:"POST",body:a,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==e.status?showErrorMessage(o.error,"proj"):window.location.href="./"})).then((e=>e.json().then((a=>{if(e.ok)return addProject(l,t.get("isMainProject"),a.imgLocation,t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset();401!==e.status?showErrorMessage(a.error,"proj"):window.location.href="./"}))))))})),document.querySelector("#addPostForm").addEventListener("submit",(e=>{if(e.preventDefault(),""===editors.CKEditorAddPost.getData())return void showErrorMessage("Post body cannot be empty","addPost");let t=new FormData;t.append("title",document.querySelector("#postTitle").value),t.append("featured",document.querySelector("#isFeatured").checked?"1":"0"),t.append("abstract",document.querySelector("#postAbstract").value),t.append("body",editors.CKEditorAddPost.getData()),t.append("bodyText",viewToPlainText(editors.CKEditorAddPost.editing.view.document.getRoot())),t.append("dateCreated",(new Date).toISOString().slice(0,19).replace("T"," ")),t.append("categories",document.querySelector("#postCategories").value.toLowerCase()),t.append("headerImg",document.querySelector("#headerImg").files[0]),fetch("/api/blog/post",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((a=>{if(e.ok)return document.querySelector("#addPostForm").reset(),editors.CKEditorAddPost.setData(""),addPostInfo(a.ID,t.get("title"),t.get("dateCreated"),t.get("dateModified")),void showSuccessMessage("Post added successfully","addPost");401!==e.status?e.json().then((e=>showErrorMessage(e.error,"addPost"))):window.location.href="./"}))))})),document.querySelector("#editPostForm").addEventListener("submit",(e=>{e.preventDefault();let t=document.querySelector("#editPostForm input[type='submit']").id;if(""===t)return void showErrorMessage("Currently not editing any post","editPost");if(""===editors.CKEditorEditPost.getData())return void showErrorMessage("Post body cannot be empty","editPost");let a={};a.title=document.querySelector("#editPostTitle").value,a.featured=document.querySelector("#editIsFeatured").checked?"1":"0",a.abstract=document.querySelector("#editPostAbstract").value,a.body=editors.CKEditorEditPost.getData(),a.bodyText=viewToPlainText(editors.CKEditorEditPost.editing.view.document.getRoot()),a.dateModified=(new Date).toISOString().slice(0,19).replace("T"," "),a.categories=document.querySelector("#editPostCategories").value.toLowerCase();let l=new FormData;l.append("headerImg",document.querySelector("#editHeaderImg").files[0]),fetch("/api/blog/post/"+t,{method:"PATCH",body:JSON.stringify(a),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>{if(e.ok)return"undefined"===l.get("headerImg")?(document.querySelector("#editPostForm").reset(),document.querySelector("#editPostForm input[type='submit']").id="",editors.CKEditorEditPost.setData(""),void showSuccessMessage("Post edited successfully","editPost")):fetch("/api/blog/headerImage/"+t,{method:"POST",body:l,headers:{Authorization:"Bearer "+localStorage.getItem("token")}});401!==e.status?e.json().then((e=>showErrorMessage(e.error,"editPost"))):window.location.href="./"})).then((e=>e.json().then((t=>{if(e.ok)return document.querySelector("#editPostForm").reset(),document.querySelector("#editPostForm input[type='submit']").id="",console.log(),editors.CKEditorEditPost.setData(""),void showSuccessMessage("Post edited successfully","editPost");401!==e.status?showErrorMessage(t.error,"editPost"):window.location.href="./"}))))})),document.querySelector("#sendNewsletterForm").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("subject",document.querySelector("#newsletterSubject").value),t.append("message",editors.CKEditorNewsletter.getData()),fetch("/api/blog/newsletter",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((t=>{if(e.ok)return document.querySelector("#sendNewsletterForm").reset(),editors.CKEditorNewsletter.setData(""),void showSuccessMessage("Newsletter sent successfully","newsletter");401!==e.status?showErrorMessage(t.error,"newsletter"):window.location.href="./"}))))})),document.querySelector("#goToCV").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToCV"),goToPage("curriculumVitae")})),document.querySelector("#goToProjects").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToProjects"),goToPage("projects")})),document.querySelector("#blog").addEventListener("click",(()=>{document.querySelector("nav.sideNav ul li.dropdown ul").classList.toggle("active"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-down"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-right")})),document.querySelector("#goToAddPost").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToAddPost"),goToPage("addPost"),document.querySelector("#blog").classList.add("active")})),document.querySelector("#goToEditPost").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToEditPost"),goToPage("editPost"),document.querySelector("#blog").classList.add("active")})),document.querySelector("#goToNewsletter").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToNewsletter"),goToPage("newsletter")})),document.querySelector("#logout").addEventListener("click",(()=>{fetch("/api/user/logout").then((e=>{e.ok&&window.location.reload()}))})),document.querySelector("#eduError .close").addEventListener("click",(()=>document.querySelector("#eduError").classList.toggle("hidden"))),document.querySelector("#workError .close").addEventListener("click",(()=>document.querySelector("#workError").classList.toggle("hidden"))),document.querySelector("#projError .close").addEventListener("click",(()=>document.querySelector("#projError").classList.toggle("hidden"))),document.querySelector("#addPostError .close").addEventListener("click",(()=>document.querySelector("#addPostError").classList.toggle("hidden"))),document.querySelector("#addPostSuccess .close").addEventListener("click",(()=>document.querySelector("#addPostSuccess").classList.toggle("hidden"))),document.querySelector("#editPostError .close").addEventListener("click",(()=>document.querySelector("#editPostError").classList.toggle("hidden"))),document.querySelector("#editPostSuccess .close").addEventListener("click",(()=>document.querySelector("#editPostSuccess").classList.toggle("hidden"))); \ No newline at end of file diff --git a/dist/editor/js/index.js b/dist/editor/js/index.js index 342abca..2382d12 100644 --- a/dist/editor/js/index.js +++ b/dist/editor/js/index.js @@ -1 +1 @@ -function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function switchView(e,t){document.querySelector(e).classList.toggle("shown"),setTimeout((()=>document.querySelector(e).style.transform="translateX(150vw)"),500),setTimeout((()=>document.querySelector(e).style.display="none"),500),setTimeout((()=>document.querySelector(t).style.removeProperty("display")),200),setTimeout((()=>document.querySelector(t).classList.toggle("shown")),300),setTimeout((()=>document.querySelector(t).style.removeProperty("transform")),400)}document.addEventListener("DOMContentLoaded",(e=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok&&(window.location.href="./editor.html")}))})),document.querySelector("#login form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;if(e.target.username.value.length>0&&e.target.password.value.length>0)return t.append("username",e.target.username.value),t.append("password",e.target.password.value),void fetch("/api/user/login",{method:"POST",body:t}).then((e=>e.json().then((t=>{if(e.ok)return localStorage.setItem("token",t.token),void(window.location.href="./editor.html");400!==e.status?showErrorMessage("Invalid username or password.","login"):showErrorMessage("Please type in a username and password.","login")}))));showErrorMessage("Please type in a username and password.","login")})),document.querySelector("#loginError .close").addEventListener("click",(()=>document.querySelector("#loginError").classList.toggle("hidden"))),document.querySelector("#resetError .close").addEventListener("click",(()=>document.querySelector("#resetError").classList.toggle("hidden"))),document.querySelector("#changeError .close").addEventListener("click",(()=>document.querySelector("#changeError").classList.toggle("hidden"))),document.querySelectorAll("form i.fa-eye").forEach((e=>{e.addEventListener("click",(e=>{if("password"===e.target.previousElementSibling.type)return e.target.previousElementSibling.type="text",e.target.classList.remove("fa-eye"),void e.target.classList.add("fa-eye-slash");e.target.previousElementSibling.type="password",e.target.classList.remove("fa-eye-slash"),e.target.classList.add("fa-eye")}))})),document.querySelector("#resetPwd").addEventListener("click",(()=>{switchView("#login","#resetPassword")})),document.querySelector("#loginBtn").addEventListener("click",(()=>{switchView("#resetPassword","#login")})),document.querySelector("#resetPassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.email?(window.email=e.target.email.value,t.append("email",e.target.email.value),fetch(`/api/user/checkResetEmail/${e.target.email.value}`).then((e=>{e.ok&&switchView("#resetPassword","#checkResetCode"),showErrorMessage("Invalid email.","reset")}))):showErrorMessage("Please type in your email.","reset")})),document.querySelector("#checkResetCode form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.code.value?(t.append("code",e.target.code.value),fetch(`/api/user/checkResetCode/${e.target.code.value}`).then((e=>{e.ok&&switchView("#checkResetCode","#changePassword"),showErrorMessage("Invalid code.","resetCode")}))):showErrorMessage("Please type in your reset code.","check")})),document.querySelector("#changePassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.pass.value||""!==e.target.rePass.value?e.target.pass.value===e.target.rePass.value?(t.append("password",e.target.pass.value),fetch("/api/user/changePassword",{method:"POST",body:t}).then((e=>{e.ok&&switchView("#changePassword","#login"),showErrorMessage("Something went wrong.","change")}))):showErrorMessage("Passwords do not match.","change"):showErrorMessage("Please type in a new password.","change")})); \ No newline at end of file +function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function switchView(e,t){document.querySelector(e).classList.toggle("shown"),setTimeout((()=>document.querySelector(e).style.transform="translateX(150vw)"),500),setTimeout((()=>document.querySelector(e).style.display="none"),500),setTimeout((()=>document.querySelector(t).style.removeProperty("display")),200),setTimeout((()=>document.querySelector(t).classList.toggle("shown")),300),setTimeout((()=>document.querySelector(t).style.removeProperty("transform")),400)}document.addEventListener("DOMContentLoaded",(e=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok&&(window.location.href="./editor.html")}))})),document.querySelector("#login form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;if(e.target.username.value.length>0&&e.target.password.value.length>0)return t.append("username",e.target.username.value),t.append("password",e.target.password.value),void fetch("/api/user/login",{method:"POST",body:t}).then((e=>e.json().then((t=>{if(e.ok)return localStorage.setItem("token",t.token),void(window.location.href="./editor.html");400!==e.status?401!==e.status?showErrorMessage(t.error,"login"):showErrorMessage("Invalid username or password.","login"):showErrorMessage("Please type in a username and password.","login")}))));showErrorMessage("Please type in a username and password.","login")})),document.querySelector("#loginError .close").addEventListener("click",(()=>document.querySelector("#loginError").classList.toggle("hidden"))),document.querySelector("#resetError .close").addEventListener("click",(()=>document.querySelector("#resetError").classList.toggle("hidden"))),document.querySelector("#changeError .close").addEventListener("click",(()=>document.querySelector("#changeError").classList.toggle("hidden"))),document.querySelectorAll("form i.fa-eye").forEach((e=>{e.addEventListener("click",(e=>{if("password"===e.target.previousElementSibling.type)return e.target.previousElementSibling.type="text",e.target.classList.remove("fa-eye"),void e.target.classList.add("fa-eye-slash");e.target.previousElementSibling.type="password",e.target.classList.remove("fa-eye-slash"),e.target.classList.add("fa-eye")}))})),document.querySelector("#resetPwd").addEventListener("click",(()=>{switchView("#login","#resetPassword")})),document.querySelector("#loginBtn").addEventListener("click",(()=>{switchView("#resetPassword","#login")})),document.querySelector("#resetPassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.email?(window.email=e.target.email.value,t.append("email",e.target.email.value),fetch(`/api/user/checkResetEmail/${e.target.email.value}`).then((e=>{e.ok&&switchView("#resetPassword","#checkResetCode"),showErrorMessage("Invalid email.","reset")}))):showErrorMessage("Please type in your email.","reset")})),document.querySelector("#checkResetCode form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.code.value?(t.append("code",e.target.code.value),fetch(`/api/user/checkResetCode/${e.target.code.value}`).then((e=>{e.ok&&switchView("#checkResetCode","#changePassword"),showErrorMessage("Invalid code.","resetCode")}))):showErrorMessage("Please type in your reset code.","check")})),document.querySelector("#changePassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.pass.value||""!==e.target.rePass.value?e.target.pass.value===e.target.rePass.value?(t.append("password",e.target.pass.value),fetch("/api/user/changePassword",{method:"POST",body:t}).then((e=>{e.ok&&switchView("#changePassword","#login"),showErrorMessage("Something went wrong.","change")}))):showErrorMessage("Passwords do not match.","change"):showErrorMessage("Please type in a new password.","change")})); \ No newline at end of file diff --git a/src/api/blog/blogData.php b/src/api/blog/blogData.php index 3221e58..459442c 100644 --- a/src/api/blog/blogData.php +++ b/src/api/blog/blogData.php @@ -8,11 +8,15 @@ use DOMDocument; use PDO; use Psr\Http\Message\UploadedFileInterface; use DonatelloZa\RakePlus\RakePlus; -use function DI\string; +use PHPMailer\PHPMailer\PHPMailer; +use PHPMailer\PHPMailer\Exception; +use function api\utils\dbConn; +use function api\utils\getEmailPassword; use const api\utils\feedGenerator\ATOM; use const api\utils\feedGenerator\RSS2; + require_once __DIR__ . "/../utils/config.php"; require_once __DIR__ . "/../utils/imgUtils.php"; require_once __DIR__ . "/../utils/feedGenerator/FeedWriter.php"; @@ -254,10 +258,10 @@ class blogData * @param string $dateCreated - Date the blog post was created * @param bool $featured - Whether the blog post is featured or not * @param string $categories - Categories of the blog post - * @param UploadedFileInterface $headerImg - Header image of the blog post + * @param UploadedFileInterface|null $headerImg - Header image of the blog post * @return int|string - ID of the blog post or error message */ - public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string + public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface|null $headerImg): int|string { $conn = dbConn(); $folderID = uniqid(); @@ -289,6 +293,13 @@ class blogData $keywords = implode(", ", RakePlus::create($bodyText)->keywords()); + $latest = $this->getLatestBlogPost(); + $prevTitle = $latest["title"]; + $prevAbstract = $latest["abstract"]; + $prevHeaderImage = $latest["headerImg"]; + + $headerImage = $targetFile["imgLocation"]; + $stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, bodyText, categories, keywords, folderID) VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :bodyText, :categories, :keywords, :folderID);"); $stmt->bindParam(":title", $title); @@ -296,7 +307,7 @@ class blogData $stmt->bindParam(":dateModified", $dateCreated); $isFeatured = $featured ? 1 : 0; $stmt->bindParam(":featured", $isFeatured); - $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); + $stmt->bindParam(":headerImg", $headerImage); $stmt->bindParam(":abstract", $abstract); $stmt->bindParam(":body", $newBody); $stmt->bindParam(":bodyText", $bodyText); @@ -304,12 +315,267 @@ class blogData $stmt->bindParam(":keywords", $keywords); $stmt->bindParam(":folderID", $folderID); - if ($stmt->execute()) + if (!$stmt->execute()) { - return intval($conn->lastInsertId()); + return "Error, couldn't create post"; } - return "Error, couldn't create post"; + $stmtEmails = $conn->prepare("SELECT email FROM newsletter;"); + $stmtEmails->execute(); + $emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC); + + $emailBody = << + + + + Rohit Pai's blog + + + +
+
+

Hey, I've got a new post!

+
+ +
+
+
+

latest post

+
+ header image of the latest post +
+ +
+

$title

+

$abstract

+ See Post +
+
+ +
+

in case you missed the previous post

+
+ header image of the previous post +
+ +
+

$prevTitle

+

$prevAbstract

+ See Post +
+
+
+ + + +EOD; + + foreach ($emails as $email) + { + $this->sendMail($email["email"], $emailBody, "Hey, Rohit's blog has a new post!"); + } + + return intval($conn->lastInsertId()); + } /** @@ -616,12 +882,12 @@ class blogData foreach ($posts as $post) { $items[] = array( - "id" => string($post["ID"]), + "id" => strval($post["ID"]), "url" => "https://rohitpai.co.uk/blog/post/" . rawurlencode($post["title"]) . "#disqus_thread", "title" => $post["title"], "date_published" => date($post["dateCreated"]), "date_modified" => date($post["dateModified"]), -// "description" => $post["abstract"], + "description" => $post["abstract"], "banner_image" => "https://rohitpai.co.uk/" . rawurlencode($post["headerImg"]), "content_html" => $post["body"] ); @@ -655,4 +921,572 @@ class blogData return $feed; } + + /** + * Add an email to the newsletter and send welcome email + * @param string $email - Email to add to the newsletter + * @return string|array - Success or error message + */ + public function addNewsletterEmail(string $email): string|array + { + $conn = dbConn(); + $stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;"); + $stmtCheckEmail->bindParam(":email", $email); + $stmtCheckEmail->execute(); + $result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC); + + if ($result) + { + return "Email already exists"; + } + + $stmt = $conn->prepare("INSERT INTO newsletter (email) VALUES (:email);"); + $stmt->bindParam(":email", $email); + $stmt->execute(); + + $body = << + + + + Rohit Pai's blog + + + +
+
+

hello from rohit

+
+ +
+
+
+

hey there, i'm rohit!

+
+
+

What to Expect

+

You'll get an email from me everytime I make a new post to my blog. Sometimes, you may get a special + email on occasion, where I talk about something interesting that's not worth a full on post but I + Still want to tell you about it.

+

Don't worry, I won't spam you with emails. Well, thanks for signing up to my newsletter, hopefully + you'll hear from me soon!

+

P.S. Please consider adding this email address rohit@rohitpai.co.uk to your emails + contact list so that my emails won't get sent to span, thanks.

+
+
+
+ + + + EOD; + + return $this->sendMail($email, $body, "Hello from Rohit!"); + + } + + /** + * Send an email to the given email address + * @param string $email - Email address to send the email to + * @param string $body - Body of the email + * @param string $subject - Subject of the email + * @return string|string[] + */ + public function sendMail(string $email, string $body, string $subject): string|array + { + $mail = new PHPMailer(true); + try + { + $mail->isSMTP(); + $mail->Host = "smtp.hostinger.com"; + $mail->SMTPAuth = true; + $mail->Username = "rohit@rohitpai.co.uk"; + $mail->Password = getEmailPassword(); + $mail->SMTPSecure = "tls"; + $mail->Port = 587; + $mail->setFrom("rohit@rohitpai.co.uk", "Rohit Pai"); + $mail->addAddress($email); + $mail->isHTML(); + $mail->Subject = $subject; + $mail->Body = $body; + $mail->send(); + return "success"; + } + catch (Exception $e) + { + return array("errorMessage" => "Error, couldn't send email because of " . $e); + } + } + + /** + * @param string $email - Email to delete from the newsletter + * @return string - Success or error message + */ + public function deleteNewsletterEmail(string $email): string + { + $conn = dbConn(); + + $stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;"); + $stmtCheckEmail->bindParam(":email", $email); + $stmtCheckEmail->execute(); + $result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "email not found"; + } + + $stmt = $conn->prepare("DELETE FROM newsletter WHERE email = :email;"); + $stmt->bindParam(":email", $email); + $stmt->execute();; + + return "success"; + } + + /** + * @param string $subject - Subject of the newsletter + * @param string $message - Message content + * @return array|string - Success or error message + */ + public function sendNewsletter(string $subject, string $message): array|string + { + $conn = dbConn(); + $stmtEmails = $conn->prepare("SELECT email FROM newsletter;"); + $stmtEmails->execute(); + $emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC); + $msg = ""; + + $body = << + + + + Rohit Pai's blog + + + +
+
+

a surprise hello from rohit

+
+ +
+
+
+

$subject

+
+ $message +
+
+
+ + + + EOD; + + + foreach ($emails as $email) + { + $msg = $this->sendMail($email["email"], $body, $subject); + if (is_array($msg)) + { + return $msg; + } + } + + return $msg; + } } \ No newline at end of file diff --git a/src/api/blog/blogRoutes.php b/src/api/blog/blogRoutes.php index 975052c..320c9cf 100644 --- a/src/api/blog/blogRoutes.php +++ b/src/api/blog/blogRoutes.php @@ -269,12 +269,38 @@ class blogRoutes implements routesInterface return $response; }); + $app->delete("/blog/newsletter/{email}", function (Request $request, Response $response, $args) + { + if ($args["email"] == null) + { + $response->getBody()->write(json_encode(array("error" => "Please provide an email"))); + return $response->withStatus(400); + } + + $message = $this->blogData->deleteNewsletterEmail($args["email"]); + + if ($message === "email not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, email not found"))); + return $response->withStatus(404); + } + + if ($message === "error") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, something went wrong"))); + return $response->withStatus(500); + } + + return $response; + }); + $app->post("/blog/post", function (Request $request, Response $response) { $data = $request->getParsedBody(); $files = $request->getUploadedFiles(); - $headerImg = $files["headerImg"]; - if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"])) + if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"])) { // uh oh sent some empty data $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); @@ -288,6 +314,11 @@ class blogRoutes implements routesInterface return $response->withStatus(400); } + if (array_key_exists("headerImg", $files)) + { + $headerImg = $files["headerImg"]; + } + if (empty($files["headerImg"])) { $headerImg = null; @@ -339,19 +370,66 @@ class blogRoutes implements routesInterface if (empty($files)) { // uh oh sent some empty data - $response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent")))); + $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); return $response->withStatus(400); } $message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]); if (!is_array($message)) { - $response->getBody()->write(json_encode(array("error" => array("message" => $message)))); + $response->getBody()->write(json_encode(array("error" => $message))); return $response->withStatus(500); } $response->getBody()->write(json_encode($message)); return $response->withStatus(201); }); + + $app->post("/blog/newsletter", function (Request $request, Response $response) + { + $data = $request->getParsedBody(); + if (empty($data["subject"]) || empty($data["message"])) + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); + return $response->withStatus(400); + } + + $message = $this->blogData->sendNewsletter(strtolower($data["subject"]), $data["message"]); + if (is_array($message)) + { + $response->getBody()->write(json_encode(array("error" => "Error, something went wrong"))); + return $response->withStatus(500); + } + + $response->getBody()->write(json_encode(array("message" => "Message sent"))); + return $response->withStatus(201); + }); + + $app->post("/blog/newsletter/{email}", function (Request $request, Response $response, $args) + { + if ($args["email"] == null) + { + $response->getBody()->write(json_encode(array("error" => "Please provide an email"))); + return $response->withStatus(400); + } + + $message = $this->blogData->addNewsletterEmail($args["email"]); + if ($message === "Email already exists") + { + $response->getBody()->write(json_encode(array("message" => "exists"))); + return $response->withStatus(409); + } + + if (is_array($message) || !$message || $message === "error") + { + $response->getBody()->write(json_encode(array("message" => "Something went wrong"))); + return $response->withStatus(500); + } + + $response->getBody()->write(json_encode(array("message" => "Thanks for signing up!"))); + return $response->withStatus(201); + }); + } } \ No newline at end of file diff --git a/src/api/project/projectData.php b/src/api/project/projectData.php index 8a81b2c..fb1c631 100644 --- a/src/api/project/projectData.php +++ b/src/api/project/projectData.php @@ -5,6 +5,7 @@ namespace api\project; use api\utils\imgUtils; use PDO; use Psr\Http\Message\UploadedFileInterface; +use function api\utils\dbConn; require_once __DIR__ . "/../utils/config.php"; require_once __DIR__ . "/../utils/imgUtils.php"; diff --git a/src/api/timeline/timelineData.php b/src/api/timeline/timelineData.php index f646e5e..9bb40e4 100644 --- a/src/api/timeline/timelineData.php +++ b/src/api/timeline/timelineData.php @@ -3,6 +3,7 @@ namespace api\timeline; use PDO; +use function api\utils\dbConn; require_once __DIR__ . "/../utils/config.php"; diff --git a/src/api/user/userData.php b/src/api/user/userData.php index d63d785..0e42372 100644 --- a/src/api/user/userData.php +++ b/src/api/user/userData.php @@ -4,6 +4,8 @@ namespace api\user; use Firebase\JWT\JWT; use PDO; +use function api\utils\dbConn; +use function api\utils\getSecretKey; require_once __DIR__ . "/../utils/config.php"; diff --git a/src/api/utils/middleware.php b/src/api/utils/middleware.php index 14d7627..71c851c 100644 --- a/src/api/utils/middleware.php +++ b/src/api/utils/middleware.php @@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; use Slim\Psr7\Response; +use Throwable; use Tuupola\Middleware\JwtAuthentication; use Tuupola\Middleware\JwtAuthentication\RequestMethodRule; use Tuupola\Middleware\JwtAuthentication\RequestPathRule; @@ -84,8 +85,8 @@ class middleware $app->add(new JwtAuthentication([ "rules" => [ new RequestPathRule([ - "path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"], - "ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"] + "path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"], + "ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"] ]), new RequestMethodRule([ "ignore" => ["OPTIONS", "GET"] @@ -133,8 +134,27 @@ class middleware return $response; } }); - $app->addErrorMiddleware(true, true, true); + + $errorMiddleware = $app->addErrorMiddleware(true, true, true); + + + $errorHandler = $errorMiddleware->getDefaultErrorHandler(); + + $errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception, + bool $displayErrorDetails, + bool $logErrors, + bool $logErrorDetails + ) use ($app, $errorHandler) + { + $statusCode = $exception->getCode() ?: 500; + + // Create a JSON response with the error message + $response = $app->getResponseFactory()->createResponse($statusCode); + $response->getBody()->write(json_encode(['error' => $exception->getMessage()])); + + return $response; + }); } } \ No newline at end of file diff --git a/src/blog/css/blogPosts.css b/src/blog/css/blogPosts.css index 4ef1a2b..4f7a6ba 100644 --- a/src/blog/css/blogPosts.css +++ b/src/blog/css/blogPosts.css @@ -151,7 +151,7 @@ div.otherPosts a, div.feeds a { padding: 0.5em 1em; } -div.newsletter form input[type="submit"] { +div.newsletter div.form input[type="submit"] { margin-top: 1em; padding: 0.5em 1em; } diff --git a/src/blog/css/category.css b/src/blog/css/category.css index b315cb1..31992f3 100644 --- a/src/blog/css/category.css +++ b/src/blog/css/category.css @@ -12,11 +12,12 @@ section.catPosts .largePost { section.categories { display: flex; flex-direction: row; - justify-content: center; + justify-content: flex-start; align-items: center; flex-wrap: wrap; width: 100%; margin-bottom: 5em; + row-gap: 1em; } section.categories .btnContainer { diff --git a/src/blog/css/home.css b/src/blog/css/home.css index 22853d0..91e326b 100644 --- a/src/blog/css/home.css +++ b/src/blog/css/home.css @@ -108,7 +108,7 @@ section.largePost .outerContent .postContent a { align-self: flex-end; } -#main .error { +#main .errorFof { display: table; width: 100%; height: 100vh; diff --git a/src/blog/css/main.css b/src/blog/css/main.css index 200525a..1d12aac 100644 --- a/src/blog/css/main.css +++ b/src/blog/css/main.css @@ -23,6 +23,44 @@ font-weight: bold; } +.modal-container { + display: block; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); /* Background color for the entire screen */ + box-sizing: border-box; /* Include padding and border in the element's total width and height */ +} + +.modal-container.hidden { + display: none; +} + +.modal { + position: fixed; + right: 0; + bottom: 0; + width: 40%; + max-width: 550px; + height: auto; + max-height: 70vh; + overflow: auto; + margin: 1.25em; + padding: 1.25em; + box-sizing: border-box; /* Include padding and border in the element's total width and height */ +} + +.modal-content { + background-color: #DDDDDD; + margin: auto; + padding: 20px; + border: 5px solid var(--primaryHover); + width: 100%; + box-shadow: 0 6px 4px 0 var(--mutedBlack); +} + /**** Media Queries *****/ @media screen and (max-width: 90em) { diff --git a/src/blog/index.html b/src/blog/index.html index 4dbe070..6a266ba 100644 --- a/src/blog/index.html +++ b/src/blog/index.html @@ -72,6 +72,22 @@ + +