1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-09-15 04:05:32 -04:00

v5: Revenge of the JavaScript 🦸 (#711)

Hugo ➡️ Next.js
This commit is contained in:
2021-12-30 08:18:41 -05:00
committed by GitHub
parent b7505fa260
commit 9979e1bf3f
577 changed files with 8019 additions and 11864 deletions

View File

@@ -0,0 +1,5 @@
.container {
max-width: 865px;
margin: 0 auto;
display: block;
}

30
components/Container.tsx Normal file
View File

@@ -0,0 +1,30 @@
import { useRouter } from "next/router";
import { NextSeo } from "next-seo";
import * as config from "../lib/config";
import styles from "./Container.module.scss";
type Props = {
title?: string;
description?: string;
children: unknown;
};
export default function Container({ title, description, children }: Props) {
const router = useRouter();
return (
<>
<NextSeo
title={title}
description={description}
canonical={`${config.baseURL}${router.asPath}`}
openGraph={{
title: title,
url: `${config.baseURL}${router.asPath}`,
}}
/>
<div className={styles.container}>{children}</div>
</>
);
}

View File

@@ -0,0 +1,67 @@
.content {
font-size: 0.925em;
letter-spacing: -0.004em;
line-height: 1.7;
b,
strong {
letter-spacing: 0.008em; // not sure why the discrepancy between weights
}
blockquote {
margin-left: 0;
padding-left: 1.5em;
border-left: 3px solid var(--link);
color: var(--medium-dark);
}
h2,
h3,
h4 {
margin-top: 1.25em;
margin-bottom: 0.5em;
letter-spacing: 0.001em;
line-height: 1.5;
}
// special bottom border for H2s
h2 {
padding-bottom: 0.25em;
border-bottom: 1px solid var(--kinda-light);
}
figure {
margin: 1em auto;
text-align: center;
line-height: 1;
img {
height: auto;
max-width: 100%;
}
figcaption {
font-size: 0.95em;
line-height: 1.5;
margin-top: 0.5em;
color: var(--medium);
}
}
ul,
ol {
margin-left: 1.5em;
padding-left: 0;
li {
padding-left: 0.25em;
}
}
hr {
margin: 1.5em auto;
height: 2px;
border: 0;
background-color: var(--light);
}
}

9
components/Content.tsx Normal file
View File

@@ -0,0 +1,9 @@
import styles from "./Content.module.scss";
type Props = {
children: unknown;
};
export default function Content({ children }: Props) {
return <div className={styles.content}>{children}</div>;
}

View File

@@ -0,0 +1,12 @@
.main {
width: 100%;
padding: 1.5em;
color: var(--text);
background-color: var(--background-inner);
}
@media screen and (max-width: 800px) {
.main {
padding: 1.25em;
}
}

14
components/Layout.tsx Normal file
View File

@@ -0,0 +1,14 @@
import Header from "./page-header/Header";
import Footer from "./page-footer/Footer";
import styles from "./Layout.module.scss";
export default function Layout({ children }) {
return (
<>
<Header />
<main className={styles.main}>{children}</main>
<Footer />
</>
);
}

View File

@@ -0,0 +1,23 @@
.copy_button {
position: absolute;
top: 0;
right: 0;
padding: 0.75em;
color: var(--text);
background-color: var(--background-inner);
border: 1px solid var(--kinda-light);
cursor: pointer;
&:hover {
color: var(--link);
}
.octicon {
width: 16px;
height: 16px;
}
.octicon-check {
color: var(--success);
}
}

View File

@@ -0,0 +1,59 @@
import { useState, useEffect } from "react";
import copy from "copy-to-clipboard";
import trimNewlines from "trim-newlines";
// react components:
import { CopyIcon, CheckIcon } from "@primer/octicons-react";
import styles from "./CopyButton.module.scss";
type Props = {
content: string;
timeout?: number;
};
export default function CopyButton({ content, timeout = 2000 }: Props) {
const [copied, setCopied] = useState(false);
const handleCopy = (e) => {
// stop browser from navigating away from page (this shouldn't happen anyways)
e.preventDefault();
// prevent unintentional double-clicks by unfocusing button
e.target.blur();
// trim any surrounding whitespace from target block's content and send it to the clipboard
const didCopy = copy(trimNewlines(content));
// indicate success
setCopied(didCopy);
};
useEffect(() => {
// reset everything after given ms (defaults to 2 seconds)
if (copied) {
const id = setTimeout(() => {
setCopied(false);
}, timeout);
return () => clearTimeout(id);
}
return () => {};
}, [timeout, copied]);
return (
<button
className={styles.copy_button}
title="Copy to clipboard"
aria-label="Copy to clipboard"
onClick={handleCopy}
disabled={copied}
>
{copied ? (
<CheckIcon size={16} className={`${styles.octicon} ${styles["octicon-check"]}`} />
) : (
<CopyIcon size={16} className={styles.octicon} />
)}
</button>
);
}

View File

@@ -0,0 +1,96 @@
.form {
input[type="text"],
input[type="email"],
select,
textarea {
width: 100%;
padding: 0.8em;
margin: 0.6em 0;
border: 2px solid;
border-radius: 0.3em;
color: var(--text);
background-color: var(--super-duper-light);
border-color: var(--light);
&:focus {
outline: none; // disable browsers' outer border
border-color: var(--link);
}
}
textarea {
height: 12em;
min-height: 6em;
margin-bottom: 0;
line-height: 1.5;
// allow vertical resizing & disable horizontal
resize: vertical;
}
}
.markdown_tip {
font-size: 0.825em;
line-height: 1.75;
a {
background: none;
padding-bottom: 0;
&:first-of-type {
font-weight: 500;
}
}
}
.captcha {
margin: 1em 0;
}
.action_row {
display: flex;
align-items: center;
min-height: 3.75em;
}
.btn_submit {
flex-shrink: 0;
height: 3.25em;
padding: 1em 1.25em;
margin-right: 1.5em;
border: 0;
border-radius: 0.3em;
font-size: 1.1em;
cursor: pointer;
user-select: none;
color: var(--text);
background-color: var(--kinda-light);
&:hover {
color: var(--super-duper-light);
background-color: var(--link);
}
.send_icon {
height: 1.2em;
width: 1.2em;
vertical-align: -0.22em;
border: 0;
display: inline-block;
margin-right: 0.4em;
cursor: inherit;
}
}
.result_success,
.result_error {
font-weight: 600;
}
.result_success {
color: var(--success);
}
.result_error {
color: var(--error);
}

View File

@@ -0,0 +1,139 @@
import { useState } from "react";
import HCaptcha from "@hcaptcha/react-hcaptcha";
import { CheckIcon, XIcon } from "@primer/octicons-react";
import { SendIcon } from "../icons";
import styles from "./ContactForm.module.scss";
export default function ContactForm() {
// status/feedback:
const [status, setStatus] = useState({ success: false, message: "" });
// keep track of fetch:
const [sending, setSending] = useState(false);
const onSubmit = (e) => {
// immediately prevent browser from actually navigating to a new page
e.preventDefault();
// begin the process
setSending(true);
// extract data from form fields
const formData = {
name: e.target.elements.name?.value,
email: e.target.elements.email?.value,
message: e.target.elements.message?.value,
"h-captcha-response": e.target.elements["h-captcha-response"]?.value,
};
// some client-side validation to save requests (these are also checked on the server to be safe)
if (!(formData.name && formData.email && formData.message && formData["h-captcha-response"])) {
setSending(false);
setStatus({ success: false, message: "Please make sure that all fields are filled in." });
return;
}
// if we've gotten here then all data is (or should be) valid and ready to post to API
fetch("/api/contact/", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(formData),
})
.then((response) => response.json())
.then((data) => {
setSending(false);
if (data.success === true) {
// handle successful submission
// disable submissions, hide the send button, and let user know we were successful
setStatus({ success: true, message: "Thanks! You should hear from me soon." });
} else {
// pass on any error sent by the server
throw new Error(data.message);
}
})
.catch((error) => {
const message = error instanceof Error ? error.message : "UNKNOWN_EXCEPTION";
setSending(false);
// give user feedback based on the error message returned
if (message === "USER_INVALID_CAPTCHA") {
setStatus({
success: false,
message: "Did you complete the CAPTCHA? (If you're human, that is...)",
});
} else if (message === "USER_MISSING_DATA") {
setStatus({
success: false,
message: "Please make sure that all fields are filled in.",
});
} else {
// something else went wrong, and it's probably my fault...
setStatus({ success: false, message: "Internal server error. Try again later?" });
}
});
};
return (
<form className={styles.form} onSubmit={onSubmit} action="/api/contact/" method="POST">
<input type="text" name="name" placeholder="Name" required disabled={status.success} />
<input type="email" name="email" placeholder="Email" required disabled={status.success} />
<textarea name="message" placeholder="Write something..." required disabled={status.success} />
<div className={styles.markdown_tip}>
Basic{" "}
<a
href="https://commonmark.org/help/"
title="Markdown reference sheet"
target="_blank"
rel="noopener noreferrer"
>
Markdown syntax
</a>{" "}
is allowed here, e.g.: <strong>**bold**</strong>, <em>_italics_</em>, [
<a href="https://jarv.is" target="_blank" rel="noopener noreferrer">
links
</a>
](https://jarv.is), and <code>`code`</code>.
</div>
<div className={styles.captcha}>
<HCaptcha
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY}
size="normal"
onVerify={() => true} // this is allegedly optional but a function undefined error is thrown without it
/>
</div>
<div className={styles.action_row}>
<button
className={styles.btn_submit}
title="Send Message"
aria-label="Send Message"
disabled={sending}
style={{ display: status.success ? "none" : null }}
>
{sending ? (
<span>Sending...</span>
) : (
<>
<SendIcon className={styles.send_icon} /> <span>Send</span>
</>
)}
</button>
<span
className={status.success ? styles.result_success : styles.result_error}
style={{ display: !status.message || sending ? "none" : null }}
>
{status.success ? <CheckIcon size={16} /> : <XIcon size={16} />} {status.message}
</span>
</div>
</form>
);
}

28
components/hits/Hits.tsx Normal file
View File

@@ -0,0 +1,28 @@
import useSWR from "swr";
import { fetcher } from "../../lib/fetcher";
import Loading from "../loading/Loading";
export default function Counter({ slug }) {
// start fetching repos from API immediately
const { data, error } = useSWR(`/api/hits/?slug=${encodeURIComponent(slug)}`, fetcher, {
// avoid double (or more) counting views
revalidateOnFocus: false,
});
// show spinning loading indicator if data isn't fetched yet
if (!data) {
return <Loading boxes={3} width={20} />;
}
// fail secretly
if (error) {
return;
}
// we have data!
return (
<span title={`${data.hits.toLocaleString("en-US")} ${data.hits === 1 ? "view" : "views"}`}>
{data.hits.toLocaleString("en-US")}
</span>
);
}

View File

@@ -0,0 +1,59 @@
import hexRgb from "hex-rgb";
import isAbsoluteUrl from "is-absolute-url";
import Link from "next/link";
type Props = {
children: unknown;
href: string;
lightColor: string;
darkColor: string;
title?: string;
className?: string;
external?: boolean;
};
export default function ColorLink({
children,
href,
lightColor,
darkColor,
title,
className,
external = false,
}: Props) {
external = external || isAbsoluteUrl(href);
// hacky hack to form a unique CSS var based on the light hex code, since they need to be set "globally"
const varName = `Home__${lightColor.replace("#", "")}`;
const alpha = 0.4;
return (
<Link href={href} passHref={true} prefetch={false}>
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
className={className}
title={title}
target={external ? "_blank" : undefined}
rel={external ? "noopener noreferrer" : undefined}
>
{children}
<style jsx global>{`
:root {
--${varName}: ${lightColor};
--${varName}_alpha: ${hexRgb(lightColor, { alpha: alpha, format: "css" })};
}
[data-theme="dark"] {
--${varName}: ${darkColor};
--${varName}_alpha: ${hexRgb(darkColor, { alpha: alpha, format: "css" })};
}
`}</style>
<style jsx>{`
a {
color: var(--${varName});
background-image: linear-gradient(var(--${varName}_alpha), var(--${varName}_alpha));
}
`}</style>
</a>
</Link>
);
}

View File

@@ -0,0 +1,107 @@
.home {
font-size: 1em;
h1 {
margin: 0 0 0.3em -0.03em; // TODO: why is this indented slightly?
font-size: 1.8em;
font-weight: 500;
letter-spacing: -0.014em;
.wave {
display: inline-block;
margin-left: 2px;
animation: wave 5s infinite;
animation-delay: 1s;
transform-origin: 65% 80%;
will-change: transform;
}
}
h2 {
margin: 0.5em 0 0.5em -0.03em; // TODO: why is this indented slightly?
font-size: 1.35em;
font-weight: 400;
letter-spacing: -0.022em;
line-height: 1.4;
}
p {
margin: 0.85em 0;
letter-spacing: -0.009em;
line-height: 1.7;
&:last-of-type {
margin-bottom: 0;
}
}
sup {
letter-spacing: normal;
position: relative;
margin-left: 0.2em;
a {
background: none;
padding-bottom: 0;
}
}
.pgp {
font-size: 0.875em;
word-spacing: -0.3em;
margin-right: 0.075em;
}
.light {
color: var(--medium-light);
}
.birthday:hover {
cursor: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 36 36%27 width=%2720%27 height=%2720%27%3E%3Cg fill=%27none%27%3E%3Cpath fill=%27%23292F33%27 d=%27m2.651 6.073 26.275 26.276c.391.391 2.888-2.107 2.497-2.497L5.148 3.576c-.39-.391-2.888 2.107-2.497 2.497z%27/%3E%3Cpath fill=%27%2366757F%27 d=%27M29.442 31.23 3.146 4.934l.883-.883 26.296 26.296z%27/%3E%3Cpath fill=%27%23E1E8ED%27 d=%27m33.546 33.483-.412.412-.671.671a.967.967 0 0 1-.255.169.988.988 0 0 1-1.159-.169l-2.102-2.102.495-.495.883-.883 1.119-1.119 2.102 2.102a.999.999 0 0 1 0 1.414zM4.029 4.79l-.883.883-.495.495L.442 3.96a.988.988 0 0 1-.169-1.159.967.967 0 0 1 .169-.255l.671-.671.412-.412a.999.999 0 0 1 1.414 0l2.208 2.208L4.029 4.79z%27/%3E%3Cpath fill=%27%23F5F8FA%27 d=%27m30.325 30.497 2.809 2.809-.671.671a.967.967 0 0 1-.255.169l-2.767-2.767.884-.882zM3.146 5.084.273 2.211a.967.967 0 0 1 .169-.255l.671-.671 2.916 2.916-.883.883z%27/%3E%3Cpath fill=%27%23FFAC33%27 d=%27m27.897 10.219 1.542.571.6 2.2a.667.667 0 0 0 1.287 0l.6-2.2 1.542-.571a.665.665 0 0 0 0-1.25l-1.534-.568-.605-2.415a.667.667 0 0 0-1.293 0l-.605 2.415-1.534.568a.665.665 0 0 0 0 1.25m-16.936 9.628 2.61.966.966 2.61a1.103 1.103 0 0 0 2.07 0l.966-2.61 2.609-.966a1.103 1.103 0 0 0 0-2.07l-2.609-.966-.966-2.61a1.105 1.105 0 0 0-2.07 0l-.966 2.61-2.61.966a1.104 1.104 0 0 0 0 2.07M23.13 4.36l1.383.512.512 1.382a.585.585 0 0 0 1.096 0l.512-1.382 1.382-.512a.584.584 0 0 0 0-1.096l-1.382-.512-.512-1.382a.585.585 0 0 0-1.096 0l-.512 1.382-1.383.512a.585.585 0 0 0 0 1.096%27/%3E%3C/g%3E%3C/svg%3E")
0 0,
auto;
}
}
@media screen and (max-width: 800px) {
.home {
font-size: 0.975em;
h1 {
font-size: 1.6em;
}
h2 {
font-size: 1.2em;
}
}
}
@keyframes wave {
0% {
transform: rotate(0deg);
}
5% {
transform: rotate(14deg);
}
10% {
transform: rotate(-8deg);
}
15% {
transform: rotate(14deg);
}
20% {
transform: rotate(-4deg);
}
25% {
transform: rotate(10deg);
}
30% {
transform: rotate(0deg);
}
// pause for 3.5 out of 5 seconds
100% {
transform: rotate(0deg);
}
}

247
components/home/Home.tsx Normal file
View File

@@ -0,0 +1,247 @@
import Link from "./ColorLink";
import { WaveIcon, LockIcon } from "../icons";
import styles from "./Home.module.scss";
export default function Home() {
return (
<div className={styles.home}>
<h1>
Hi there! I'm Jake.{" "}
<span className={styles.wave}>
<WaveIcon />
</span>
</h1>
<h2>
I'm a frontend web developer based in{" "}
<Link
href="https://www.youtube-nocookie.com/embed/rLwbzGyC6t4?hl=en&amp;fs=1&amp;showinfo=1&amp;rel=0&amp;iv_load_policy=3"
title='"Boston Accent Trailer - Late Night with Seth Meyers" on YouTube'
lightColor="#fb4d42"
darkColor="#ff5146"
>
Boston
</Link>
.
</h2>
<p>
I specialize in{" "}
<Link
href="https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/"
title='"The Brutal Lifecycle of JavaScript Frameworks" by Ian Allen'
lightColor="#1091b3"
darkColor="#6fcbe3"
>
modern JS frameworks
</Link>{" "}
and{" "}
<Link
href="http://vanilla-js.com/"
title="The best JS framework in the world by Eric Wastl"
lightColor="#f48024"
darkColor="#e18431"
>
vanilla JavaScript
</Link>{" "}
to make nifty{" "}
<Link href="https://jamstack.wtf/" title="WTF is JAMstack?" lightColor="#04a699" darkColor="#08bbac">
JAMstack sites
</Link>{" "}
with dynamic{" "}
<Link href="https://nodejs.org/en/" title="Node.js Official Website" lightColor="#6fbc4e" darkColor="#84d95f">
Node.js
</Link>{" "}
services. But I'm fluent in non-buzzwords like{" "}
<Link
href="https://stitcher.io/blog/php-in-2020"
title='"PHP in 2020" by Brent Roose'
lightColor="#8892bf"
darkColor="#a4afe3"
>
PHP
</Link>
,{" "}
<Link
href="https://www.ruby-lang.org/en/"
title="Ruby Official Website"
lightColor="#d34135"
darkColor="#f95a4d"
>
Ruby
</Link>
, and{" "}
<Link href="https://golang.org/" title="Golang Official Website" lightColor="#00acd7" darkColor="#2ad1fb">
Go
</Link>{" "}
too.
</p>
<p>
Whenever possible, I also apply my experience in{" "}
<Link
href="https://github.com/jakejarvis/awesome-shodan-queries"
title="jakejarvis/awesome-shodan-queries on GitHub"
lightColor="#00b81a"
darkColor="#57f06d"
>
application security
</Link>
,{" "}
<Link
href="https://www.cloudflare.com/learning/serverless/what-is-serverless/"
title='"What is serverless computing?" on Cloudflare'
lightColor="#0098ec"
darkColor="#43b9fb"
>
serverless stacks
</Link>
, and{" "}
<Link href="https://xkcd.com/1319/" title='"Automation" on xkcd' lightColor="#ff6200" darkColor="#f46c16">
DevOps automation
</Link>
.
</p>
<p>
I fell in love with{" "}
<Link
href="/previously/"
title="My Terrible, Horrible, No Good, Very Bad First Websites"
lightColor="#4169e1"
darkColor="#8ca9ff"
>
frontend web design
</Link>{" "}
and{" "}
<Link
href="/notes/my-first-code/"
title="Jake's Bulletin Board, circa 2003"
lightColor="#9932cc"
darkColor="#d588fb"
>
backend programming
</Link>{" "}
back when my only source of income was{" "}
<Link
className={styles.birthday}
href="/birthday/"
title="🎉 Cranky Birthday Boy on VHS Tape 📼"
lightColor="#e40088"
darkColor="#fd40b1"
>
the Tooth Fairy
</Link>
. <span className={styles.light}>I've improved a bit since then, I think...</span>
</p>
<p>
Over the years, some of my side projects{" "}
<Link
href="https://tuftsdaily.com/news/2012/04/06/student-designs-iphone-joeytracker-app/"
title='"Student designs iPhone JoeyTracker app" on The Tufts Daily'
lightColor="#ff1b1b"
darkColor="#f06060"
>
have
</Link>{" "}
<Link
href="/leo/"
title="Powncer segment on The Lab with Leo Laporte (G4techTV)"
lightColor="#f78200"
darkColor="#fd992a"
>
been
</Link>{" "}
<Link
href="https://www.google.com/books/edition/The_Facebook_Effect/RRUkLhyGZVgC?hl=en&gbpv=1&dq=%22jake%20jarvis%22&pg=PA226&printsec=frontcover&bsq=%22jake%20jarvis%22"
title='"The Facebook Effect" by David Kirkpatrick (Google Books)'
lightColor="#f2b702"
darkColor="#ffcc2e"
>
featured
</Link>{" "}
<Link
href="https://money.cnn.com/2007/06/01/technology/facebookplatform.fortune/index.htm"
title='"The new Facebook is on a roll" on CNN Money'
lightColor="#5ebd3e"
darkColor="#78df55"
>
by
</Link>{" "}
<Link
href="https://www.wired.com/2007/04/our-web-servers/"
title='"Middio: A YouTube Scraper for Major Label Music Videos" on Wired'
lightColor="#009cdf"
darkColor="#29bfff"
>
various
</Link>{" "}
<Link
href="https://gigaom.com/2009/10/06/fresh-faces-in-tech-10-kid-entrepreneurs-to-watch/6/"
title='"Fresh Faces in Tech: 10 Kid Entrepreneurs to Watch" on Gigaom'
lightColor="#3e49bb"
darkColor="#7b87ff"
>
media
</Link>{" "}
<Link
href="https://adage.com/article/small-agency-diary/client-ceo-s-son/116723/"
title='"Your Next Client? The CEO&#39;s Son" on Advertising Age'
lightColor="#973999"
darkColor="#db60dd"
>
outlets
</Link>
.
</p>
<p>
You can find more of my work on{" "}
<Link
href="https://github.com/jakejarvis"
title="Jake Jarvis on GitHub"
lightColor="#8d4eff"
darkColor="#a379f0"
>
GitHub
</Link>{" "}
and{" "}
<Link
href="https://www.linkedin.com/in/jakejarvis/"
title="Jake Jarvis on LinkedIn"
lightColor="#0073b1"
darkColor="#3b9dd2"
>
LinkedIn
</Link>
. I'm always available to connect over{" "}
<Link href="/contact/" title="Send an email" lightColor="#de0c0c" darkColor="#ff5050">
email
</Link>{" "}
<sup className="monospace">
<Link href="/pubkey.asc" title="My Public Key" lightColor="#757575" darkColor="#959595" external={true}>
<span className={styles.pgp}>
<LockIcon alt="PGP Key" /> 2B0C 9CF2 51E6 9A39
</span>
</Link>
</sup>
,{" "}
<Link
href="https://twitter.com/jakejarvis"
title="Jake Jarvis on Twitter"
lightColor="#00acee"
darkColor="#3bc9ff"
>
Twitter
</Link>
, or{" "}
<Link href="sms:+1-617-917-3737" title="Send SMS to +1 (617) 917-3737" lightColor="#6fcc01" darkColor="#8edb34">
SMS
</Link>{" "}
as well!
</p>
</div>
);
}

51
components/icons/index.ts Normal file
View File

@@ -0,0 +1,51 @@
import BotIcon from "./svg/bot.svg";
import BulbOffIcon from "./svg/bulb-off.svg";
import BulbOnIcon from "./svg/bulb-on.svg";
import ContactIcon from "./svg/contact.svg";
import DateIcon from "./svg/date.svg";
import EditIcon from "./svg/edit.svg";
import FloppyIcon from "./svg/floppy.svg";
import HeartIcon from "./svg/heart.svg";
import HomeIcon from "./svg/home.svg";
import LaptopIcon from "./svg/laptop.svg";
import LicenseIcon from "./svg/license.svg";
import LockIcon from "./svg/lock.svg";
import MailIcon from "./svg/mail.svg";
import NextjsIcon from "./svg/nextjs.svg";
import NotesIcon from "./svg/notes.svg";
import PrivacyIcon from "./svg/privacy.svg";
import ProjectsIcon from "./svg/projects.svg";
import SendIcon from "./svg/send.svg";
import SirenIcon from "./svg/siren.svg";
import TagIcon from "./svg/tag.svg";
import TapeIcon from "./svg/tape.svg";
import ViewsIcon from "./svg/views.svg";
import WandIcon from "./svg/wand.svg";
import WaveIcon from "./svg/wave.svg";
export {
BotIcon,
BulbOffIcon,
BulbOnIcon,
ContactIcon,
DateIcon,
EditIcon,
FloppyIcon,
HeartIcon,
HomeIcon,
LaptopIcon,
LicenseIcon,
LockIcon,
MailIcon,
NextjsIcon,
NotesIcon,
PrivacyIcon,
ProjectsIcon,
SendIcon,
SirenIcon,
TagIcon,
TapeIcon,
ViewsIcon,
WandIcon,
WaveIcon,
};

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#F4900C" cx="33.5" cy="14.5" rx="2.5" ry="3.5"/><ellipse fill="#F4900C" cx="2.5" cy="14.5" rx="2.5" ry="3.5"/><path fill="#FFAC33" d="M34 19c0 .553-.447 1-1 1h-3c-.553 0-1-.447-1-1v-9c0-.552.447-1 1-1h3c.553 0 1 .448 1 1v9zM7 19c0 .553-.448 1-1 1H3c-.552 0-1-.447-1-1v-9c0-.552.448-1 1-1h3c.552 0 1 .448 1 1v9z"/><path fill="#FFCC4D" d="M28 5c0 2.761-4.478 4-10 4C12.477 9 8 7.761 8 5s4.477-5 10-5c5.522 0 10 2.239 10 5z"/><path fill="#F4900C" d="M25 4.083C25 5.694 21.865 7 18 7c-3.866 0-7-1.306-7-2.917 0-1.611 3.134-2.917 7-2.917 3.865 0 7 1.306 7 2.917z"/><path fill="#269" d="M30 5.5C30 6.881 28.881 7 27.5 7h-19C7.119 7 6 6.881 6 5.5S7.119 3 8.5 3h19C28.881 3 30 4.119 30 5.5z"/><path fill="#55ACEE" d="M30 6H6c-1.104 0-2 .896-2 2v26h28V8c0-1.104-.896-2-2-2z"/><path fill="#3B88C3" d="M35 33v-1c0-1.104-.896-2-2-2H22.071l-3.364 3.364c-.391.391-1.023.391-1.414 0L13.929 30H3c-1.104 0-2 .896-2 2v1c0 1.104-.104 2 1 2h32c1.104 0 1-.896 1-2z"/><circle fill="#FFF" cx="24.5" cy="14.5" r="4.5"/><circle fill="#DD2E44" cx="24.5" cy="14.5" r="2.721"/><circle fill="#FFF" cx="11.5" cy="14.5" r="4.5"/><path fill="#F5F8FA" d="M29 25.5c0 1.381-1.119 2.5-2.5 2.5h-17C8.119 28 7 26.881 7 25.5S8.119 23 9.5 23h17c1.381 0 2.5 1.119 2.5 2.5z"/><path fill="#CCD6DD" d="M17 23h2v5h-2zm-5 0h2v5h-2zm10 0h2v5h-2zM7 25.5c0 1.21.859 2.218 2 2.45v-4.9c-1.141.232-2 1.24-2 2.45zm20-2.45v4.899c1.141-.232 2-1.24 2-2.45s-.859-2.217-2-2.449z"/><circle fill="#DD2E44" cx="11.5" cy="14.5" r="2.721"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="none"><path d="m30 11.376c0 6.6229714-5.2272727 7.6515429-5.2272727 13.824 0 3.1865143-3.2649546 3.4549714-5.75 3.4549714-2.1463182 0-6.8853637-.8012571-6.8853637-3.4570285 0-6.1693715-5.1373636-7.1979429-5.1373636-13.8219429 0-6.20331429 5.5252273-11.232 11.5867727-11.232 6.0636364 0 11.4132273 5.02868571 11.4132273 11.232z" fill="#cccbcb"/><path d="m22.8564091 33.4285714c0 .8516572-2.3355455 2.5714286-4.3564091 2.5714286s-4.3564091-1.7197714-4.3564091-2.5714286c0-.8516571 2.3345-.5142857 4.3564091-.5142857 2.0208636 0 4.3564091-.3373714 4.3564091.5142857z" fill="#ccd6dd"/><path d="m23.4209545 10.5870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0l-3.4426818 3.3870857-3.4426818-3.3870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0-.4087728.4021714-.4087728 1.0522286 0 1.4544l3.8755 3.8129143v10.8884571c0 .5688.4683636 1.0285715 1.0454545 1.0285715s1.0454545-.4597715 1.0454545-1.0285715v-10.8884571l3.8755-3.8129143c.4087728-.4021714.4087728-1.0522286 0-1.4544z" fill="#7d7a72"/><path d="m24.7727273 31.8857143c0 1.1355428-.9367273 2.0571428-2.0909091 2.0571428h-8.3636364c-1.1541818 0-2.0909091-.9216-2.0909091-2.0571428v-6.1714286h12.5454546z" fill="#99aab5"/><path d="m12.2262273 32.9142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828571l12.5454545-2.0571429c.5687273-.1008 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828572l-12.5454545 2.0571428c-.0575.0102857-.1160455.0144-.1725.0144zm0-4.1142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828572l12.5454545-2.0571428c.5687273-.0997714 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828571l-12.5454545 2.0571429c-.0575.0102857-.1160455.0144-.1725.0144z" fill="#ccd6dd"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="none"><path d="m30 11.376c0 6.6229714-5.2272727 7.6515429-5.2272727 13.824 0 3.1865143-3.2649546 3.4549714-5.75 3.4549714-2.1463182 0-6.8853637-.8012571-6.8853637-3.4570285 0-6.1693715-5.1373636-7.1979429-5.1373636-13.8219429 0-6.20331429 5.5252273-11.232 11.5867727-11.232 6.0636364 0 11.4132273 5.02868571 11.4132273 11.232z" fill="#ffd983"/><path d="m22.8564091 33.4285714c0 .8516572-2.3355455 2.5714286-4.3564091 2.5714286s-4.3564091-1.7197714-4.3564091-2.5714286c0-.8516571 2.3345-.5142857 4.3564091-.5142857 2.0208636 0 4.3564091-.3373714 4.3564091.5142857z" fill="#b9c9d9"/><path d="m23.4209545 10.5870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0l-3.4426818 3.3870857-3.4426818-3.3870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0-.4087728.4021714-.4087728 1.0522286 0 1.4544l3.8755 3.8129143v10.8884571c0 .5688.4683636 1.0285715 1.0454545 1.0285715s1.0454545-.4597715 1.0454545-1.0285715v-10.8884571l3.8755-3.8129143c.4087728-.4021714.4087728-1.0522286 0-1.4544z" fill="#ffcc4d"/><path d="m24.7727273 31.8857143c0 1.1355428-.9367273 2.0571428-2.0909091 2.0571428h-8.3636364c-1.1541818 0-2.0909091-.9216-2.0909091-2.0571428v-6.1714286h12.5454546z" fill="#99aab5"/><path d="m12.2262273 32.9142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828571l12.5454545-2.0571429c.5687273-.1008 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828572l-12.5454545 2.0571428c-.0575.0102857-.1160455.0144-.1725.0144zm0-4.1142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828572l12.5454545-2.0571428c.5687273-.0997714 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828571l-12.5454545 2.0571429c-.0575.0102857-.1160455.0144-.1725.0144z" fill="#ccd6dd"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M22 33c0 2.209-8 2.209-8 0V23c0-2.209 1.791-4 4-4s4 1.791 4 4v10z"/><path fill="#292F33" d="M10 3c-4.418 0-8 3.582-8 8v12h16V11c0-4.418-3.582-8-8-8z"/><path fill="#CCD6DD" d="M20 18.761C20 19.997 18.935 21 17.707 21H2.293C1.064 21 0 19.997 0 18.761V8.239C0 7.003 1.064 6 2.293 6h15.414C18.935 6 20 7.003 20 8.239v10.522z"/><path fill="#99AAB5" d="M20 8.239C20 7.003 18.935 6 17.707 6H2.293C1.064 6 0 7.003 0 8.239v1.419l4.879 4.904-4.78 4.806c.112.376.316.716.596.983l4.972-4.998 2.407 2.419c1.052 1.06 2.768 1.06 3.821 0l2.426-2.432 4.984 5.011c.28-.268.483-.608.596-.983l-4.792-4.818L20 9.646V8.239z"/><path fill="#E1E8ED" d="M17.707 6H2.293C1.127 6 .121 6.906.02 8.055l8.408 8.397c.869.874 2.277.84 3.145-.035l8.41-8.346C19.889 6.914 18.877 6 17.707 6z"/><path fill="#99AAB5" d="M26 3H10c4.418 0 8 3.582 8 8v12h16V11c0-4.418-3.582-8-8-8z"/><path fill="#DD2E44" d="M26 1h-4c-1.104 0-2 .896-2 2v12c0 1.104.896 2 2 2s2-.896 2-2V7h2c1.104 0 2-.896 2-2V3c0-1.104-.896-2-2-2z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
components/icons/svg/date.svg Executable file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m36 32c0 2.209-1.791 4-4 4h-28c-2.209 0-4-1.791-4-4v-23c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4z" fill="#e0e7ec"/><path d="m23.657 19.12h-5.787c-1.22 0-1.673-.791-1.673-1.56 0-.791.429-1.56 1.673-1.56h8.184c1.154 0 1.628 1.04 1.628 1.628 0 .452-.249.927-.52 1.492l-5.607 11.395c-.633 1.266-.882 1.717-1.899 1.717-1.244 0-1.877-.949-1.877-1.605 0-.271.068-.474.226-.791zm-12.768-.12h-.5c-1.085 0-1.538-.731-1.538-1.5 0-.792.565-1.5 1.538-1.5h2.015c.972 0 1.515.701 1.515 1.605v12.865c0 1.13-.558 1.763-1.53 1.763s-1.5-.633-1.5-1.763z" fill="#66757f"/><path d="m34 0h-3.277c.172.295.277.634.277 1 0 1.104-.896 2-2 2s-2-.896-2-2c0-.366.105-.705.277-1h-18.554c.172.295.277.634.277 1 0 1.104-.896 2-2 2s-2-.896-2-2c0-.366.105-.705.277-1h-3.277c-1.104 0-2 .896-2 2v11h36v-11c0-1.104-.896-2-2-2z" fill="#dd2f45"/><path d="m13.182 4.604c0-.5.32-.78.75-.78.429 0 .749.28.749.78v5.017h1.779c.51 0 .73.38.72.72-.02.33-.28.659-.72.659h-2.498c-.49 0-.78-.319-.78-.819zm-6.91 0c0-.5.32-.78.75-.78s.75.28.75.78v3.488c0 .92.589 1.649 1.539 1.649.909 0 1.529-.769 1.529-1.649v-3.488c0-.5.319-.78.749-.78s.75.28.75.78v3.568c0 1.679-1.38 2.949-3.028 2.949-1.669 0-3.039-1.25-3.039-2.949zm-.782 4.397c0 1.679-1.069 2.119-1.979 2.119-.689 0-1.839-.27-1.839-1.14 0-.269.23-.609.56-.609.4 0 .75.37 1.199.37.56 0 .56-.52.56-.84v-4.297c0-.5.32-.78.749-.78.431 0 .75.28.75.78z" fill="#f5f8fa"/><path d="m32 10c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0-3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m-3 3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0-3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m-3 3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0-3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m-3 0c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0 3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1" fill="#f4abba"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
components/icons/svg/edit.svg Executable file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m35.222 33.598c-.647-2.101-1.705-6.059-2.325-7.566-.501-1.216-.969-2.438-1.544-3.014-.575-.575-1.553-.53-2.143.058 0 0-2.469 1.675-3.354 2.783-1.108.882-2.785 3.357-2.785 3.357-.59.59-.635 1.567-.06 2.143.576.575 1.798 1.043 3.015 1.544 1.506.62 5.465 1.676 7.566 2.325.359.11 1.74-1.271 1.63-1.63z" fill="#d99e82"/><path d="m13.643 5.308c1.151 1.151 1.151 3.016 0 4.167l-4.167 4.168c-1.151 1.15-3.018 1.15-4.167 0l-4.168-4.168c-1.15-1.151-1.15-3.016 0-4.167l4.167-4.167c1.15-1.151 3.016-1.151 4.167 0z" fill="#ea596e"/><path d="m31.353 23.018-4.17 4.17-4.163 4.165-15.628-15.627 8.335-8.334z" fill="#ffcc4d"/><path d="m32.078 34.763s2.709 1.489 3.441.757-.765-3.435-.765-3.435-2.566.048-2.676 2.678z" fill="#292f33"/><path d="m2.183 10.517 8.335-8.335 5.208 5.209-8.334 8.335z" fill="#ccd6dd"/><path d="m3.225 11.558 8.334-8.334 1.042 1.042-8.334 8.334zm2.083 2.086 8.335-8.335 1.042 1.042-8.335 8.334z" fill="#99aab5"/></svg>

After

Width:  |  Height:  |  Size: 997 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M4 36s-4 0-4-4V4s0-4 4-4h26c1 0 2 1 2 1l3 3s1 1 1 2v26s0 4-4 4H4z"/><path fill="#55ACEE" d="M5 19v-1s0-2 2-2h21c2 0 2 2 2 2v1H5z"/><path fill="#E1E8ED" d="M5 32.021V19h25v13s0 2-2 2H7c-2 0-2-1.979-2-1.979zM10 3s0-1 1-1h18c1.048 0 1 1 1 1v10s0 1-1 1H11s-1 0-1-1V3zm12 10h5V3h-5v10z"/></svg>

After

Width:  |  Height:  |  Size: 374 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/></svg>

After

Width:  |  Height:  |  Size: 369 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m36 33.5c0 .828-.672 1.5-1.5 1.5h-33c-.828 0-1.5-.672-1.5-1.5s.672-1.5 1.5-1.5h33c.828 0 1.5.672 1.5 1.5z" fill="#5c913b"/><path d="m12.344 14.702h-2c-.276 0-.5-.224-.5-.5v-7c0-.276.224-.5.5-.5h2c.276 0 .5.224.5.5v7c0 .276-.224.5-.5.5z" fill="#a0041e"/><path d="m5.942 32c-.137-4.657-.506-8-.942-8-.435 0-.804 3.343-.941 8z" fill="#ffcc4d"/><path d="m10 18.731c0 5.575-2.238 7.269-5 7.269-2.761 0-5-1.694-5-7.269 0-5.577 4-13.731 5-13.731s5 8.154 5 13.731z" fill="#77b255"/><path d="m8 16 13-13 13 13v16h-26z" fill="#ffe8b6"/><path d="m21 16h1v16h-1z" fill="#ffcc4d"/><path d="m34 17c-.256 0-.512-.098-.707-.293l-12.293-12.293-12.293 12.293c-.391.391-1.023.391-1.414 0s-.391-1.023 0-1.414l13-13c.391-.391 1.023-.391 1.414 0l13 13c.391.391.391 1.023 0 1.414-.195.195-.451.293-.707.293z" fill="#66757f"/><path d="m21 17c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l6.5-6.5c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-6.5 6.5c-.195.195-.451.293-.707.293z" fill="#66757f"/><path d="m13 26h4v6h-4z" fill="#c1694f"/><path d="m13 17h4v4h-4zm12.5 0h4v4h-4zm0 9h4v4h-4z" fill="#55acee"/><path d="m10.625 29.991c0 1.613-.858 2.103-1.917 2.103-1.058 0-1.917-.49-1.917-2.103s1.533-3.973 1.917-3.973 1.917 2.359 1.917 3.973zm25.25 0c0 1.613-.858 2.103-1.917 2.103-1.058 0-1.917-.49-1.917-2.103s1.533-3.973 1.917-3.973 1.917 2.359 1.917 3.973z" fill="#77b255"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M34 29.096c-.417-.963-.896-2.008-2-2.008h-1c1.104 0 2-.899 2-2.008V8.008C33 6.899 32.104 6 31 6H5c-1.104 0-2 .899-2 2.008V25.08c0 1.109.896 2.008 2 2.008H4c-1.104 0-1.667 1.004-2 2.008l-2 4.895C0 35.101.896 36 2 36h32c1.104 0 2-.899 2-2.008l-2-4.896z"/><path fill="#9AAAB4" d="M.008 34.075l.006.057.17.692C.5 35.516 1.192 36 2 36h32c1.076 0 1.947-.855 1.992-1.925H.008z"/><path fill="#5DADEC" d="M31 24.075c0 .555-.447 1.004-1 1.004H6c-.552 0-1-.449-1-1.004V9.013c0-.555.448-1.004 1-1.004h24c.553 0 1 .45 1 1.004v15.062z"/><path fill="#AEBBC1" d="M32.906 31.042l-.76-2.175c-.239-.46-.635-.837-1.188-.837H5.11c-.552 0-.906.408-1.156 1.036l-.688 1.977c-.219.596.448 1.004 1 1.004h7.578s.937-.047 1.103-.608c.192-.648.415-1.624.463-1.796.074-.264.388-.531.856-.531h8.578c.5 0 .746.253.811.566.042.204.312 1.141.438 1.782.111.571 1.221.586 1.221.586h6.594c.551 0 1.217-.471.998-1.004z"/><path fill="#9AAAB4" d="M22.375 33.113h-7.781c-.375 0-.538-.343-.484-.675.054-.331.359-1.793.383-1.963.023-.171.274-.375.524-.375h7.015c.297 0 .49.163.55.489.059.327.302 1.641.321 1.941.019.301-.169.583-.528.583z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M32 0H10C7.791 0 6 1.791 6 4v24H4c-2.209 0-4 1.791-4 4s1.791 4 4 4h24c2.209 0 4-1.791 4-4V8c2.209 0 4-1.791 4-4s-1.791-4-4-4z"/><path fill="#E39F3D" d="M8 10h24V8H10L8 7z"/><path fill="#FFE8B6" d="M10 0C7.791 0 6 1.791 6 4v24.555C5.41 28.211 4.732 28 4 28c-2.209 0-4 1.791-4 4s1.791 4 4 4 4-1.791 4-4V7.445C8.59 7.789 9.268 8 10 8c2.209 0 4-1.791 4-4s-1.791-4-4-4z"/><path fill="#C1694F" d="M12 4c0 1.104-.896 2-2 2s-2-.896-2-2 .896-2 2-2 2 .896 2 2M6 32c0 1.104-.896 2-2 2s-2-.896-2-2 .896-2 2-2 2 .896 2 2m24-17c0 .552-.447 1-1 1H11c-.552 0-1-.448-1-1s.448-1 1-1h18c.553 0 1 .448 1 1m0 4c0 .553-.447 1-1 1H11c-.552 0-1-.447-1-1s.448-1 1-1h18c.553 0 1 .447 1 1m0 4c0 .553-.447 1-1 1H11c-.552 0-1-.447-1-1s.448-1 1-1h18c.553 0 1 .447 1 1m0 4c0 .553-.447 1-1 1H11c-.552 0-1-.447-1-1 0-.553.448-1 1-1h18c.553 0 1 .447 1 1"/></svg>

After

Width:  |  Height:  |  Size: 913 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#AAB8C2" d="M13 3C7.477 3 3 7.477 3 13v10h4V13c0-3.313 2.687-6 6-6s6 2.687 6 6v10h4V13c0-5.523-4.477-10-10-10z"/><path fill="#FFAC33" d="M26 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V20c0-2.209 1.791-4 4-4h18c2.209 0 4 1.791 4 4v12z"/><path fill="#C1694F" d="M35 9c0-4.971-4.029-9-9-9s-9 4.029-9 9c0 3.917 2.507 7.24 6 8.477V33.5c0 1.381 1.119 2.5 2.5 2.5 1.213 0 2.223-.864 2.45-2.01.018.001.032.01.05.01.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1v-1c.553 0 1-.447 1-1v-2c0-.553-.447-1-1-1v-2.277c.596-.347 1-.984 1-1.723v-4.523c3.493-1.236 6-4.559 6-8.477zm-9-7c1.104 0 2 .896 2 2s-.896 2-2 2-2-.896-2-2 .896-2 2-2z"/></svg>

After

Width:  |  Height:  |  Size: 698 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M36 27c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V9c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v18z"/><path fill="#99AAB5" d="M11.95 17.636L.637 28.949c-.027.028-.037.063-.06.091.34.57.814 1.043 1.384 1.384.029-.023.063-.033.09-.06L13.365 19.05c.39-.391.39-1.023 0-1.414-.392-.391-1.024-.391-1.415 0M35.423 29.04c-.021-.028-.033-.063-.06-.09L24.051 17.636c-.392-.391-1.024-.391-1.415 0-.391.392-.391 1.024 0 1.414l11.313 11.314c.026.026.062.037.09.06.571-.34 1.044-.814 1.384-1.384"/><path fill="#99AAB5" d="M32 5H4C1.791 5 0 6.791 0 9v1.03l14.528 14.496c1.894 1.893 4.988 1.893 6.884 0L36 10.009V9c0-2.209-1.791-4-4-4z"/><path fill="#E1E8ED" d="M32 5H4C2.412 5 1.051 5.934.405 7.275l14.766 14.767c1.562 1.562 4.096 1.562 5.657 0L35.595 7.275C34.949 5.934 33.589 5 32 5z"/></svg>

After

Width:  |  Height:  |  Size: 863 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m11.5725 0c-.1763 0-.3098.0013-.3584.0067-.0516.0053-.2159.021-.3636.0328-3.4088.3073-6.6017 2.1463-8.624 4.9728-1.1261 1.5717-1.8463 3.3543-2.1183 5.2427-.0962.659-.108.8537-.108 1.7474s.012 1.0884.108 1.7476c.652 4.506 3.8591 8.2919 8.2087 9.6945.7789.2511 1.6.4223 2.5337.5255.3636.04 1.9354.04 2.299 0 1.6117-.1783 2.9772-.577 4.3237-1.2643.2065-.1056.2464-.1337.2183-.1573-.0188-.0139-.8987-1.1938-1.9543-2.62l-1.919-2.592-2.4047-3.5583c-1.3231-1.9564-2.4117-3.556-2.4211-3.556-.0094-.0026-.0187 1.5787-.0235 3.509-.0067 3.3802-.0093 3.5162-.0516 3.596-.061.115-.108.1618-.2064.2134-.075.0374-.1408.0445-.495.0445h-.406l-.1078-.068a.4383.4383 0 0 1 -.1572-.1712l-.0493-.1056.0053-4.703.0067-4.7054.0726-.0915c.0376-.0493.1174-.1125.1736-.143.0962-.047.1338-.0517.5396-.0517.4787 0 .5584.0187.6827.1547.0353.0377 1.3373 1.9987 2.895 4.3608a10760.433 10760.433 0 0 0 4.7344 7.1706l1.9002 2.8782.096-.0633c.8518-.5536 1.7525-1.3418 2.4657-2.1627 1.5179-1.7429 2.4963-3.868 2.8247-6.134.0961-.6591.1078-.854.1078-1.7475 0-.8937-.012-1.0884-.1078-1.7476-.6522-4.506-3.8592-8.2919-8.2087-9.6945-.7672-.2487-1.5836-.42-2.4985-.5232-.169-.0176-1.0835-.0366-1.6123-.037zm4.0685 7.217c.3473 0 .4082.0053.4857.047.1127.0562.204.1642.237.2767.0186.061.0234 1.3653.0186 4.3044l-.0067 4.2175-.7436-1.14-.7461-1.14v-3.066c0-1.982.0093-3.0963.0234-3.1502.0375-.1313.1196-.2346.2323-.2955.0961-.0494.1313-.054.4997-.054z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m27.815 4h1.996v2.391h-1.996z" fill="#66757f"/><path d="m29 4h-20c-2.209 0-4 1.791-4 4v24c0 2.209 1.791 4 4 4h20c2.209 0 4-1.791 4-4v-24c0-2.209-1.791-4-4-4z" fill="#55acee"/><path d="m27 4h-20c-2.209 0-4 1.791-4 4v24c0 2.209 1.791 4 4 4h20c2.209 0 4-1.791 4-4v-24c0-2.209-1.791-4-4-4z" fill="#ccd6dd"/><path d="m28 15c0 .553-.447 1-1 1h-20c-.552 0-1-.447-1-1 0-.552.448-1 1-1h20c.553 0 1 .448 1 1zm0 5c0 .553-.447 1-1 1h-20c-.552 0-1-.447-1-1s.448-1 1-1h20c.553 0 1 .447 1 1zm0 5c0 .553-.447 1-1 1h-20c-.552 0-1-.447-1-1s.448-1 1-1h20c.553 0 1 .447 1 1zm-8 5c0 .553-.447 1-1 1h-12c-.552 0-1-.447-1-1s.448-1 1-1h12c.553 0 1 .447 1 1z" fill="#99aab5"/><path d="m7.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.27.667 1.27 1.489s-.569 1.489-1.27 1.489z" fill="#292f33"/><path d="m8.543 7.083c-.055-.48-.374-.792-.729-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.021-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.76.276 1.224-.447 1.16-1.026z" fill="#66757f"/><path d="m13.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.27.667 1.27 1.489s-.569 1.489-1.27 1.489z" fill="#292f33"/><path d="m14.543 7.083c-.055-.48-.374-.792-.729-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.02-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.76.276 1.224-.447 1.16-1.026z" fill="#66757f"/><path d="m19.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.271.667 1.271 1.489-.001.822-.57 1.489-1.271 1.489z" fill="#292f33"/><path d="m20.543 7.083c-.055-.48-.374-.792-.728-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.02-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.759.276 1.223-.447 1.159-1.026z" fill="#66757f"/><path d="m25.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.271.667 1.271 1.489-.001.822-.57 1.489-1.271 1.489z" fill="#292f33"/><path d="m26.543 7.083c-.055-.48-.374-.792-.728-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.02-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.759.276 1.223-.447 1.159-1.026z" fill="#66757f"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m33 36v-1c0-3.313-2.687-6-6-6h-18c-3.313 0-6 2.687-6 6v1zm-6.25-15.565c1.188.208 2.619.129 2.416.917-.479 1.854-2.604 1.167-2.979 1.188-.375.02.563-2.105.563-2.105z" fill="#66757f"/><path d="m27.062 20.645c1.875.25 2.541.416 1.166.958-.772.305-2.243 4.803-3.331 4.118-1.087-.685 2.165-5.076 2.165-5.076z" fill="#292f33"/><path d="m9.255 20.435c-1.188.208-2.619.129-2.416.917.479 1.854 2.604 1.167 2.979 1.188.375.02-.563-2.105-.563-2.105z" fill="#66757f"/><path d="m8.943 20.645c-1.875.25-2.541.416-1.166.958.772.305 2.243 4.803 3.331 4.118s-2.165-5.076-2.165-5.076z" fill="#292f33"/><path d="m21.771 4.017c-1.958-.634-6.566-.461-7.718 1.037-2.995.058-6.508 2.764-6.969 6.335-.456 3.534.56 5.175.922 7.833.409 3.011 2.102 3.974 3.456 4.377 1.947 2.572 4.017 2.462 7.492 2.462 6.787 0 10.019-4.541 10.305-12.253.172-4.665-2.565-8.198-7.488-9.791z" fill="#ffac33"/><path d="m25.652 14.137c-.657-.909-1.497-1.641-3.34-1.901.691.317 1.353 1.411 1.44 2.016.086.605.173 1.094-.374.49-2.192-2.423-4.579-1.469-6.944-2.949-1.652-1.034-2.155-2.177-2.155-2.177s-.202 1.526-2.707 3.081c-.726.451-1.593 1.455-2.073 2.937-.346 1.066-.238 2.016-.238 3.64 0 4.74 3.906 8.726 8.726 8.726s8.726-4.02 8.726-8.726c-.004-2.948-.312-4.1-1.061-5.137z" fill="#ffdc5d"/><path d="m18.934 21.565h-1.922c-.265 0-.481-.215-.481-.481v-.174c0-.265.215-.482.481-.482h1.922c.265 0 .482.216.482.482v.174c0 .266-.216.481-.482.481" fill="#c1694f"/><path clip-rule="evenodd" d="m7.657 14.788c.148.147.888.591 1.036 1.034s.445 2.954 1.333 3.693c.916.762 4.37.478 5.032.149 1.48-.738 1.662-2.798 1.924-3.842.148-.591 1.036-.591 1.036-.591s.888 0 1.036.591c.262 1.044.444 3.104 1.924 3.841.662.33 4.116.614 5.034-.147.887-.739 1.183-3.25 1.331-3.694.146-.443.888-.886 1.035-1.034.148-.148.148-.739 0-.887-.296-.295-3.788-.559-7.548-.148-.75.082-1.035.295-2.812.295-1.776 0-2.062-.214-2.812-.295-3.759-.411-7.252-.148-7.548.148-.149.148-.149.74-.001.887z" fill="#292f33" fill-rule="evenodd"/><path d="m7.858 8.395s1.359-8.901 5.932-8.372c3.512.406 4.89.825 7.833.097 1.947-.482 4.065 1.136 5.342 4.379.816 2.068 1.224 4.041 1.224 4.041s3.938-.385 4.165 1.732c.228 2.117-4.354 4.716-15.889 4.716-6.465-.001-13.135-2.358-13.452-4.331s4.845-2.262 4.845-2.262z" fill="#66757f"/><path d="m8.125 7.15s-.27 1.104-.406 1.871c-.136.768.226 1.296 2.705 1.824 3.287.7 10.679.692 15.058-.383 1.759-.432 2.886-.72 2.751-1.583-.167-1.068-.196-1.066-.541-2.208 0 0-1.477.502-3.427.96-2.66.624-9.964.911-13.481.144-1.874-.41-2.659-.625-2.659-.625zm-.136 13.953c-.354.145 2.921 1.378 7.48 1.458 4.771.084 6.234.39 5.146 1.459-1.146 1.125-.852 2.894-.771 3.418s2.047 1.916 2.208 2.56c.161.645-1.229 5.961-1.229 5.961l-8.729-.252c-2.565-8.844-2.883-8.501-4.105-13.604-.241-1.008 0-1 0-1z" fill="#292f33"/><path d="m6.989 21.144c-.354.146 2.921 1.378 7.48 1.458 4.771.084 6.234.39 5.146 1.459-1.146 1.125-.664 2.894-.583 3.418s1.859 1.916 2.021 2.561c.16.644-1.231 5.96-1.231 5.96l-8.729-.252c-2.565-8.844-2.883-8.501-4.105-13.604-.24-1.008.001-1 .001-1z" fill="#66757f"/><path d="m28.052 21.103c.354.145-2.921 1.378-7.479 1.458-4.771.084-6.234.39-5.146 1.459 1.146 1.125 2.976 2.892 2.896 3.416-.081.524-4.172 1.918-4.333 2.562-.161.645 1.229 5.961 1.229 5.961l8.729-.252c2.565-8.844 2.883-8.501 4.104-13.604.241-1.008 0-1 0-1z" fill="#292f33"/><path d="m28.958 21.103c.354.145-2.921 1.378-7.479 1.458-4.771.084-6.234.39-5.146 1.459 1.146 1.125 2.977 2.892 2.896 3.416s-4.172 1.918-4.333 2.562c-.161.645 1.229 5.961 1.229 5.961l8.657.01c2.565-8.844 2.955-8.763 4.177-13.866.24-1.008-.001-1-.001-1z" fill="#66757f"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M35 36v-5c0-3.314-2.686-6-6-6H13c-3.313 0-6 2.686-6 6v5h28z"/><path fill="#FFDC5D" d="M16.64 25.106c0 .894 2.36 1.993 4.36 1.993s4.359-1.099 4.359-1.992V21.29h-8.72v3.816z"/><path fill="#F9CA55" d="M16.632 22.973c1.216 1.374 2.724 1.746 4.364 1.746 1.639 0 3.146-.373 4.363-1.746v-3.491h-8.728v3.491z"/><path fill="#FFDC5D" d="M14.444 12.936c0 1.448-.734 2.622-1.639 2.622s-1.639-1.174-1.639-2.622.734-2.623 1.639-2.623c.905-.001 1.639 1.174 1.639 2.623m16.389 0c0 1.448-.733 2.622-1.639 2.622-.905 0-1.639-1.174-1.639-2.622s.733-2.623 1.639-2.623c.906-.001 1.639 1.174 1.639 2.623"/><path fill="#FFDC5D" d="M12.477 13.96c0-5.589 3.816-10.121 8.523-10.121s8.522 4.532 8.522 10.121S25.707 24.081 21 24.081c-4.706-.001-8.523-4.532-8.523-10.121"/><path fill="#C1694F" d="M21 20.802c-2.754 0-3.6-.705-3.741-.848-.256-.256-.256-.671 0-.927.248-.248.646-.255.902-.023.052.037.721.487 2.839.487 2.2 0 2.836-.485 2.842-.49.256-.255.657-.243.913.015.256.256.242.683-.014.938-.141.143-.987.848-3.741.848"/><path fill="#FFAC33" d="M21 0c5.648 0 9.178 4.648 9.178 8.121 0 3.473-.706 4.863-1.412 3.473l-1.412-2.778s-4.235 0-5.647-1.39c0 0 2.118 4.168-2.118 0 0 0 .706 2.779-3.53-.694 0 0-2.118 1.389-2.824 4.862-.196.964-1.412 0-1.412-3.473C11.822 4.648 14.646 0 21 0"/><path fill="#662113" d="M17 14c-.55 0-1-.45-1-1v-1c0-.55.45-1 1-1s1 .45 1 1v1c0 .55-.45 1-1 1m8 0c-.55 0-1-.45-1-1v-1c0-.55.45-1 1-1s1 .45 1 1v1c0 .55-.45 1-1 1"/><path fill="#C1694F" d="M21.75 16.75h-1.5c-.413 0-.75-.337-.75-.75s.337-.75.75-.75h1.5c.413 0 .75.337.75.75s-.337.75-.75.75"/><path fill="#E1E8ED" d="M33 35c0 .553-.447 1-1 1H22c-.553 0-1-.447-1-1 0-.553.447-1 1-1h10c.553 0 1 .447 1 1z"/><path fill="#E1E8ED" d="M20.24 22H3.759c-1.524 0-3.478.771-2.478 3.531l3.072 8.475C4.354 34.006 4.75 36 7 36h20l-4-11.24c-.438-1.322-1.235-2.76-2.76-2.76z"/><path fill="#99AAB5" d="M19.24 22H2.759c-1.524 0-3.478.771-2.478 3.531l3.072 8.475C3.354 34.006 3.75 36 6 36h20l-4-11.24c-.438-1.322-1.235-2.76-2.76-2.76z"/><path fill="#E1E8ED" d="M14.019 29.283c.524 1.572.099 3.13-.949 3.479-1.048.35-2.322-.641-2.846-2.213s-.099-3.13.949-3.479c1.048-.349 2.323.641 2.846 2.213zM19 24.75H3c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h16c.414 0 .75.336.75.75s-.336.75-.75.75z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m36 32a4 4 0 0 1 -4 4h-28a4 4 0 0 1 -4-4v-9c0-2.209.791-3 3-3h30c2.209 0 3 .791 3 3z" fill="#d99e82"/><path d="m25 20a7 7 0 1 1 -14 0z" fill="#662113"/><path d="m4 36h28a4 4 0 0 0 4-4h-36a4 4 0 0 0 4 4z" fill="#c1694f"/><path d="m27.435 8.511-7.863-7.863a2.23 2.23 0 0 0 -3.145 0l-7.863 7.863c-.864.866-.571 1.489.652 1.489h4.784v6a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-6h4.784c1.223 0 1.516-.623.651-1.489z" fill="#dd2e44"/></svg>

After

Width:  |  Height:  |  Size: 494 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m34.16 28.812-2.916-26.134c-.17-1.525-1.459-2.678-2.993-2.678h-20.587c-1.545 0-2.839 1.168-2.997 2.704l-2.67 26.108z" fill="#dd2e44"/><circle cx="18.069" cy="14" fill="#be1931" r="9.366"/><path d="m35.521 29.18h-35.042l-.479 4.82c0 2 2 2 2 2h32s2 0 2-2z" fill="#99aab5"/><path d="m35.594 29.912-.073-.732c-.141-.738-.77-1.18-1.521-1.18h-32c-.751 0-1.38.442-1.521 1.18l-.073.732z" fill="#ccd6dd"/><path d="m29.647 13.63-7.668-1.248 4.539-6.308c.107-.148.091-.354-.039-.484-.131-.129-.336-.146-.484-.039l-6.309 4.538-1.247-7.667c-.029-.181-.187-.314-.37-.314s-.341.133-.37.314l-1.248 7.667-6.308-4.538c-.149-.107-.353-.09-.484.039-.13.131-.146.335-.039.484l4.538 6.308-7.668 1.248c-.181.029-.314.186-.314.37s.133.341.314.37l7.668 1.248-4.538 6.308c-.107.149-.091.354.039.484.131.129.335.146.484.039l6.308-4.538 1.248 7.667c.029.182.187.314.37.314s.341-.134.37-.314l1.247-7.667 6.308 4.538c.148.106.354.09.484-.039.13-.131.146-.335.039-.484l-4.538-6.308 7.668-1.248c.182-.029.314-.187.314-.37s-.132-.341-.314-.37z" fill="#ec9dad"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
components/icons/svg/tag.svg Executable file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m32.017 20.181-14.672-14.435c-.658-.659-1.522-.746-2.385-.746h-10.077c-1.854 0-2.883 1.029-2.883 2.883v10.082c0 .861.089 1.723.746 2.38l14.554 14.672c1.311 1.31 3.378 1.31 4.688 0l10.059-10.088c1.31-1.312 1.28-3.438-.03-4.748zm-23.596-8.76c-.585.585-1.533.585-2.118 0s-.586-1.533 0-2.118c.585-.586 1.533-.585 2.118 0 .585.586.586 1.533 0 2.118z" fill="#ffd983"/><path d="m9.952 7.772c-1.43-1.431-3.749-1.431-5.179 0-1.431 1.43-1.431 3.749 0 5.18 1.43 1.43 3.749 1.43 5.18 0 1.43-1.431 1.429-3.749-.001-5.18zm-1.53 3.65c-.585.585-1.534.585-2.119 0s-.586-1.534 0-2.119c.585-.587 1.534-.585 2.119 0s.586 1.533 0 2.119z" fill="#d99e82"/><path d="m8.507 10.501c-.391.391-1.023.391-1.415 0-.391-.391-.39-1.023 0-1.414l8.485-8.485c.391-.391 1.023-.391 1.415 0 .391.391.39 1.023 0 1.414z" fill="#c1694f"/></svg>

After

Width:  |  Height:  |  Size: 873 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M36 25s0 4-4 4H4s-4 0-4-4V10c0-4 4-4 4-4h28s4 0 4 4v15z"/><path fill="#CCD6DD" d="M32 12h-6s-1 0-1 1v8s0 1 1 1h6c1 0 2-2 2-5s-1-5-2-5z"/><path d="M29.894 12H26s-1 0-1 1v1c1.656 0 3 1.343 3 3s-1.344 3-3 3v1s0 1 1 1h3.895C31.193 20.73 32 18.96 32 17c0-1.959-.808-3.729-2.106-5z"/><path fill="#66757F" d="M28 17c0-1.657-1.344-3-3-3v6c1.656 0 3-1.343 3-3z"/><path fill="#CCD6DD" d="M4 12h6s1 0 1 1v8s0 1-1 1H4c-1 0-2-2-2-5s1-5 2-5z"/><path d="M11 20c-1.657 0-3-1.343-3-3s1.343-3 3-3v-1c0-1-1-1-1-1H6.106C4.808 13.271 4 15.04 4 17s.808 3.729 2.106 5H10c1 0 1-1 1-1v-1z"/><path fill="#66757F" d="M8 17c0 1.657 1.343 3 3 3v-6c-1.657 0-3 1.343-3 3z"/><path fill="#88C9F9" d="M13 14s0-1 1-1h8s1 0 1 1v6s0 1-1 1h-8s-1 0-1-1v-6z"/><path d="M34 26c0 .553-.447 1-1 1H3c-.552 0-1-.447-1-1 0-.553.448-1 1-1h30c.553 0 1 .447 1 1z"/></svg>

After

Width:  |  Height:  |  Size: 907 B

1
components/icons/svg/views.svg Executable file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#F5F8FA" cx="8.828" cy="18" rx="7.953" ry="13.281"/><path fill="#E1E8ED" d="M8.828 32.031C3.948 32.031.125 25.868.125 18S3.948 3.969 8.828 3.969 17.531 10.132 17.531 18s-3.823 14.031-8.703 14.031zm0-26.562C4.856 5.469 1.625 11.09 1.625 18s3.231 12.531 7.203 12.531S16.031 24.91 16.031 18 12.8 5.469 8.828 5.469z"/><circle fill="#8899A6" cx="6.594" cy="18" r="4.96"/><circle fill="#292F33" cx="6.594" cy="18" r="3.565"/><circle fill="#F5F8FA" cx="7.911" cy="15.443" r="1.426"/><ellipse fill="#F5F8FA" cx="27.234" cy="18" rx="7.953" ry="13.281"/><path fill="#E1E8ED" d="M27.234 32.031c-4.88 0-8.703-6.163-8.703-14.031s3.823-14.031 8.703-14.031S35.938 10.132 35.938 18s-3.824 14.031-8.704 14.031zm0-26.562c-3.972 0-7.203 5.622-7.203 12.531 0 6.91 3.231 12.531 7.203 12.531S34.438 24.91 34.438 18 31.206 5.469 27.234 5.469z"/><circle fill="#8899A6" cx="25" cy="18" r="4.96"/><circle fill="#292F33" cx="25" cy="18" r="3.565"/><circle fill="#F5F8FA" cx="26.317" cy="15.443" r="1.426"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="none"><path d="m2.651 6.073 26.275 26.276c.391.391 2.888-2.107 2.497-2.497l-26.275-26.276c-.39-.391-2.888 2.107-2.497 2.497z" fill="#292f33"/><path d="m29.442 31.23-26.296-26.296.883-.883 26.296 26.296z" fill="#66757f"/><path d="m33.546 33.483-.412.412-.671.671a.967.967 0 0 1 -.255.169.988.988 0 0 1 -1.159-.169l-2.102-2.102.495-.495.883-.883 1.119-1.119 2.102 2.102a.999.999 0 0 1 0 1.414zm-29.517-28.693-.883.883-.495.495-2.209-2.208a.988.988 0 0 1 -.169-1.159.967.967 0 0 1 .169-.255l.671-.671.412-.412a.999.999 0 0 1 1.414 0l2.208 2.208z" fill="#e1e8ed"/><path d="m30.325 30.497 2.809 2.809-.671.671a.967.967 0 0 1 -.255.169l-2.767-2.767zm-27.179-25.413-2.873-2.873a.967.967 0 0 1 .169-.255l.671-.671 2.916 2.916z" fill="#f5f8fa"/><path d="m27.897 10.219 1.542.571.6 2.2a.667.667 0 0 0 1.287 0l.6-2.2 1.542-.571a.665.665 0 0 0 0-1.25l-1.534-.568-.605-2.415a.667.667 0 0 0 -1.293 0l-.605 2.415-1.534.568a.665.665 0 0 0 0 1.25m-16.936 9.628 2.61.966.966 2.61a1.103 1.103 0 0 0 2.07 0l.966-2.61 2.609-.966a1.103 1.103 0 0 0 0-2.07l-2.609-.966-.966-2.61a1.105 1.105 0 0 0 -2.07 0l-.966 2.61-2.61.966a1.104 1.104 0 0 0 0 2.07m12.169-15.487 1.383.512.512 1.382a.585.585 0 0 0 1.096 0l.512-1.382 1.382-.512a.584.584 0 0 0 0-1.096l-1.382-.512-.512-1.382a.585.585 0 0 0 -1.096 0l-.512 1.382-1.383.512a.585.585 0 0 0 0 1.096" fill="#ffac33"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m4.861 9.147c.94-.657 2.357-.531 3.201.166l-.968-1.407c-.779-1.111-.5-2.313.612-3.093 1.112-.777 4.263 1.312 4.263 1.312-.786-1.122-.639-2.544.483-3.331 1.122-.784 2.67-.513 3.456.611l10.42 14.72-1.328 12.875-11.083-4.042-9.667-14.333c-.793-1.129-.519-2.686.611-3.478z" fill="#ef9645"/><path d="m2.695 17.336s-1.132-1.65.519-2.781c1.649-1.131 2.78.518 2.78.518l5.251 7.658c.181-.302.379-.6.6-.894l-7.288-10.627s-1.131-1.649.519-2.78c1.649-1.131 2.78.518 2.78.518l6.855 9.997c.255-.208.516-.417.785-.622l-7.947-11.591s-1.131-1.649.519-2.78c1.649-1.131 2.78.518 2.78.518l7.947 11.589c.292-.179.581-.334.871-.498l-7.428-10.832s-1.131-1.649.518-2.78 2.78.518 2.78.518l7.854 11.454 1.194 1.742c-4.948 3.394-5.419 9.779-2.592 13.902.565.825 1.39.26 1.39.26-3.393-4.949-2.357-10.51 2.592-13.903l-1.459-7.302s-.545-1.924 1.378-2.47c1.924-.545 2.47 1.379 2.47 1.379l1.685 5.004c.668 1.984 1.379 3.961 2.32 5.831 2.657 5.28 1.07 11.842-3.94 15.279-5.465 3.747-12.936 2.354-16.684-3.11z" fill="#ffdc5d"/><g fill="#5dadec"><path d="m12 32.042c-4 0-8.042-4.042-8.042-8.042 0-.553-.405-1-.958-1s-1.042.447-1.042 1c0 6 4.042 10.042 10.042 10.042.553 0 1-.489 1-1.042s-.447-.958-1-.958z"/><path d="m7 34c-3 0-5-2-5-5 0-.553-.447-1-1-1s-1 .447-1 1c0 4 3 7 7 7 .553 0 1-.447 1-1s-.447-1-1-1zm17-32c-.552 0-1 .448-1 1s.448 1 1 1c4 0 8 3.589 8 8 0 .552.448 1 1 1s1-.448 1-1c0-5.514-4-10-10-10z"/><path d="m29 .042c-.552 0-1 .406-1 .958s.448 1.042 1 1.042c3 0 4.958 2.225 4.958 4.958 0 .552.489 1 1.042 1s.958-.448.958-1c0-3.837-2.958-6.958-6.958-6.958z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,56 @@
type Props = {
boxes?: number;
timing?: number;
width: number;
};
export default function Loading({ boxes = 3, timing = 0.1, width }: Props) {
// each box is just an empty div
const divs = [];
// allow a custom number of pulsing boxes (defaults to 3)
for (let i = 0; i < boxes; i++) {
// width of each box correlates with number of boxes (with a little padding)
// each individual box's animation has a staggered start in corresponding order
divs.push(
<div key={i}>
<style jsx>{`
div {
display: inline-block;
width: ${width / (boxes + 1)}px;
height: 100%;
animation: loading 1.5s infinite ease-in-out both;
animation-delay: ${i * timing}s;
background-color: var(--medium-light);
}
/* modified from https://tobiasahlin.com/spinkit/ */
@keyframes loading {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(0.6);
}
}
`}</style>
</div>
);
}
return (
<div>
{divs}
<style jsx>{`
div {
width: ${width}px;
height: ${width / 2}px;
display: inline-block;
text-align: center;
}
`}</style>
</div>
);
}

View File

@@ -0,0 +1,91 @@
import Link from "next/link";
import Image from "next/image";
import TweetEmbed from "react-tweet-embed";
import Gist from "react-gist";
import getNodeText from "../lib/getNodeText";
import Video from "./video/FullPageVideo";
import CopyButton from "./clipboard/CopyButton";
import { MarkGithubIcon } from "@primer/octicons-react";
import type { LinkProps } from "next/link";
import type { ImageProps } from "next/image";
import type { ReactPlayerProps } from "react-player";
// The following components are all passed into <MDXProvider /> as replacement HTML tags or drop-in React components
// available in .mdx files containing post content, since they're not directly aware of the components in this folder.
const mdxComponents = {
a: ({
href,
target,
rel,
className,
children,
}: LinkProps & {
target?: string;
rel?: string;
className?: string;
children?: unknown;
}) => (
<Link href={href} passHref={true}>
<a className={className} target={target} rel={rel}>
{children}
</a>
</Link>
),
img: (props: ImageProps) => {
return (
// height and width are part of the props, so they get automatically passed here with {...props}
<div className={props.className}>
{/* eslint-disable-next-line jsx-a11y/alt-text */}
<Image {...props} />
</div>
);
},
code: (props: any) => {
if (props.className?.split(" ").includes("hljs")) {
return (
<div>
<CopyButton content={getNodeText(props.children)} />
<code {...props}>{props.children}</code>
<style jsx>{`
div {
position: relative;
max-width: 100%;
overflow-x: scroll;
margin: 1em 0;
}
`}</style>
</div>
);
} else {
return <code {...props}>{props.children}</code>;
}
},
video: (props: ReactPlayerProps) => <Video {...props} />,
tweet: (props: { id: string }) => (
<TweetEmbed
id={props.id}
options={{
dnt: true,
align: "center",
}}
/>
),
gist: (props: { id: string; file?: string }) => <Gist {...props} />,
octocat: (props) => (
<a className="no-underline" href={`https://github.com/${props.repo}`} target="_blank" rel="noopener noreferrer">
<MarkGithubIcon size={24} verticalAlign="text-top" />
<style jsx>{`
a {
margin: 0 0.3em;
color: var(--text);
}
a:hover {
color: var(--link);
}
`}</style>
</a>
),
};
export default mdxComponents;

View File

@@ -0,0 +1,27 @@
.section {
margin: 1.5em 0;
.year {
font-size: 2.1em;
margin-top: 0;
margin-bottom: 0.4em;
}
.list {
list-style-type: none;
margin: 0;
padding: 0;
li:last-of-type {
margin-bottom: 0;
}
}
&:first-of-type {
margin-top: 0;
}
&:last-of-type {
margin-bottom: 0;
}
}

22
components/notes/List.tsx Normal file
View File

@@ -0,0 +1,22 @@
import ListItem from "./ListItem";
import styles from "./List.module.scss";
export default function List({ allNotes }) {
const sections = [];
Object.entries(allNotes).forEach(([year, notes]: [string, any]) => {
sections.push(
<section key={year} className={styles.section}>
<h2 className={styles.year}>{year}</h2>
<ul className={styles.list}>
{notes.map((note) => (
<ListItem key={note.slug} title={note.title} date={note.date} slug={note.slug} />
))}
</ul>
</section>
);
});
return <>{sections.reverse()}</>;
}

View File

@@ -0,0 +1,12 @@
.row {
display: flex;
letter-spacing: -0.008em;
line-height: 1.75;
margin-bottom: 1em;
}
.date {
width: 5.25em;
flex-shrink: 0;
color: var(--medium);
}

View File

@@ -0,0 +1,23 @@
import Link from "next/link";
import { format, parseISO } from "date-fns";
import styles from "./ListItem.module.scss";
export type Props = {
title: string;
date: string;
slug: string;
};
export default function ListItem({ title, date, slug }: Props) {
return (
<li className={styles.row}>
<span className={styles.date}>{format(parseISO(date), "MMM d")}</span>
<span>
<Link href={`/notes/${slug}/`} prefetch={false}>
<a>{title}</a>
</Link>
</span>
</li>
);
}

View File

@@ -0,0 +1,77 @@
.meta {
display: flex;
flex-wrap: wrap;
font-size: 0.825em;
line-height: 2.3;
letter-spacing: 0.04em;
color: var(--medium);
a {
color: inherit;
background: none;
padding-bottom: 0;
}
> div {
display: inline-flex;
margin-right: 1.6em;
white-space: nowrap;
&:last-of-type {
margin-right: 0;
}
}
}
.meta_icon {
margin-right: 0.6em;
user-select: none;
.icon_svg {
vertical-align: -0.2em;
cursor: inherit;
}
}
.date,
.edit {
a {
display: inline-flex;
}
}
.tags {
white-space: normal;
display: inline-flex;
flex-wrap: wrap;
.tag {
text-transform: lowercase;
white-space: nowrap;
margin-right: 0.75em;
&::before {
content: "#"; // cosmetically hashtagify tags
padding-right: 0.125em;
color: var(--light);
}
&:last-of-type {
margin-right: 0;
}
}
}
.title {
margin: 0.3em 0 0.5em -0.03em; // TODO: why is this indented slightly?
font-size: 2.1em;
line-height: 1.3;
font-weight: 700;
letter-spacing: -0.006em;
a {
background: none;
padding-bottom: 0;
color: inherit;
}
}

68
components/notes/Meta.tsx Normal file
View File

@@ -0,0 +1,68 @@
import Link from "next/link";
import { format, parseISO } from "date-fns";
import Hits from "../hits/Hits";
import { DateIcon, TagIcon, EditIcon, ViewsIcon } from "../icons";
import * as config from "../../lib/config";
import styles from "./Meta.module.scss";
export type Props = {
title: string;
date: string;
slug: string;
tags?: string[];
};
export default function Meta({ title, date, slug, tags = [] }: Props) {
return (
<>
<div className={styles.meta}>
<div className={styles.date}>
<span className={styles.meta_icon}>
<DateIcon className={`icon ${styles.icon_svg}`} />
</span>
<span title={format(parseISO(date), "PPppp")}>{format(parseISO(date), "MMMM d, yyyy")}</span>
</div>
{tags.length > 0 && (
<div className={styles.tags}>
<span className={styles.meta_icon}>
<TagIcon className={`icon ${styles.icon_svg}`} />
</span>
{tags.map((tag) => (
<span key={tag} className={styles.tag}>
{tag}
</span>
))}
</div>
)}
<div>
<span className={styles.meta_icon}>
<EditIcon className={`icon ${styles.icon_svg}`} />
</span>
<span>
<a
href={`https://github.com/${config.githubRepo}/blob/main/notes/${slug}.mdx`}
target="_blank"
rel="noopener noreferrer"
title={`Edit "${title}" on GitHub`}
>
Improve This Post
</a>
</span>
</div>
<div>
<span className={styles.meta_icon}>
<ViewsIcon className={`icon ${styles.icon_svg}`} />
</span>
<Hits slug={`notes/${slug}`} />
</div>
</div>
<h1 className={styles.title}>
<Link href={`/notes/${slug}/`}>
<a>{title}</a>
</Link>
</h1>
</>
);
}

View File

@@ -0,0 +1,78 @@
.footer {
width: 100%;
letter-spacing: -0.005em;
padding: 1.25em 1.5em;
border-top: 1px solid var(--kinda-light);
color: var(--medium-dark);
a {
color: var(--medium-dark);
background: none;
padding-bottom: 0;
}
.view_source {
padding-bottom: 2px;
border-bottom: 1px solid;
border-color: var(--light);
}
}
.row {
display: flex;
width: 100%;
max-width: 865px;
margin: 0 auto;
justify-content: space-between;
font-size: 0.85em;
line-height: 2.3;
}
.beat {
display: inline-block;
animation: beat 10s infinite; // 6 bpm, call 911 if you see this please.
animation-delay: 7.5s; // offset from wave animation
will-change: transform;
}
@media screen and (max-width: 800px) {
.footer {
padding: 1em 1.25em 0;
// Safari iOS menu bar reappears below 44px:
// https://www.eventbrite.com/engineering/mobile-safari-why/
padding-bottom: 45px !important;
// Allows you to scroll below the viewport; default value is visible
overflow-y: scroll;
}
// stack columns on left instead of flexboxing across
.row {
display: block;
line-height: 2;
}
}
@keyframes beat {
0% {
transform: scale(1);
}
2% {
transform: scale(1.25);
}
4% {
transform: scale(1);
}
6% {
transform: scale(1.2);
}
8% {
transform: scale(1);
}
// pause for ~9 out of 10 seconds
100% {
transform: scale(1);
}
}

View File

@@ -0,0 +1,45 @@
import Link from "next/link";
import { HeartIcon, NextjsIcon } from "../icons";
import * as config from "../../lib/config";
import styles from "./Footer.module.scss";
export default function Footer() {
return (
<footer className={styles.footer}>
<div className={styles.row}>
<div className={styles.copyright}>
Content{" "}
<Link href="/license/" prefetch={false}>
<a title="Creative Commons Attribution 4.0 International">licensed under CC-BY-4.0</a>
</Link>
,{" "}
<Link href="/previously/" prefetch={false}>
<a title="Previously on...">2001 </a>
</Link>{" "}
{new Date().getFullYear()}.
</div>
<div className={styles.powered_by}>
Made with{" "}
<span className={styles.beat}>
<HeartIcon alt="Love" />
</span>{" "}
and{" "}
<a href="https://nextjs.org/" title="Powered by Next.js" target="_blank" rel="noopener noreferrer">
<NextjsIcon alt="Next.js" fill="currentColor" />
</a>
.{" "}
<a
href={`https://github.com/${config.githubRepo}`}
title="View Source on GitHub"
className={styles.view_source}
target="_blank"
rel="noopener noreferrer"
>
View source.
</a>
</div>
</div>
</footer>
);
}

View File

@@ -0,0 +1,25 @@
.header {
position: sticky;
top: 0;
width: 100%;
padding: 0.7em 1.5em;
border-bottom: 1px solid var(--kinda-light);
background-color: var(--background-header);
backdrop-filter: saturate(180%) blur(5px);
z-index: 1000;
}
.nav {
width: 100%;
max-width: 865px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
@media screen and (max-width: 800px) {
.header {
padding: 0.5em 1.25em;
}
}

View File

@@ -0,0 +1,15 @@
import Name from "./Name";
import Menu from "./Menu";
import styles from "./Header.module.scss";
export default function Header() {
return (
<header className={styles.header}>
<nav className={styles.nav}>
<Name />
<Menu />
</nav>
</header>
);
}

View File

@@ -0,0 +1,38 @@
.menu {
list-style: none;
display: flex;
align-items: center;
margin: 0;
padding: 0;
}
.item {
margin-left: 1.8em;
display: inline-flex;
align-items: center;
line-height: 1;
svg {
width: 1.6em;
height: 1.6em;
}
}
.theme_toggle {
margin-left: 1.25em;
}
@media screen and (max-width: 800px) {
.item {
margin-left: 2em;
svg {
width: 1.75em;
height: 1.75em;
}
}
.theme_toggle {
margin-left: 1.6em;
}
}

View File

@@ -0,0 +1,46 @@
import dynamic from "next/dynamic";
import MenuItem from "./MenuItem";
import { HomeIcon, NotesIcon, ProjectsIcon, ContactIcon } from "../icons";
import styles from "./Menu.module.scss";
const menuItems = [
{
icon: <HomeIcon />,
text: "Home",
href: "/",
},
{
icon: <NotesIcon />,
text: "Notes",
href: "/notes/",
},
{
icon: <ProjectsIcon />,
text: "Projects",
href: "/projects/",
},
{
icon: <ContactIcon />,
text: "Contact",
href: "/contact/",
},
];
// ensure the theme toggle isn't evaluated server-side
const ThemeToggle = dynamic(() => import("./ThemeToggle"), { ssr: false });
export default function Menu() {
return (
<ul className={styles.menu}>
{menuItems.map((item, index) => (
<li key={index} className={styles.item}>
<MenuItem {...item} />
</li>
))}
<li className={`${styles.item} ${styles.theme_toggle}`}>
<ThemeToggle />
</li>
</ul>
);
}

View File

@@ -0,0 +1,30 @@
.item_link {
display: inline-flex;
align-items: center;
color: var(--medium-dark);
background: none;
padding-bottom: 0;
&:hover {
color: var(--link);
}
}
.item_icon {
user-select: none;
}
.item_text {
font-size: 0.95em;
font-weight: 500;
margin-top: 0.05em;
margin-left: 0.75em;
line-height: 1;
}
@media screen and (max-width: 800px) {
// hide text next to emojis on mobile
.item_text {
display: none;
}
}

View File

@@ -0,0 +1,20 @@
import Link from "next/link";
import styles from "./MenuItem.module.scss";
type Props = {
href: URL | string;
icon: any;
text: string;
};
export default function MenuItem({ href, icon, text }: Props) {
return (
<Link href={href} prefetch={false}>
<a className={styles.item_link}>
<span className={styles.item_icon}>{icon}</span>
<span className={styles.item_text}>{text}</span>
</a>
</Link>
);
}

View File

@@ -0,0 +1,43 @@
.title {
display: flex;
align-items: center;
color: var(--medium-dark);
background: none;
padding-bottom: 0;
&:hover {
color: var(--link);
.selfie {
opacity: 0.9;
}
}
}
.selfie {
width: 50px;
height: 50px;
img {
border: 1px solid var(--light) !important;
border-radius: 50%;
}
}
.name {
margin: 0 0.6em;
font-size: 1.25em;
font-weight: 500;
letter-spacing: -0.01em;
}
@media screen and (max-width: 800px) {
.selfie {
width: 70px;
height: 70px;
}
.name {
display: none;
}
}

View File

@@ -0,0 +1,27 @@
import Link from "next/link";
import Image from "next/image";
import selfiePic from "../../public/static/images/me.jpg";
import styles from "./Name.module.scss";
export default function Name() {
return (
<Link href="/">
<a className={styles.title}>
<span className={styles.selfie}>
<Image
src={selfiePic}
alt="Photo of Jake Jarvis"
width={75}
height={75}
quality={60}
layout="intrinsic"
priority
/>
</span>
<span className={styles.name}>Jake Jarvis</span>
</a>
</Link>
);
}

View File

@@ -0,0 +1,7 @@
.toggle {
border: 0;
padding: 0;
background: none;
line-height: 1;
cursor: pointer;
}

View File

@@ -0,0 +1,67 @@
import { useState, useEffect, useCallback } from "react";
import { BulbOffIcon, BulbOnIcon } from "../icons";
import styles from "./ThemeToggle.module.scss";
// store preference in local storage
const storageKey = "dark_mode";
export const getDarkPref = () => localStorage.getItem(storageKey);
export const setDarkPref = (pref: boolean) => localStorage.setItem(storageKey, pref as unknown as string);
// use the `<html class="...">` as a hint to what the theme was set to outside of the button component
// there's probably (definitely) a cleaner way to do this..?
export const isDark = () => document.documentElement.getAttribute("data-theme") === "dark";
// sets appropriate `<html data-theme="...">` CSS property
export const updateDOM = (dark: boolean) => {
const root = document.documentElement;
// set `<html data-theme="...">`
root.setAttribute("data-theme", dark ? "dark" : "light");
};
export default function ThemeToggle() {
// sync button up with theme and preference states after initialization
const [dark, setDark] = useState(isDark());
const [saved, setSaved] = useState(!!getDarkPref());
// real-time switching between modes based on user's system if preference isn't set (and it's supported by OS/browser)
const matchCallback = useCallback((e) => setDark(e.matches), []);
useEffect(() => {
try {
// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
const matcher = window.matchMedia("(prefers-color-scheme: dark)");
// only listen to OS if the user hasn't specified a preference
if (!saved) {
matcher.addEventListener("change", matchCallback, true);
}
// cleanup and stop listening if/when preference is explicitly set
return () => matcher.removeEventListener("change", matchCallback, true);
} catch (e) {} // eslint-disable-line no-empty
}, [saved, matchCallback]);
// sets appropriate HTML when mode changes
useEffect(() => updateDOM(dark), [dark]);
const handleToggle = () => {
// only update the local storage preference if the user explicitly presses the lightbulb
setDarkPref(!dark);
setSaved(true);
// set theme to the opposite of current theme
setDark(!dark);
};
return (
<button
className={styles.toggle}
onClick={handleToggle}
title={dark ? "Toggle Light Mode" : "Toggle Dark Mode"}
aria-label={dark ? "Toggle Light Mode" : "Toggle Dark Mode"}
>
{dark ? <BulbOffIcon /> : <BulbOnIcon />}
</button>
);
}

View File

@@ -0,0 +1,18 @@
.title {
margin-top: 0;
margin-bottom: 0.4em;
font-size: 2em;
text-align: center;
a {
background: none;
padding-bottom: 0;
color: var(--text);
}
}
@media screen and (max-width: 800px) {
.title {
font-size: 1.8em;
}
}

View File

@@ -0,0 +1,17 @@
import { useRouter } from "next/router";
import styles from "./PageTitle.module.scss";
type Props = {
title: unknown;
};
export default function Content({ title }: Props) {
const router = useRouter();
return (
<h1 className={styles.title}>
<a href={router.asPath}>{title}</a>
</h1>
);
}

View File

@@ -0,0 +1,56 @@
.card {
flex-grow: 1;
width: 416px; // magic number
padding: 1em 1.2em;
margin: 0.6em;
border: 1px solid;
border-radius: 0.5em;
font-size: 0.85em;
color: var(--medium-dark);
border-color: var(--kinda-light);
.name {
font-size: 1.2em;
font-weight: 600;
}
.description {
margin: 0.25em 0 0.75em;
line-height: 1.7;
}
.meta {
display: flex;
flex-wrap: wrap;
.meta_item {
margin-right: 1.5em;
font-size: 0.9em;
color: var(--medium);
a {
background: none;
padding-bottom: 0;
color: inherit;
&:hover {
color: var(--link);
}
}
.octicon,
.language_color {
margin-right: 0.5em;
}
.language_color {
display: inline-block;
width: 1.15em;
height: 1.15em;
border-radius: 50%;
position: relative;
top: 0.175em;
}
}
}
}

View File

@@ -0,0 +1,94 @@
import { intlFormat, formatDistanceToNowStrict, parseISO } from "date-fns";
// react components:
import { StarIcon, RepoForkedIcon } from "@primer/octicons-react";
import styles from "./RepositoryCard.module.scss";
type Props = {
name: string;
url: string;
description?: string;
language?: {
name: string;
color?: string;
};
stars?: number;
forks?: number;
updatedAt: string;
};
export default function RepositoryCard({ name, url, description, language, stars, forks, updatedAt }: Props) {
return (
<div className={styles.card}>
<a className={styles.name} href={url} target="_blank" rel="noopener noreferrer">
{name}
</a>
{description && <p className={styles.description}>{description}</p>}
<div className={styles.meta}>
{language && (
<div className={styles.meta_item}>
<span className={styles.language_color}>
<style jsx>{`
span {
background-color: ${language.color};
}
`}</style>
</span>
<span>{language.name}</span>
</div>
)}
{stars > 0 && (
<div className={styles.meta_item}>
<a
href={`${url}/stargazers`}
title={`${stars.toLocaleString("en-US")} ${stars === 1 ? "star" : "stars"}`}
target="_blank"
rel="noopener noreferrer"
>
<StarIcon size={16} className={styles.octicon} />
<span>{stars.toLocaleString("en-US")}</span>
</a>
</div>
)}
{forks > 0 && (
<div className={styles.meta_item}>
<a
href={`${url}/network/members`}
title={`${forks.toLocaleString("en-US")} ${forks === 1 ? "fork" : "forks"}`}
target="_blank"
rel="noopener noreferrer"
>
<RepoForkedIcon size={16} className={styles.octicon} />
<span>{forks.toLocaleString("en-US")}</span>
</a>
</div>
)}
<div
className={styles.meta_item}
title={intlFormat(
parseISO(updatedAt),
{
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
timeZoneName: "short",
},
{
locale: "en-US",
}
)}
>
<span>Updated {formatDistanceToNowStrict(parseISO(updatedAt), { addSuffix: true })}</span>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,12 @@
.grid {
display: flex;
flex-flow: row wrap;
justify-content: space-evenly;
align-items: flex-start;
width: 100%;
}
.view_more {
text-align: center;
margin-bottom: 0;
}

View File

@@ -0,0 +1,20 @@
import RepositoryCard from "./RepositoryCard";
import styles from "./RepositoryGrid.module.scss";
export default function RepositoryGrid({ repos }) {
return (
<>
<div className={styles.grid}>
{repos.map((repo) => (
<RepositoryCard key={repo.name} {...repo} />
))}
</div>
<p className={styles.view_more}>
<a href="https://github.com/jakejarvis?tab=repositories" target="_blank" rel="noopener noreferrer">
View more on GitHub...
</a>
</p>
</>
);
}

View File

@@ -0,0 +1,10 @@
.wrapper {
position: relative;
padding-top: 56.25%; // 100 / (1280 / 720)
}
.react_player {
position: absolute;
top: 0;
left: 0;
}

View File

@@ -0,0 +1,12 @@
import ReactPlayer from "react-player/lazy";
import type { ReactPlayerProps } from "react-player";
import styles from "./FullPageVideo.module.scss";
export default function Video(props: ReactPlayerProps) {
return (
<div className={styles.wrapper}>
<ReactPlayer className={styles.react_player} width="100%" height="100%" {...props} />
</div>
);
}