1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-09-13 21:35:34 -04:00

next-mdx-remote v4 (#737)

This commit is contained in:
2022-01-18 09:25:09 -05:00
committed by GitHub
parent 2ef5d06c38
commit a406010bd2
110 changed files with 1009 additions and 1490 deletions

View File

@@ -27,7 +27,7 @@ module.exports = {
files: ["*.md", "*.mdx"],
extends: ["plugin:mdx/recommended"],
rules: {
"import/no-unresolved": "off",
"react/jsx-no-undef": "off",
},
},
],

View File

@@ -0,0 +1,97 @@
.code {
position: relative;
width: 100%;
margin: 1em auto;
}
/* the following sub-classes MUST be global -- the highlight rehype plugin isn't aware of this file */
.code :global(.code-highlight) {
display: block;
overflow-x: auto;
padding: 1em;
tab-size: 2;
color: var(--code-text);
background-color: var(--code-background);
}
/* leave room for clipboard button to the right of the first line */
.code :global(.code-highlight) > :global(.code-line:first-of-type) {
margin-right: 3em;
}
.code :global(.code-highlight) > :global(.code-line.line-number::before) {
display: inline-block;
width: 1.5em;
margin-right: 1.5em;
text-align: right;
color: var(--code-comment);
content: attr(line); /* added to spans by prism */
}
.code :global(.code-highlight) :global(.token.comment),
.code :global(.code-highlight) :global(.token.prolog),
.code :global(.code-highlight) :global(.token.cdata) {
color: var(--code-comment);
}
.code :global(.code-highlight) :global(.token.delimiter),
.code :global(.code-highlight) :global(.token.boolean),
.code :global(.code-highlight) :global(.token.keyword),
.code :global(.code-highlight) :global(.token.selector),
.code :global(.code-highlight) :global(.token.important),
.code :global(.code-highlight) :global(.token.doctype),
.code :global(.code-highlight) :global(.token.atrule),
.code :global(.code-highlight) :global(.token.url) {
color: var(--code-keyword);
}
.code :global(.code-highlight) :global(.token.tag),
.code :global(.code-highlight) :global(.token.builtin),
.code :global(.code-highlight) :global(.token.regex) {
color: var(--code-namespace);
}
.code :global(.code-highlight) :global(.token.property),
.code :global(.code-highlight) :global(.token.constant),
.code :global(.code-highlight) :global(.token.variable),
.code :global(.code-highlight) :global(.token.attr-value),
.code :global(.code-highlight) :global(.token.class-name),
.code :global(.code-highlight) :global(.token.string),
.code :global(.code-highlight) :global(.token.char) {
color: var(--code-variable);
}
.code :global(.code-highlight) :global(.token.literal-property),
.code :global(.code-highlight) :global(.token.attr-name) {
color: var(--code-attribute);
}
.code :global(.code-highlight) :global(.token.function) {
color: var(--code-literal);
}
.code :global(.code-highlight) :global(.token.tag .punctuation),
.code :global(.code-highlight) :global(.token.attr-value .punctuation) {
color: var(--code-punctuation);
}
.code :global(.code-highlight) :global(.token.inserted) {
background-color: var(--code-addition);
}
.code :global(.code-highlight) :global(.token.deleted) {
background-color: var(--code-deletion);
}
.code :global(.code-highlight) :global(.token.url) {
text-decoration: underline;
}
.code :global(.code-highlight) :global(.token.bold) {
font-weight: bold;
}
.code :global(.code-highlight) :global(.token.italic) {
font-style: italic;
}

View File

@@ -1,19 +1,19 @@
import CopyButton from "./CopyButton";
import CopyButton from "../CopyButton/CopyButton";
import type { ReactNode } from "react";
import styles from "./Code.module.css";
import styles from "./CodeBlock.module.css";
export type CustomCodeProps = {
export type Props = {
className?: string;
children: ReactNode;
};
const CustomCode = (props: CustomCodeProps) => {
const CodeBlock = (props: Props) => {
if (props.className?.split(" ").includes("code-highlight")) {
// full multi-line code blocks with prism highlighting and copy-to-clipboard button
return (
<>
<div className={styles.code_block}>
<div className={styles.code}>
<CopyButton source={props.children} />
<code {...props}>{props.children}</code>
</div>
@@ -25,4 +25,4 @@ const CustomCode = (props: CustomCodeProps) => {
}
};
export default CustomCode;
export default CodeBlock;

View File

@@ -3,7 +3,7 @@ import Link from "next/link";
import css from "styled-jsx/css";
import type { ReactNode } from "react";
type ColorLinkProps = {
type Props = {
children: ReactNode;
href: string;
lightColor: string;
@@ -12,7 +12,7 @@ type ColorLinkProps = {
external?: boolean;
};
const getFancyLinkStyles = ({ lightColor, darkColor }: Partial<ColorLinkProps>) => {
const getFancyLinkStyles = ({ lightColor, darkColor }: Partial<Props>) => {
// spits out a linear-gradient (that's not realy a gradient) with translucent color in rgba() format
const linearGradient = (hex: string, alpha = 0.4) => {
// hex -> rgb, adapted from https://github.com/sindresorhus/hex-rgb/blob/main/index.js
@@ -39,7 +39,7 @@ const getFancyLinkStyles = ({ lightColor, darkColor }: Partial<ColorLinkProps>)
`;
};
const ColorLink = ({ href, title, lightColor, darkColor, external = false, children }: ColorLinkProps) => {
const ColorfulLink = ({ href, title, lightColor, darkColor, external = false, children }: Props) => {
const { className, styles } = getFancyLinkStyles({ lightColor, darkColor });
return (
@@ -60,4 +60,4 @@ const ColorLink = ({ href, title, lightColor, darkColor, external = false, child
);
};
export default memo(ColorLink);
export default memo(ColorfulLink);

View File

@@ -4,7 +4,7 @@ import classNames from "classnames/bind";
import { Formik, Form, Field } from "formik";
import HCaptcha from "@hcaptcha/react-hcaptcha";
import isEmailLike from "is-email-like";
import { SendIcon, CheckOcticon, XOcticon } from "../icons";
import { SendIcon, CheckOcticon, XOcticon } from "../Icons";
import type { FormikHelpers } from "formik";

View File

@@ -1,6 +1,7 @@
import styles from "./Content.module.css";
import type { ReactNode } from "react";
import styles from "./Content.module.css";
type Props = {
children: ReactNode;
};

View File

@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
import classNames from "classnames/bind";
import copy from "copy-to-clipboard";
import innerText from "react-innertext";
import { ClipboardOcticon, CheckOcticon } from "../icons";
import { ClipboardOcticon, CheckOcticon } from "../Icons";
import type { ReactNode } from "react";
import styles from "./CopyButton.module.css";

View File

@@ -1,14 +1,14 @@
import Image from "./Image";
import Image from "../Image/Image";
import innerText from "react-innertext";
import type { ReactNode } from "react";
import type { ImageProps } from "next/image";
import type { ImageProps as NextImageProps } from "next/image";
type CustomFigureProps = Omit<ImageProps, "alt"> & {
type Props = Omit<NextImageProps, "alt"> & {
children: ReactNode; // caption (can be in markdown, yay!!!)
alt?: string; // becomes optional -- pulled from plaintext-ified caption if missing
};
const CustomFigure = ({ children, alt, ...imageProps }: CustomFigureProps) => {
const Figure = ({ children, alt, ...imageProps }: Props) => {
return (
<figure>
<Image alt={alt || innerText(children)} {...imageProps} />
@@ -17,4 +17,4 @@ const CustomFigure = ({ children, alt, ...imageProps }: CustomFigureProps) => {
);
};
export default CustomFigure;
export default Figure;

View File

@@ -1,6 +1,6 @@
import { memo } from "react";
import Link from "next/link";
import { HeartIcon, NextjsLogo } from "../icons";
import { HeartIcon, NextjsLogo } from "../Icons";
import * as config from "../../lib/config";
import styles from "./Footer.module.css";

View File

@@ -0,0 +1,3 @@
import GistEmbed from "react-gist";
export default GistEmbed;

View File

@@ -0,0 +1,52 @@
.header {
position: sticky;
top: 0;
width: 100%;
height: 4.5em;
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 {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 865px;
margin: 0 auto;
}
.nav > div {
line-height: 0;
}
.left {
flex: 1;
}
@media screen and (max-width: 768px) {
.header {
padding: 0.75em 1.25em;
height: 5.9em;
}
.left {
flex: 0;
}
.right {
flex: 1;
max-width: 325px;
margin-left: 2.5em;
}
}
@media screen and (max-width: 380px) {
.right {
max-width: 225px;
margin-left: 1.6em;
}
}

View File

@@ -0,0 +1,21 @@
import { memo } from "react";
import Name from "../Name/Name";
import Menu from "../Menu/Menu";
import styles from "./Header.module.css";
const Header = () => (
<header className={styles.header}>
<nav className={styles.nav}>
<div className={styles.left}>
<Name />
</div>
<div className={styles.right}>
<Menu />
</div>
</nav>
</header>
);
export default memo(Header);

View File

@@ -1,5 +1,5 @@
import useSWR from "swr";
import Loading from "../loading/Loading";
import Loading from "../Loading/Loading";
import { fetcher } from "../../lib/fetcher";
const HitCounter = ({ slug }) => {

View File

@@ -0,0 +1,21 @@
import NextImage from "next/image";
import type { ImageProps as NextImageProps } from "next/image";
const Image = ({ src, width, height, alt, quality, priority }: NextImageProps) => {
return (
<div className="image_wrapper">
<NextImage
src={(src as string).replace(/^\/public/g, "")}
layout="intrinsic"
width={width}
height={height}
alt={alt || ""}
quality={quality || 65}
loading={priority ? "eager" : "lazy"}
priority={!!priority}
/>
</div>
);
};
export default Image;

View File

@@ -1,8 +1,8 @@
import Head from "next/head";
import { useTheme } from "next-themes";
import Header from "./header/Header";
import Footer from "./footer/Footer";
import { themeColors } from "../lib/config";
import Header from "../Header/Header";
import Footer from "../Footer/Footer";
import { themeColors } from "../../lib/config";
import type { ReactNode } from "react";
import styles from "./Layout.module.css";

View File

@@ -0,0 +1,71 @@
.menu {
display: inline-flex;
padding: 0;
margin: 0;
}
.menu li {
list-style: none;
display: inline-flex;
margin-left: 1.8em;
}
.menu li .link {
display: inline-flex;
align-items: center;
color: var(--medium-dark);
background: none;
padding-bottom: 0;
}
.menu li .link:hover {
color: var(--link);
}
.menu li .icon {
width: 1.6em;
height: 1.6em;
}
.menu li span {
font-size: 0.95em;
font-weight: 500;
margin-left: 0.8em;
line-height: 1;
}
.menu li.theme_toggle {
margin-left: 1.25em;
}
@media screen and (max-width: 768px) {
.menu {
width: 100%;
justify-content: space-between;
}
.menu li {
margin-left: 0;
}
.menu li .icon {
width: 1.8em;
height: 1.8em;
}
/* hide text next to emojis on mobile */
.menu li span {
display: none;
}
.menu li.theme_toggle {
margin-left: -0.3em;
}
}
/* the home icon is redundant when space is SUPER tight */
@media screen and (max-width: 380px) {
.menu li:first-of-type {
display: none;
}
}

49
components/Menu/Menu.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { memo } from "react";
import Link from "next/link";
import ThemeToggle from "../ThemeToggle/ThemeToggle";
import { HomeIcon, NotesIcon, ProjectsIcon, ContactIcon } from "../Icons";
import styles from "./Menu.module.css";
const links = [
{
icon: <HomeIcon className={`icon ${styles.icon}`} />,
text: "Home",
href: "/",
},
{
icon: <NotesIcon className={`icon ${styles.icon}`} />,
text: "Notes",
href: "/notes/",
},
{
icon: <ProjectsIcon className={`icon ${styles.icon}`} />,
text: "Projects",
href: "/projects/",
},
{
icon: <ContactIcon className={`icon ${styles.icon}`} />,
text: "Contact",
href: "/contact/",
},
];
const Menu = () => (
<ul className={styles.menu}>
{links.map((link, index) => (
<li key={index}>
<Link href={link.href} prefetch={false}>
<a className={styles.link}>
{link.icon} <span>{link.text}</span>
</a>
</Link>
</li>
))}
<li className={styles.theme_toggle}>
<ThemeToggle className={styles.icon} />
</li>
</ul>
);
export default memo(Menu);

View File

@@ -0,0 +1,45 @@
.name {
display: inline-flex;
align-items: center;
color: var(--medium-dark);
background: none;
padding-bottom: 0;
}
.name .selfie {
width: 50px;
height: 50px;
line-height: 0;
padding: 0;
}
.name .selfie img {
border: 1px solid var(--light) !important;
border-radius: 50%;
}
.name:hover {
color: var(--link);
}
.name:hover .selfie {
opacity: 0.9;
}
.name span:last-of-type {
margin: 0 0.6em;
font-size: 1.2em;
font-weight: 500;
line-height: 1;
}
@media screen and (max-width: 768px) {
.name .selfie {
width: 70px;
height: 70px;
}
.name span:last-of-type {
display: none;
}
}

20
components/Name/Name.tsx Normal file
View File

@@ -0,0 +1,20 @@
import { memo } from "react";
import Link from "next/link";
import Image from "next/image";
import styles from "./Name.module.css";
import meJpg from "../../public/static/images/me.jpg";
const Name = () => (
<Link href="/">
<a className={styles.name}>
<div className={styles.selfie}>
<Image src={meJpg} alt="Photo of Jake Jarvis" width={70} height={70} quality={60} layout="intrinsic" priority />
</div>
<span>Jake Jarvis</span>
</a>
</Link>
);
export default memo(Name);

View File

@@ -1,13 +1,13 @@
import Link from "next/link";
import { format } from "date-fns";
import HitCounter from "./HitCounter";
import { DateIcon, TagIcon, EditIcon, ViewsIcon } from "../icons";
import HitCounter from "../HitCounter/HitCounter";
import { DateIcon, TagIcon, EditIcon, ViewsIcon } from "../Icons";
import * as config from "../../lib/config";
import type { NoteMetaType } from "../../types";
import styles from "./Meta.module.css";
import styles from "./NoteMeta.module.css";
const Meta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaType) => (
const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaType) => (
<>
<div className={styles.meta}>
<div className={styles.date}>
@@ -66,4 +66,4 @@ const Meta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaType) => (
</>
);
export default Meta;
export default NoteMeta;

View File

@@ -2,9 +2,9 @@ import Link from "next/link";
import { format } from "date-fns";
import type { NoteMetaType } from "../../types";
import styles from "./List.module.css";
import styles from "./NotesList.module.css";
const List = ({ notesByYear }) => {
const NotesList = ({ notesByYear }) => {
const sections = [];
Object.entries(notesByYear).forEach(([year, notes]: [string, NoteMetaType[]]) => {
@@ -39,4 +39,4 @@ const List = ({ notesByYear }) => {
return <>{reversed}</>;
};
export default List;
export default NotesList;

View File

@@ -0,0 +1,19 @@
import { OctocatOcticon } from "../Icons";
type Props = {
repo: string;
};
const OctocatLink = (props: Props) => (
<a
className="no-underline"
href={`https://github.com/${props.repo}`}
target="_blank"
rel="noopener noreferrer"
style={{ margin: "0 0.4em", color: "var(--text)" }}
>
<OctocatOcticon fill="currentColor" />
</a>
);
export default OctocatLink;

View File

@@ -1,10 +1,10 @@
import { intlFormat, formatDistanceToNowStrict } from "date-fns";
import { StarOcticon, ForkOcticon } from "../icons";
import { StarOcticon, ForkOcticon } from "../Icons";
import { RepoType } from "../../types";
import styles from "./RepoCard.module.css";
import styles from "./RepositoryCard.module.css";
const RepoCard = (props: RepoType) => (
const RepositoryCard = (props: RepoType) => (
<div className={styles.card}>
<a className={styles.name} href={props.url} target="_blank" rel="noopener noreferrer">
{props.name}
@@ -71,4 +71,4 @@ const RepoCard = (props: RepoType) => (
</div>
);
export default RepoCard;
export default RepositoryCard;

View File

@@ -0,0 +1,17 @@
import Tweet from "react-tweet-embed";
type Props = {
id: string;
};
const TweetEmbed = (props: Props) => (
<Tweet
id={props.id}
options={{
dnt: true,
align: "center",
}}
/>
);
export default TweetEmbed;

View File

@@ -0,0 +1,10 @@
.wrapper {
position: relative;
padding-top: 56.25%;
}
.wrapper > div {
position: absolute;
top: 0;
left: 0;
}

View File

@@ -0,0 +1,63 @@
import ReactPlayer from "react-player/file";
import styles from "./Video.module.css";
type Props = {
webm?: string;
mp4?: string;
thumbnail?: string;
subs?: string;
autoplay?: boolean;
};
const Video = ({ webm, mp4, thumbnail, subs, autoplay }: Props) => {
const url = [
webm && {
src: webm,
type: "video/webm",
},
mp4 && {
src: mp4,
type: "video/mp4",
},
];
const config = {
file: {
attributes: {
controlsList: "nodownload",
preload: "metadata",
autoPlay: autoplay,
muted: autoplay,
loop: autoplay,
},
},
};
if (thumbnail) {
// @ts-ignore
config.file.attributes.poster = thumbnail;
}
if (subs) {
// @ts-ignore
config.file.tracks = [
{
kind: "subtitles",
src: subs,
srcLang: "en",
label: "English",
default: true,
},
];
}
return (
<div className={styles.wrapper}>
{/* @ts-ignore */}
<ReactPlayer width="100%" height="100%" url={url} config={config} controls={!autoplay} />
</div>
);
};
export default Video;

View File

@@ -0,0 +1,10 @@
.wrapper {
position: relative;
padding-top: 56.25%;
}
.wrapper > div {
position: absolute;
top: 0;
left: 0;
}

View File

@@ -0,0 +1,21 @@
import ReactPlayer from "react-player/youtube";
import styles from "./YouTubeEmbed.module.css";
type Props = {
id: string;
};
const YouTubeEmbed = ({ id }: Props) => (
<div className={styles.wrapper}>
<ReactPlayer
width="100%"
height="100%"
url={`https://www.youtube-nocookie.com/watch?v=${id}`}
light={`https://i.ytimg.com/vi/${id}/hqdefault.jpg`}
controls
/>
</div>
);
export default YouTubeEmbed;

View File

@@ -1,97 +0,0 @@
.code_block {
position: relative;
width: 100%;
margin: 1em auto;
}
/* the following sub-classes MUST be global -- the highlight rehype plugin isn't aware of this file */
.code_block :global(.code-highlight) {
display: block;
overflow-x: auto;
padding: 1em;
tab-size: 2;
color: var(--code-text);
background-color: var(--code-background);
}
/* leave room for clipboard button to the right of the first line */
.code_block :global(.code-highlight) > :global(.code-line:first-of-type) {
margin-right: 3em;
}
.code_block :global(.code-highlight) > :global(.code-line.line-number::before) {
display: inline-block;
width: 1.5em;
margin-right: 1.5em;
text-align: right;
color: var(--code-comment);
content: attr(line); /* added to spans by prism */
}
.code_block :global(.code-highlight) :global(.token.comment),
.code_block :global(.code-highlight) :global(.token.prolog),
.code_block :global(.code-highlight) :global(.token.cdata) {
color: var(--code-comment);
}
.code_block :global(.code-highlight) :global(.token.delimiter),
.code_block :global(.code-highlight) :global(.token.boolean),
.code_block :global(.code-highlight) :global(.token.keyword),
.code_block :global(.code-highlight) :global(.token.selector),
.code_block :global(.code-highlight) :global(.token.important),
.code_block :global(.code-highlight) :global(.token.doctype),
.code_block :global(.code-highlight) :global(.token.atrule),
.code_block :global(.code-highlight) :global(.token.url) {
color: var(--code-keyword);
}
.code_block :global(.code-highlight) :global(.token.tag),
.code_block :global(.code-highlight) :global(.token.builtin),
.code_block :global(.code-highlight) :global(.token.regex) {
color: var(--code-namespace);
}
.code_block :global(.code-highlight) :global(.token.property),
.code_block :global(.code-highlight) :global(.token.constant),
.code_block :global(.code-highlight) :global(.token.variable),
.code_block :global(.code-highlight) :global(.token.attr-value),
.code_block :global(.code-highlight) :global(.token.class-name),
.code_block :global(.code-highlight) :global(.token.string),
.code_block :global(.code-highlight) :global(.token.char) {
color: var(--code-variable);
}
.code_block :global(.code-highlight) :global(.token.literal-property),
.code_block :global(.code-highlight) :global(.token.attr-name) {
color: var(--code-attribute);
}
.code_block :global(.code-highlight) :global(.token.function) {
color: var(--code-literal);
}
.code_block :global(.code-highlight) :global(.token.tag .punctuation),
.code_block :global(.code-highlight) :global(.token.attr-value .punctuation) {
color: var(--code-punctuation);
}
.code_block :global(.code-highlight) :global(.token.inserted) {
background-color: var(--code-addition);
}
.code_block :global(.code-highlight) :global(.token.deleted) {
background-color: var(--code-deletion);
}
.code_block :global(.code-highlight) :global(.token.url) {
text-decoration: underline;
}
.code_block :global(.code-highlight) :global(.token.bold) {
font-weight: bold;
}
.code_block :global(.code-highlight) :global(.token.italic) {
font-style: italic;
}

View File

@@ -1,168 +0,0 @@
.header {
position: sticky;
top: 0;
width: 100%;
height: 4.5em;
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 {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 865px;
margin: 0 auto;
}
.nav > div {
line-height: 0;
}
/*** left side: photo/name ***/
.left {
flex: 1;
}
.name {
display: inline-flex;
align-items: center;
color: var(--medium-dark);
background: none;
padding-bottom: 0;
}
.name .selfie {
width: 50px;
height: 50px;
line-height: 0;
padding: 0;
}
.name .selfie img {
border: 1px solid var(--light) !important;
border-radius: 50%;
}
.name:hover {
color: var(--link);
}
.name:hover .selfie {
opacity: 0.9;
}
.name span:last-of-type {
margin: 0 0.6em;
font-size: 1.2em;
font-weight: 500;
line-height: 1;
}
/*** right side: menu ***/
.menu {
display: inline-flex;
padding: 0;
margin: 0;
}
.menu li {
list-style: none;
display: inline-flex;
margin-left: 1.8em;
}
.menu li .link {
display: inline-flex;
align-items: center;
color: var(--medium-dark);
background: none;
padding-bottom: 0;
}
.menu li .link:hover {
color: var(--link);
}
.menu li .icon {
width: 1.6em;
height: 1.6em;
}
.menu li span {
font-size: 0.95em;
font-weight: 500;
margin-left: 0.8em;
line-height: 1;
}
.menu li.theme_toggle {
margin-left: 1.25em;
}
@media screen and (max-width: 768px) {
.header {
padding: 0.75em 1.25em;
height: 5.9em;
}
.left {
flex: 0;
}
.name .selfie {
width: 70px;
height: 70px;
}
.name span:last-of-type {
display: none;
}
.right {
flex: 1;
max-width: 325px;
margin-left: 2.5em;
}
.menu {
width: 100%;
justify-content: space-between;
}
.menu li {
margin-left: 0;
}
.menu li .icon {
width: 1.8em;
height: 1.8em;
}
/* hide text next to emojis on mobile */
.menu li span {
display: none;
}
.menu li.theme_toggle {
margin-left: -0.3em;
}
}
/* the home icon is redundant when space is SUPER tight */
@media screen and (max-width: 380px) {
.right {
max-width: 225px;
margin-left: 1.6em;
}
.menu li:first-of-type {
display: none;
}
}

View File

@@ -1,77 +0,0 @@
import { memo } from "react";
import Link from "next/link";
import Image from "next/image";
import ThemeToggle from "./ThemeToggle";
import { HomeIcon, NotesIcon, ProjectsIcon, ContactIcon } from "../icons";
import meJpg from "../../public/static/images/me.jpg";
import styles from "./Header.module.css";
const links = [
{
icon: <HomeIcon className={`icon ${styles.icon}`} />,
text: "Home",
href: "/",
},
{
icon: <NotesIcon className={`icon ${styles.icon}`} />,
text: "Notes",
href: "/notes/",
},
{
icon: <ProjectsIcon className={`icon ${styles.icon}`} />,
text: "Projects",
href: "/projects/",
},
{
icon: <ContactIcon className={`icon ${styles.icon}`} />,
text: "Contact",
href: "/contact/",
},
];
const Header = () => (
<header className={styles.header}>
<nav className={styles.nav}>
<div className={styles.left}>
<Link href="/">
<a className={styles.name}>
<div className={styles.selfie}>
<Image
src={meJpg}
alt="Photo of Jake Jarvis"
width={70}
height={70}
quality={60}
layout="intrinsic"
priority
/>
</div>
<span>Jake Jarvis</span>
</a>
</Link>
</div>
<div className={styles.right}>
<ul className={styles.menu}>
{links.map((link, index) => (
<li key={index}>
<Link href={link.href} prefetch={false}>
<a className={styles.link}>
{link.icon} <span>{link.text}</span>
</a>
</Link>
</li>
))}
<li className={styles.theme_toggle}>
<ThemeToggle className={styles.icon} />
</li>
</ul>
</div>
</nav>
</header>
);
export default memo(Header);

View File

@@ -1,3 +0,0 @@
import Gist from "react-gist";
export default Gist;

View File

@@ -1,21 +0,0 @@
import Image from "next/image";
import type { ImageProps } from "next/image";
const CustomImage = ({ src, width, height, alt, priority }: ImageProps) => {
return (
<div className="image_wrapper">
<Image
src={src}
layout="intrinsic"
width={width}
height={height}
alt={alt || ""}
quality={65}
loading={priority ? "eager" : "lazy"}
priority={!!priority}
/>
</div>
);
};
export default CustomImage;

View File

@@ -1,17 +0,0 @@
import TweetEmbed from "react-tweet-embed";
type CustomTweetProps = {
id: string;
};
const Tweet = (props: CustomTweetProps) => (
<TweetEmbed
id={props.id}
options={{
dnt: true,
align: "center",
}}
/>
);
export default Tweet;

View File

@@ -1,24 +0,0 @@
import ReactPlayer from "react-player/lazy";
import type { ReactPlayerProps } from "react-player";
const Video = (props: ReactPlayerProps) => (
<div
style={{
position: "relative",
paddingTop: "56.25%",
}}
>
<ReactPlayer
width="100%"
height="100%"
style={{
position: "absolute",
top: 0,
left: 0,
}}
{...props}
/>
</div>
);
export default Video;

17
lib/mdx-components.ts Normal file
View File

@@ -0,0 +1,17 @@
import dynamic from "next/dynamic";
// Bundle these components by default:
export { default as Image } from "../components/Image/Image";
export { default as Figure } from "../components/Figure/Figure";
// `code` is intentionally lowercase here -- replaces `<code>` tag from remark
export { default as code } from "../components/CodeBlock/CodeBlock";
// All of these components are technically passed into all posts, but next/dynamic ensures they're loaded only
// when they're referenced in the individual mdx files.
export const Video = dynamic(() => import("../components/Video/Video"));
export const YouTube = dynamic(() => import("../components/YouTubeEmbed/YouTubeEmbed"));
export const Tweet = dynamic(() => import("../components/TweetEmbed/TweetEmbed"));
export const Gist = dynamic(() => import("../components/GistEmbed/GistEmbed"));
// one-offs for specific posts
export const OctocatLink = dynamic(() => import("../components/OctocatLink/OctocatLink"));

View File

@@ -2,7 +2,7 @@ import fs from "fs";
import path from "path";
import { renderToStaticMarkup } from "react-dom/server";
import matter from "gray-matter";
import { bundleMDX } from "mdx-bundler";
import { serialize } from "next-mdx-remote/serialize";
import { compiler } from "markdown-to-jsx";
import removeMarkdown from "remove-markdown";
import sanitizeHtml from "sanitize-html";
@@ -64,20 +64,12 @@ export const getNoteData = (slug: string): { frontMatter: NoteMetaType; content:
};
export const getNote = async (slug: string): Promise<NoteType> => {
// https://github.com/kentcdodds/mdx-bundler#nextjs-esbuild-enoent
process.env.ESBUILD_BINARY_PATH =
process.platform === "win32"
? path.join(process.cwd(), "node_modules", "esbuild", "esbuild.exe")
: path.join(process.cwd(), "node_modules", "esbuild", "bin", "esbuild");
const { frontMatter, content } = getNoteData(slug);
const { code: mdxSource } = await bundleMDX({
source: content,
cwd: path.join(process.cwd(), NOTES_DIR),
xdmOptions: (options) => {
options.remarkPlugins = [...(options.remarkPlugins ?? []), [remarkGfm, { singleTilde: false }]];
options.rehypePlugins = [
...(options.rehypePlugins ?? []),
const source = await serialize(content, {
parseFrontmatter: false,
mdxOptions: {
remarkPlugins: [[remarkGfm, { singleTilde: false }]],
rehypePlugins: [
[rehypeExternalLinks, { target: "_blank", rel: ["noopener", "noreferrer"] }],
[rehypeSlug, {}],
[
@@ -90,26 +82,13 @@ export const getNote = async (slug: string): Promise<NoteType> => {
},
],
[rehypePrism, { ignoreMissing: true }],
];
return options;
},
esbuildOptions: (options) => {
options.minify = true;
options.target = ["es2018"];
options.loader = {
...options.loader,
".js": "jsx",
".ts": "tsx",
};
return options;
],
},
});
return {
frontMatter,
mdxSource,
source,
};
};

View File

@@ -11,14 +11,10 @@ tags:
image: "/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
import Video from "../components/media/Video";
The team behind Bernie Sanders' 2020 campaign [released a new web app](https://www.nbcnews.com/politics/2020-election/bernie-sanders-2020-campaign-unveils-app-increase-its-voter-database-n999206) last month named [BERN](https://app.berniesanders.com/). The goal of BERN is simple: to gather as much information as they can on as many voters in the United States as they can, and make their grassroots army of enthusiastic supporters do the work. It's undoubtedly a smart strategy, but also a concerning one for myself and other privacy advocates.
<Image
src="/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg"
src="/public/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg"
width="865"
height="433"
alt="Sad Bernie"
@@ -51,34 +47,32 @@ Using either feature, a volunteer starts with a search of the database for the v
Here's one of the instructional videos provided internally to volunteers:
<Video
url={[
{ src: "/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.webm", type: "video/webm" },
{ src: "/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.mp4", type: "video/mp4" },
]}
config={{
file: {
attributes: {
poster: "/static/images/notes/bernie-sanders-bern-app-data/poster-friend-to-friend.png",
controlsList: "nodownload",
preload: "metadata",
autoPlay: false,
},
},
}}
controls={true}
webm="/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.webm"
mp4="/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.mp4"
thumbnail="/static/images/notes/bernie-sanders-bern-app-data/poster-friend-to-friend.png"
/>
...and a few privacy-related questions about the friend-to-friend feature were answered by campaign staff in a separate closed webinar for volunteers this week:
<Image src="/static/images/notes/bernie-sanders-bern-app-data/webinar-qa-1.png" width="400" height="155" alt="Q&A 1" />
<Image
src="/public/static/images/notes/bernie-sanders-bern-app-data/webinar-qa-1.png"
width="400"
height="155"
alt="Q&A 1"
/>
<Image src="/static/images/notes/bernie-sanders-bern-app-data/webinar-qa-2.png" width="400" height="184" alt="Q&A 2" />
<Image
src="/public/static/images/notes/bernie-sanders-bern-app-data/webinar-qa-2.png"
width="400"
height="184"
alt="Q&A 2"
/>
Defenders of the BERN app have pointed out that the information used is already available from public voter rolls maintained independently by each state. This is true. But these public records have never been tied to a campaign's internal voter files through a tool that's wide open to the entire internet, with incentives to add valuable data that benefits one candidate.
There were even unverified claims that [BERN was leaking voter ID numbers](https://info.idagent.com/blog/bern-app-exposes-150m-voter-records), which are the same as one's driver's license ID numbers in some states, through JSON responses in the first few days after its release. There don't be appear to be strict rate limits on calls to the API either, potentially inviting malicious actors from around the world — wink wink — to scrape personal data on tens of millions of Americans en masse.
<Figure src="/static/images/notes/bernie-sanders-bern-app-data/json-response.jpg" width="865" height="369">
<Figure src="/public/static/images/notes/bernie-sanders-bern-app-data/json-response.jpg" width="865" height="369">
BERN's API response in Chrome DevTools
</Figure>
@@ -86,14 +80,14 @@ Others have noted that web-based organizing tools like BERN have been used by ca
But the latter category of databases — like [NationBuilder](https://nationbuilder.com/) and, more notably, [NGP VAN's VoteBuilder](https://act.ngpvan.com/votebuilder) software based on the Obama campaign's inventions and now used by almost all Democratic campaigns across the United States — are secured and strictly guarded. Volunteer accounts need to be created and approved by paid campaign organizers and are locked down to provide the bare minimum amount of information necessary for one to canvass or phone bank a shortlist of voters. Every single click is also recorded in a [detailed log](sanders-campaign-audit.pdf) down to the millisecond. (This is how [Bernie's organizers got busted](https://time.com/4155185/bernie-sanders-hillary-clinton-data/) snooping around Hillary's VoteBuilder data last cycle, by the way.)
<Figure src="/static/images/notes/bernie-sanders-bern-app-data/votebuilder-audit.png" width="750" height="447">
<Figure src="/public/static/images/notes/bernie-sanders-bern-app-data/votebuilder-audit.png" width="750" height="447">
[NGP VAN's audit of the Sanders campaign's VoteBuilder
activity](/static/images/notes/bernie-sanders-bern-app-data/sanders-campaign-audit.pdf)
</Figure>
BERN is taking this to an unprecedented level. Allowing anybody on the internet to sign up and add others' personal information to the campaign's database without their knowledge is troubling, especially when you consider the gamified "points" system they've added as an incentive to report as much information on as many people as possible.
<Figure src="/static/images/notes/bernie-sanders-bern-app-data/reddit-bros.png" width="600" height="301">
<Figure src="/public/static/images/notes/bernie-sanders-bern-app-data/reddit-bros.png" width="600" height="301">
[BERN discussion on /r/SandersForPresident
thread](https://www.reddit.com/r/SandersForPresident/comments/bi15la/new_get_the_official_bernie_sanders_2020_app_bern/elxi85m/)
</Figure>
@@ -101,7 +95,7 @@ BERN is taking this to an unprecedented level. Allowing anybody on the internet
In addition to the points system, it was revealed in the webinar mentioned above that the campaign is planning on giving out shiny rewards based on how many friends one adds, setting expectations at 50+ contacts to reach the "Bernie Super Bundler" tier — whatever that means.
<Image
src="/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-1.png"
src="/public/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-1.png"
width="700"
height="451"
alt="Webinar Slide 1"
@@ -110,7 +104,7 @@ In addition to the points system, it was revealed in the webinar mentioned above
In the middle of the webinar, the organizer also paused the presentation for _fifteen minutes_ — complete with a countdown clock — and told volunteers to race to add as many of their friends as possible in that time. She announced afterwards that participants added 20 to 40 friends into the app on average, with some allegedly adding close to 100 in fifteen minutes.
<Image
src="/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-2.png"
src="/public/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-2.png"
width="700"
height="451"
alt="Webinar Slide 2"

View File

@@ -11,13 +11,10 @@ tags:
image: "/static/images/notes/cloudflare-dns-archive-is-blocked/archive-is.png"
---
import Image from "../components/media/Image";
import Tweet from "../components/media/Tweet";
**tl;dr:** No. Quite the opposite, actually — [Archive.is](https://archive.is/)'s owner is intentionally blocking 1.1.1.1 users.
<Image
src="/static/images/notes/cloudflare-dns-archive-is-blocked/archive-is.png"
src="/public/static/images/notes/cloudflare-dns-archive-is-blocked/archive-is.png"
width="865"
height="180"
alt="Archive.today screenshot"

View File

@@ -12,10 +12,8 @@ tags:
image: "/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png"
---
import Image from "../components/media/Image";
<Image
src="/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png"
src="/public/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png"
width="320"
height="284"
alt="Terminal.app on macOS"

View File

@@ -11,159 +11,129 @@ tags:
image: "/static/images/notes/coronavirus-open-source/covid19dashboards.png"
---
import Image from "../components/media/Image";
import Video from "../components/media/Video";
export const Octocat = (props) => {
return (
<a
className="no-underline"
href={`https://github.com/${props.repo}`}
target="_blank"
rel="noopener noreferrer"
style={{ margin: "0 0.3em", color: "var(--text)" }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="1em"
height="1em"
aria-hidden="true"
className="icon"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
/>
</svg>
</a>
);
};
We're all quickly learning that worldwide pandemics can bring out both [the best](https://www.vox.com/culture/2020/3/13/21179293/coronavirus-italy-covid19-music-balconies-sing) and [the worst](https://twitter.com/9NewsAUS/status/1236088663093608448) of humanity. But one thing has become readily apparent to me — outside of the large teams of medical professionals risking their lives right this minute, the open source community stands alone in its ability to rapidly organize in the midst of chaos to give back to the world and, in this case, make it safer for all of us.
These are just a few incredible open source projects that didn't exist a few months ago, but rapidly formed teams of dozens of contributors to fill both big needs and small niches in the fight to defeat the novel coronavirus, aka [**COVID-19**](https://www.cdc.gov/coronavirus/2019-nCoV/index.html).
## [The COVID Tracking Project](https://covidtracking.com/) <Octocat repo="COVID19Tracking/website" />
## [The COVID Tracking Project](https://covidtracking.com/) <OctocatLink repo="COVID19Tracking/website" />
Now that Americans are _finally_ starting to get tested for the coronavirus, information and statistics about the results are being released state-by-state, which has led to a scattering of primary sources across the web, each releasing [different figures in different forms](https://docs.google.com/document/d/1OyN6_1UeDePwPwKi6UKZB8GwNC7-kSf1-BO2af8kqVA/edit). The [COVID Tracking Project](https://covidtracking.com/) collects as much information as possible from each local health authority's website and puts everything together in [easy-to-digest tables](https://covidtracking.com/data/), as well as [spreadsheets](https://docs.google.com/spreadsheets/u/2/d/e/2PACX-1vRwAqp96T9sYYq2-i7Tj0pvTf6XVHjDSMIKBdZHXiCGGdNC0ypEU9NbngS8mxea55JuCFuua1MUeOj5/pubhtml) and a [public API](https://covidtracking.com/api/).
The maintainers are also [fully transparent](https://covidtracking.com/about-tracker/) about their process and take great care to annotate individual figures with the methodology used to arrive at each, which has earned them the [trust](https://covidtracking.com/#press) of even the largest national news organizations reporting on COVID-19.
<Image
src="/static/images/notes/coronavirus-open-source/covidtracking.png"
src="/public/static/images/notes/coronavirus-open-source/covidtracking.png"
width="680"
height="328"
alt="The COVID Tracking Project"
/>
## [#findthemasks](https://findthemasks.com/) <Octocat repo="r-pop/findthemasks" />
## [#findthemasks](https://findthemasks.com/) <OctocatLink repo="r-pop/findthemasks" />
This one might be my favorite, simply because of its laser-like focus on solving a very specific (yet catastrophic) problem. The United States is [already running out](https://www.nytimes.com/2020/03/19/health/coronavirus-masks-shortage.html) of [personal protective equipment (PPE)](https://www.fda.gov/medical-devices/general-hospital-devices-and-supplies/personal-protective-equipment-infection-control) for the healthcare professionals on the front lines of this crisis. [#findthemasks.com](https://findthemasks.com/) has gathered specific donation requests and points of contact from hospitals around the country in desperate need of basic supplies.
_Please_ look up your local hospitals on [#findthemasks](https://findthemasks.com/#sites) and follow their instructions to donate anything you have hoarded — it's likely the single most impactful thing you can do at this point. If you don't see your local hospital, or don't feel comfortable shipping equipment to any hospital listed, you can also visit [PPE Link](https://ppelink.org/ppe-donations/) and they will connect you with hospitals in your area.
<Image
src="/static/images/notes/coronavirus-open-source/findthemasks.png"
src="/public/static/images/notes/coronavirus-open-source/findthemasks.png"
width="600"
height="295"
alt="#findthemasks"
/>
## [#StayTheFuckHome](https://staythefuckhome.com/) <Octocat repo="flore2003/staythefuckhome" />
## [#StayTheFuckHome](https://staythefuckhome.com/) <OctocatLink repo="flore2003/staythefuckhome" />
I figured I'd throw in this cheeky website broadcasting a simple but serious message: **STAY THE FUCK HOME!!!** If you're _still_ not convinced of the importance of this "suggestion," give their ["Self-Quarantine Manifesto"](https://staythefuckhome.com/) a quick read. Now.
The [GitHub community](https://github.com/flore2003/staythefuckhome/pulls?q=is%3Apr) has translated the instructional essay into over a dozen different languages — including a [safe-for-work version](https://staythefuckhome.com/sfw/), if that helps — and they're [looking for more translators](https://github.com/flore2003/staythefuckhome#contributing) if you're multilingual and need something besides Netflix to fill your time with while you **_stay the fuck home!_** 😉
<Image
src="/static/images/notes/coronavirus-open-source/staythefuckhome.png"
src="/public/static/images/notes/coronavirus-open-source/staythefuckhome.png"
width="600"
height="215"
alt="#StayTheFuckHome"
/>
## [COVID-19 Dashboards](https://covid19dashboards.com/) <Octocat repo="github/covid19-dashboard" />
## [COVID-19 Dashboards](https://covid19dashboards.com/) <OctocatLink repo="github/covid19-dashboard" />
This collection of various visualizations is fascinating (and sobering) to look at. If you're smarter than I am and have experience in data analysis, their team (led by a [GitHub engineer](https://github.com/hamelsmu)) would be more than happy to [add your contribution](https://github.com/github/covid19-dashboard/blob/master/CONTRIBUTING.md) to the site — they're using [Jupyter Notebooks](https://jupyter.org/) and [fastpages](https://github.com/fastai/fastpages).
<Image
src="/static/images/notes/coronavirus-open-source/covid19dashboards.png"
src="/public/static/images/notes/coronavirus-open-source/covid19dashboards.png"
width="580"
height="442"
alt="COVID-19 Dashboards"
/>
## [CoronaTracker](https://coronatracker.samabox.com/) <Octocat repo="MhdHejazi/CoronaTracker" />
## [CoronaTracker](https://coronatracker.samabox.com/) <OctocatLink repo="MhdHejazi/CoronaTracker" />
CoronaTracker is a _beautiful_ cross-platform app for iOS and macOS with intuitive maps and charts fed by reputable live data. Apple is [being justifiably picky](https://developer.apple.com/news/?id=03142020a) about "non-official" Coronavirus apps in their App Store ([so is Google](https://blog.google/inside-google/company-announcements/coronavirus-covid19-response/), by the way) but you can still [download the macOS app directly](https://coronatracker.samabox.com/) or [compile the iOS source code](https://github.com/MhdHejazi/CoronaTracker#1-ios-app) yourself using Xcode if you wish.
<Image
src="/static/images/notes/coronavirus-open-source/coronatracker.png"
src="/public/static/images/notes/coronavirus-open-source/coronatracker.png"
width="865"
height="417"
alt="CoronaTracker"
/>
## [Staying Home Club](https://stayinghome.club/) <Octocat repo="phildini/stayinghomeclub" />
## [Staying Home Club](https://stayinghome.club/) <OctocatLink repo="phildini/stayinghomeclub" />
A bit more family-friendly than [#StayTheFuckHome](https://staythefuckhome.com/), the [Staying Home Club](https://stayinghome.club/) is maintaining a running list of over a thousand companies and universities mandating that employees and students work from home, as well as events that have been canceled or moved online. Quarantining yourself might feel lonely, but here's solid proof that you're far from alone right now.
<Image
src="/static/images/notes/coronavirus-open-source/stayinghome.png"
src="/public/static/images/notes/coronavirus-open-source/stayinghome.png"
width="600"
height="137"
alt="Staying Home Club"
/>
## [Nextstrain for nCoV](https://nextstrain.org/ncov) <Octocat repo="nextstrain/ncov" />
## [Nextstrain for nCoV](https://nextstrain.org/ncov) <OctocatLink repo="nextstrain/ncov" />
This one is a bit over my head, but apparently [Nextstrain](https://nextstrain.org/) is a pretty impressive open-source service targeted at genome data analysis and visualization of different pathogens. Their [COVID-19 page](https://nextstrain.org/ncov) is still awe-inspiring to look at for a layman like me, but probably a thousand times more so if you're an actual scientist — in which case, the [genome data they've open-sourced](https://github.com/nextstrain/ncov) might be of interest to you.
<Image
src="/static/images/notes/coronavirus-open-source/nextstrain.png"
src="/public/static/images/notes/coronavirus-open-source/nextstrain.png"
width="865"
height="345"
alt="Nextstrain for nCOV"
/>
## [Johns Hopkins 2019-nCoV Data](https://systems.jhu.edu/research/public-health/ncov/) <Octocat repo="CSSEGISandData/COVID-19" />
## [Johns Hopkins 2019-nCoV Data](https://systems.jhu.edu/research/public-health/ncov/) <OctocatLink repo="CSSEGISandData/COVID-19" />
Johns Hopkins University's [visual COVID-19 global dashboard](https://www.arcgis.com/apps/opsdashboard/index.html#/bda7594740fd40299423467b48e9ecf6) has been bookmarked as my go-to source of information since the beginning of this crisis earlier this year. Now, JHU's [Center for Systems Science and Engineering](https://systems.jhu.edu/) has open-sourced [their data and analysis](https://github.com/CSSEGISandData/COVID-19) for anybody to use.
<Image
src="/static/images/notes/coronavirus-open-source/hopkins.png"
src="/public/static/images/notes/coronavirus-open-source/hopkins.png"
width="865"
height="426"
alt="Johns Hopkins 2019-nCoV Data"
/>
## [COVID-19 Scenarios](https://neherlab.org/covid19/) <Octocat repo="neherlab/covid19_scenarios" />
## [COVID-19 Scenarios](https://neherlab.org/covid19/) <OctocatLink repo="neherlab/covid19_scenarios" />
COVID-19 Scenarios will probably hit everyone in a different way, depending on your levels of optimism and/or pessimism right now. It uses [advanced scientific models](https://neherlab.org/covid19/about) to predict the future of the virus based on past data and future variables and assumptions you can tinker with yourself.
The maintainers at the [Neher Lab in Basel, Switzerland](https://neherlab.org/) even have a [discussion thread](https://github.com/neherlab/covid19_scenarios/issues/18) and an [open chatroom](https://spectrum.chat/covid19-scenarios/general/questions-discussions~8d49f461-a890-4beb-84f7-2d6ed0ae503a) set up for both scientists and non-scientists to ask questions and post ideas, which I find really nice of them!
<Image
src="/static/images/notes/coronavirus-open-source/scenarios.png"
src="/public/static/images/notes/coronavirus-open-source/scenarios.png"
width="740"
height="433"
alt="COVID-19 Scenarios"
/>
## [Corona Data Scraper](https://coronadatascraper.com/#home) <Octocat repo="lazd/coronadatascraper" />
## [Corona Data Scraper](https://coronadatascraper.com/#home) <OctocatLink repo="lazd/coronadatascraper" />
Similar to the [COVID Tracking Project](https://covidtracking.com/) above, the [Corona Data Scraper](https://coronadatascraper.com/#home) has set up an automated process to scrape verified data from across the web to form massive CSV spreadsheets and JSON objects. They even [rate the quality](https://github.com/lazd/coronadatascraper#source-rating) of each source to prioritize data accordingly.
<Image
src="/static/images/notes/coronavirus-open-source/coronadatascraper.png"
src="/public/static/images/notes/coronavirus-open-source/coronadatascraper.png"
width="750"
height="358"
alt="Corona Data Scraper"
/>
## [Folding@home](https://foldingathome.org/covid19/) <Octocat repo="FoldingAtHome/coronavirus" />
## [Folding@home](https://foldingathome.org/covid19/) <OctocatLink repo="FoldingAtHome/coronavirus" />
[Folding@home](https://foldingathome.org/) has been around [_forever_](https://en.wikipedia.org/wiki/Folding@home). I remember installing it on my family's home computer as a curious kid and making my father infuriated over how slow it got. But they [switched gears this month](https://foldingathome.org/2020/03/15/coronavirus-what-were-doing-and-how-you-can-help-in-simple-terms/) from using our computers to crunch various proteins and molecules in the background, and all of their power is now going towards discovering unknown "folds" in the coronavirus, which might be able to lead scientists to find better cures and potential vaccines.
@@ -172,30 +142,18 @@ You can [download their software here](https://foldingathome.org/start-folding/)
**Fun fact:** The team behind Folding@home has seen a [**huge** spike in computational power](https://www.reddit.com/r/pcmasterrace/comments/flgm7q/ama_with_the_team_behind_foldinghome_coronavirus/) this month after cryptominers started mining coronavirus proteins instead of boring, old Ethereum with their insanely overpowered GPUs! 👏
<Video
url={[
{ src: "/static/images/notes/coronavirus-open-source/folding.webm", type: "video/webm" },
{ src: "/static/images/notes/coronavirus-open-source/folding.mp4", type: "video/mp4" },
]}
config={{
file: {
attributes: {
poster: "/static/images/notes/coronavirus-open-source/folding-thumb.png",
autoPlay: true,
loop: true,
volume: 0,
muted: true,
},
},
}}
controls={false}
webm="/static/images/notes/coronavirus-open-source/folding.webm"
mp4="/static/images/notes/coronavirus-open-source/folding.mp4"
thumbnail="/static/images/notes/coronavirus-open-source/folding-thumb.png"
autoplay
/>
## [Coronavirus Tracker API](https://coronavirus-tracker-api.herokuapp.com/v2/locations) <Octocat repo="ExpDev07/coronavirus-tracker-api" />
## [Coronavirus Tracker API](https://coronavirus-tracker-api.herokuapp.com/v2/locations) <OctocatLink repo="ExpDev07/coronavirus-tracker-api" />
To wrap this list up, I thought I'd include [yet another API](https://github.com/ExpDev07/coronavirus-tracker-api) fed by multiple data sources that you can use to create your own open-source project if any of these inspired you. This one is incredibly flexible in terms of [query parameters and endpoints](https://github.com/ExpDev07/coronavirus-tracker-api#api-endpoints) but they all return simple JSON responses like we all know and love.
<Image
src="/static/images/notes/coronavirus-open-source/tracker-api.png"
src="/public/static/images/notes/coronavirus-open-source/tracker-api.png"
width="712"
height="371"
alt="Coronavirus Tracker API"

View File

@@ -11,14 +11,9 @@ tags:
image: "/static/images/notes/dropping-dropbox/email.png"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
import Tweet from "../components/media/Tweet";
import Video from "../components/media/Video";
I've been a loyal Dropbox user since its inception as a [Y Combinator startup](https://www.ycombinator.com/apply/dropbox/) ten years ago. Having a folder on all of my devices that instantly synchronized with each other was a game-changer for me, and I grew dependent on it more and more as they gave out free storage like candy — 48 GB for having a Samsung Chromebook, 1 GB for "Posting \<3 to Twitter," and so on — until I needed to upgrade to Dropbox Pro. But this month I canceled my Pro subscription after a few too many strikes.
<Figure src="/static/images/notes/dropping-dropbox/email.png" width="504" height="223" priority>
<Figure src="/public/static/images/notes/dropping-dropbox/email.png" width="504" height="223" priority>
Deleting 401,907 files from Dropbox... 😬
</Figure>
@@ -36,21 +31,9 @@ Decisions made by the top folks at Dropbox gave me an increasingly sour taste in
- ...and as a bonus, making the process of canceling Dropbox Pro incredibly convoluted, annoying, and sketchy. Here's a video demonstration via [Justin Dunham](https://twitter.com/jwyattd):
<Video
url={[
{ src: "/static/images/notes/dropping-dropbox/cancel.webm", type: "video/webm" },
{ src: "/static/images/notes/dropping-dropbox/cancel.mp4", type: "video/mp4" },
]}
config={{
file: {
attributes: {
poster: "/static/images/notes/dropping-dropbox/cancel.png",
controlsList: "nodownload",
preload: "metadata",
autoPlay: false,
},
},
}}
controls={true}
webm="/static/images/notes/dropping-dropbox/cancel.webm"
mp4="/static/images/notes/dropping-dropbox/cancel.mp4"
thumbnail="/static/images/notes/dropping-dropbox/cancel.png"
/>
## Seeking an alternative...
@@ -58,7 +41,7 @@ Decisions made by the top folks at Dropbox gave me an increasingly sour taste in
The infamous [Apple Ecosystem™](https://medium.com/swlh/the-irresistible-lure-of-the-apple-ecosystem-81bf8d66294a) has held me firmly in its grasp for over a decade now, and the main requirement of a replacement cloud storage service for me was smooth interoperability between my MacBook, iPhone, and iPad.
<Image
src="/static/images/notes/dropping-dropbox/icloud-storage.png"
src="/public/static/images/notes/dropping-dropbox/icloud-storage.png"
width="865"
height="137"
alt="iCloud Drive storage"
@@ -66,7 +49,12 @@ The infamous [Apple Ecosystem™](https://medium.com/swlh/the-irresistible-lure-
I've never been a proponent of leaving all your eggs in one basket. But it's hard to ignore the convenience of Apple's streamlined (and [finally](https://www.imore.com/developers-encounter-major-icloud-issues-ios-13-beta) reliable) [**iCloud Drive**](https://www.apple.com/icloud/), which is already installed on all of my devices (and actually cheaper than Dropbox gigabyte-for-gigabyte, at \$9.99/month for 2 TB). In fact, it's nearly invisible on macOS: I can simply save files in my Documents or Desktop folders as I always have and they're uploaded in the background. Git repositories now sync just fine and my files reappeared without a hitch after I recently formatted my Mac.
<Image src="/static/images/notes/dropping-dropbox/icloud-drive.png" width="680" height="423" alt="iCloud Drive" />
<Image
src="/public/static/images/notes/dropping-dropbox/icloud-drive.png"
width="680"
height="423"
alt="iCloud Drive"
/>
I still use (and highly recommend) [**Backblaze**](https://www.backblaze.com/) ([referral link](https://secure.backblaze.com/r/00x84e)) to backup my home folder and add a second layer of redundancy to storing all of my most important files on ["someone else's computer."](https://www.zdnet.com/article/stop-saying-the-cloud-is-just-someone-elses-computer-because-its-not/) And as long as I remember to plug in my external SSD every so often, they're also backed up locally via [Time Machine](https://support.apple.com/en-us/HT201250).

View File

@@ -11,14 +11,12 @@ tags:
image: "/static/images/notes/finding-candidates-subdomain-takeovers/hackerone-2.png"
---
import Figure from "../components/media/Figure";
A **subdomain takeover** occurs when a subdomain (like _example_.jarv.is) points to a shared hosting account that is abandoned by its owner, leaving the endpoint available to claim for yourself.
Not only are takeovers a fun way to dip your toes into [penetration testing](https://www.cloudflare.com/learning/security/glossary/what-is-penetration-testing/), but they can also be incredibly lucrative thanks to [bug bounty programs](https://en.wikipedia.org/wiki/Bug_bounty_program) on services like [HackerOne](https://hackerone.com/hacktivity?order_direction=DESC&order_field=popular&filter=type%3Aall&querystring=subdomain%20takeover) and [Bugcrowd](https://bugcrowd.com/programs), where corporations pay pentesters for their discoveries.
<Figure
src="/static/images/notes/finding-candidates-subdomain-takeovers/hackerone-2.png"
src="/public/static/images/notes/finding-candidates-subdomain-takeovers/hackerone-2.png"
width="620"
height="347"
priority

View File

@@ -11,12 +11,8 @@ tags:
image: "/static/images/notes/github-actions/actions-flow.png"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
import Gist from "../components/media/Gist";
<Image
src="/static/images/notes/github-actions/actions-flow.png"
src="/public/static/images/notes/github-actions/actions-flow.png"
width="780"
height="322"
alt="Example workflow for a GitHub Action"
@@ -27,7 +23,7 @@ Since being accepted into the beta for [GitHub Actions](https://github.com/featu
My favorite so far is my [Lighthouse Audit action](https://github.com/jakejarvis/lighthouse-action), which spins up a headless Google Chrome instance in an Ubuntu container and runs [Google's Lighthouse tool](https://developers.google.com/web/tools/lighthouse), which scores webpages on performance, accessibility, SEO, etc. and provides actual suggestions to improve them. It's a perfect example of the power of combining containers with Git workflows.
<Figure src="/static/images/notes/github-actions/lighthouse-output.png" width="750" height="297">
<Figure src="/public/static/images/notes/github-actions/lighthouse-output.png" width="750" height="297">
The results of a Lighthouse audit on this website, after running tests in a headless Google Chrome.
</Figure>
@@ -62,7 +58,7 @@ Using an action is also surprisingly simple, and more intuitive than [Travis CI]
For a more complex example, when I forked [Hugo](https://github.com/gohugoio/hugo) (the static site generator used to build this website) to make some small personalized changes, I also translated [their `.travis.yml` file](https://github.com/gohugoio/hugo/blob/master/.travis.yml) into a [`workflow.yml` file](https://github.com/jakejarvis/hugo-custom/blob/master/.github/workflows/workflow.yml) for practice, which simultaneously runs comprehensive unit tests on **three operating systems** (Ubuntu 18.04, Windows 10, and macOS 10.14) with the latest two Go versions _each!_ If the tests are all successful, it builds a Docker image and pushes it to both [Docker Hub](https://hub.docker.com/r/jakejarvis/hugo-custom) and the [GitHub Package Registry](https://github.com/jakejarvis/hugo-custom/packages) (also [in beta](https://github.com/features/package-registry)).
<Image
src="/static/images/notes/github-actions/hugo-logs.png"
src="/public/static/images/notes/github-actions/hugo-logs.png"
width="865"
height="418"
alt="Build logs for my Hugo fork"

View File

@@ -12,11 +12,8 @@ tags:
image: "/static/images/notes/github-rename-master/github-default.png"
---
import Image from "../components/media/Image";
import Tweet from "../components/media/Tweet";
<Image
src="/static/images/notes/github-rename-master/blm-topic.png"
src="/public/static/images/notes/github-rename-master/blm-topic.png"
width="865"
height="162"
alt="Black lives matter."
@@ -63,7 +60,7 @@ You can verify this worked by running `git branch -r`. You should see something
Setting the default branch remotely is the only step that can't be done on the command line (although you can technically [use the GitHub API](https://github.com/erbridge/github-branch-renamer)). Head to **Settings → Branches** on GitHub to [change the default branch](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-base-branch-of-a-pull-request).
<Image
src="/static/images/notes/github-rename-master/github-default.png"
src="/public/static/images/notes/github-rename-master/github-default.png"
width="810"
height="405"
alt="Changing the default branch of a GitHub repository"
@@ -86,7 +83,7 @@ Do a quick search of your codebase for `master` to manually replace any dead ref
Pay attention to CI files — `.travis.yml`, `.github/workflows/`, `.circleci/config.yml`, etc. — and make sure there aren't any external services relying on `master` being there. For example, I almost forgot to change the branch [Netlify triggers auto-deploys](https://docs.netlify.com/site-deploys/overview/#branches-and-deploys) from to build this site:
<Image
src="/static/images/notes/github-rename-master/netlify-deploy.png"
src="/public/static/images/notes/github-rename-master/netlify-deploy.png"
width="720"
height="460"
alt="Netlify auto-deployment branch setting"

View File

@@ -12,10 +12,7 @@ tags:
image: "/static/images/notes/how-to-backup-linux-server/apocalypse.png"
---
import Figure from "../components/media/Figure";
import Tweet from "../components/media/Tweet";
<Figure src="/static/images/notes/how-to-backup-linux-server/apocalypse.png" width="865" height="303" priority>
<Figure src="/public/static/images/notes/how-to-backup-linux-server/apocalypse.png" width="865" height="303" priority>
**The Cloud-pocalypse:** Coming soon(er than you think) to a server near you.
</Figure>

View File

@@ -12,8 +12,6 @@ tags:
image: "/static/images/notes/how-to-pull-request-fork-github/step7-2.png"
---
import Image from "../components/media/Image";
<svg width="150" height="150" viewBox="0 0 40 40" style={{ float: "right", marginBottom: "6px", marginLeft: "12px" }}>
<path d="M6.5 35v-4.8c0-5.4 4.3-9.7 9.7-9.7h7.6c5.4 0 9.7-4.3 9.7-9.7V6M6.5 32.5v-26" fill="none" stroke="#a3b7cc" />
<path d="M6.5 10.5a4 4 0 110-8 4 4 0 010 8z" fill="#8bb7f0" />
@@ -36,7 +34,12 @@ Starting from the very beginning, we'll fork an existing repository to our accou
Assuming you're using GitHub, this step is easy. Just find the repository you're contributing to and press the Fork button in the upper left. This will create an exact copy of the repository (and all of its branches) under your own username.
<Image src="/static/images/notes/how-to-pull-request-fork-github/step1.png" width="865" height="80" alt="Step 1" />
<Image
src="/public/static/images/notes/how-to-pull-request-fork-github/step1.png"
width="865"
height="80"
alt="Step 1"
/>
## 2. Clone your new fork locally
@@ -46,7 +49,12 @@ GitHub will automatically redirect you to the forked repository under your usern
git clone git@github.com:jakejarvis/react-native.git
```
<Image src="/static/images/notes/how-to-pull-request-fork-github/step2.png" width="420" height="208" alt="Step 2" />
<Image
src="/public/static/images/notes/how-to-pull-request-fork-github/step2.png"
width="420"
height="208"
alt="Step 2"
/>
## 3. Track the original repository as a remote of the fork
@@ -94,9 +102,19 @@ git push -u origin fix-readme-typo
You're now all ready to submit the improvement you've made to the project's maintainers for approval. Head over to the original repositories Pull Requests tab, and you should see an automatic suggestion from GitHub to create a pull request from your new branch.
<Image src="/static/images/notes/how-to-pull-request-fork-github/step7-1.png" width="865" height="75" alt="Step 7.1" />
<Image
src="/public/static/images/notes/how-to-pull-request-fork-github/step7-1.png"
width="865"
height="75"
alt="Step 7.1"
/>
<Image src="/static/images/notes/how-to-pull-request-fork-github/step7-2.png" width="700" height="354" alt="Step 7.2" />
<Image
src="/public/static/images/notes/how-to-pull-request-fork-github/step7-2.png"
width="700"
height="354"
alt="Step 7.2"
/>
---

View File

@@ -11,11 +11,8 @@ tags:
image: "/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-2-04-04-pm.png"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
<Figure
src="/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-2-04-04-pm.png"
src="/public/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-2-04-04-pm.png"
width="620"
height="456"
priority
@@ -82,7 +79,7 @@ VMware on macOS makes this a little tricky, since it packages VMs in what looks
We need to right click on the .vmwarevm "file," and select **Show Package Contents** to see what's really in there. You should see the actual .VMDK file sitting there — normally we're looking for the plain VMDK file (named _Virtual Disk.vmdk_ by default) without a bunch of numbers after it, but if you have snapshots associated with your VM, this might not be the file we actually want. But run the command below with it anyways, and the output will tell you if you need to use a different file.
<Image
src="/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-1-58-42-pm.png"
src="/public/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-1-58-42-pm.png"
width="680"
height="572"
alt="Finding .vmwarevm in Finder"

View File

@@ -11,11 +11,8 @@ image: "/static/images/notes/millenial-with-hillary-clinton/24707394571_0818d4ab
noComments: true
---
import Figure from "../components/media/Figure";
import Video from "../components/media/Video";
<Figure
src="/static/images/notes/millenial-with-hillary-clinton/24707394571_0818d4ab83_o-1-copy.jpg"
src="/public/static/images/notes/millenial-with-hillary-clinton/24707394571_0818d4ab83_o-1-copy.jpg"
width="865"
height="411"
priority
@@ -36,7 +33,7 @@ My goal here isn't to convince every Bernie believer to jump ship and support he
After working for months as a fellow on Hillary's campaign in New Hampshire leading up to the first primary in the country, I could feed you all the standard campaign talking points in my sleep: After graduating from Yale Law she went to work at the [Children's Defense Fund](https://www.childrensdefense.org/), not a high-paying New York law firm. She [went undercover](https://www.nytimes.com/2015/12/28/us/politics/how-hillary-clinton-went-undercover-to-examine-race-in-education.html?_r=0) in Alabama to investigate discrimination in public schools. She [got juveniles out of adult prisons](https://www.huffingtonpost.com/entry/huffpost-criminal-justice-survey-democratics_us_56bb85eae4b0b40245c5038b). She [gave 8 million children healthcare](https://www.hillaryclinton.com/briefing/factsheets/2015/12/23/hillary-clintons-lifelong-fight-for-quality-affordable-health-care-for-all-americans/). But there's just one thing that, for some reason, is hard for people to believe: at her core she is a good, caring, and loving person who has had only selfless intentions her entire life. I promise you.
<Figure
src="/static/images/notes/millenial-with-hillary-clinton/9e58a-1bvweqv_ve2_c1tw5-ihrhw.jpg"
src="/public/static/images/notes/millenial-with-hillary-clinton/9e58a-1bvweqv_ve2_c1tw5-ihrhw.jpg"
width="400"
height="500"
>
@@ -55,39 +52,15 @@ I'm aware of the street cred young Democrats collect by claiming they hated Hill
As [Bill Maher](https://medium.com/u/cdc04a9799f6) (an avid Bernie supporter) [said this weekend](https://www.youtube.com/watch?v=rd1gpjkjcfc), some in our party need to "learn the difference between an imperfect friend and a deadly enemy." I don't agree with everything Hillary has said or done. I don't unconditionally defend every single chapter in her public record over the past 30 years (and [neither does she](https://www.washingtonpost.com/blogs/post-partisan/wp/2016/02/25/hillary-clinton-responds-to-activist-who-demanded-apology-for-superpredator-remarks/), by the way). I don't think that's possible for any voter to find in a politician. But if you identify as a Democrat, she is the farthest thing from your enemy. Plain and simple. Like you and Bernie, she wants to prevent a Republican from winning in November and reversing so much of the progress we've made over the past seven years on their first day in office. That is our number one goal right now. And whether it gets accomplished by a President Clinton or a President Sanders, I am 100% on board either way. Let's stop fighting each other and start fighting together.
<Video
url="https://www.youtube-nocookie.com/watch?v=TqrwDMTByNM"
light="https://i.ytimg.com/vi/TqrwDMTByNM/hqdefault.jpg"
controls
/>
<YouTube id="TqrwDMTByNM" />
---
**Update:** The campaign has included my snowy New Hampshire interaction with her in one of the DNC convention videos! See a few short seconds of my joy at 1:24.
<Video
url={[
{ src: "/static/images/hillary/convention-720p.webm", type: "video/webm" },
{ src: "/static/images/hillary/convention-720p.mp4", type: "video/mp4" },
]}
config={{
file: {
attributes: {
poster: "/static/images/hillary/thumb.png",
controlsList: "nodownload",
preload: "metadata",
autoPlay: false,
},
tracks: [
{
kind: "subtitles",
src: "/static/images/hillary/subs.en.vtt",
srcLang: "en",
label: "English",
default: true,
},
],
},
}}
controls={true}
webm="/static/images/hillary/convention-720p.webm"
mp4="/static/images/hillary/convention-720p.mp4"
thumbnail="/static/images/hillary/thumb.png"
subs="/static/images/hillary/subs.en.vtt"
/>

View File

@@ -11,18 +11,20 @@ tags:
image: "/static/images/notes/my-first-code/jbb-screen1.png"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
<Image
src="/static/images/notes/my-first-code/netscape.png"
src="/public/static/images/notes/my-first-code/netscape.png"
width="865"
height="155"
alt="Awesome First Code on GitHub"
priority
/>
<Image src="/static/images/notes/my-first-code/badges.png" width="537" height="36" alt="Code Quality: A for effort" />
<Image
src="/public/static/images/notes/my-first-code/badges.png"
width="537"
height="36"
alt="Code Quality: A for effort"
/>
I recently published my terrible, horrible, no good, very bad [first HTML site](https://jakejarvis.github.io/my-first-website/) and [first PHP project](https://github.com/jakejarvis/jbb#readme) ever and developed a new addiction to Web 1.0 nostalgia, fed by others who were brave enough to do the same.
@@ -32,7 +34,7 @@ Hopefully we can all look back at our first projects and be proud of how far we'
---
<Figure src="/static/images/notes/my-first-code/jbb-logo.png" width="640" height="80">
<Figure src="/public/static/images/notes/my-first-code/jbb-logo.png" width="640" height="80">
[Jake's Bulletin Board](https://github.com/jakejarvis/jbb)
</Figure>
@@ -163,16 +165,16 @@ while ($topic = mysql_fetch_object($result30)) {
The installation "wizard" (that's the joke, I presume...) ([sql_submit.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/setup/sql_submit.php))
<Figure src="/static/images/notes/my-first-code/jbb-screen1.png" width="865" height="458">
<Figure src="/public/static/images/notes/my-first-code/jbb-screen1.png" width="865" height="458">
JBB Installation Wizard
</Figure>
And finally, JBB's actual interface... or literally as much of it as I could get to function in 2019. ([index.php](https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/index.php))
<Figure src="/static/images/notes/my-first-code/jbb-screen3.png" width="865" height="561">
<Figure src="/public/static/images/notes/my-first-code/jbb-screen3.png" width="865" height="561">
JBB Homepage
</Figure>
<Figure src="/static/images/notes/my-first-code/jbb-screen4.png" width="865" height="493">
<Figure src="/public/static/images/notes/my-first-code/jbb-screen4.png" width="865" height="493">
JBB Post
</Figure>

View File

@@ -12,12 +12,10 @@ tags:
image: "/static/images/notes/netlify-analytics-review/overview.png"
---
import Image from "../components/media/Image";
I've been trying out [Netlify Analytics](https://www.netlify.com/products/analytics/) on this site for over a month now and have some quick thoughts about this unique offering in a world full of bloated and invasive tracking scripts.
<Image
src="/static/images/notes/netlify-analytics-review/pageviews-2.png"
src="/public/static/images/notes/netlify-analytics-review/pageviews-2.png"
width="865"
height="361"
alt="Pageview charts on Netlify Analytics"
@@ -55,7 +53,7 @@ Ad blocking is becoming commonplace on the World Wide Web with [over 25% of user
That's a _huge_ chunk of visitors missing that Netlify Analytics gains back for you — and probably far more if your audience is tech-savvy like those reading this post likely are. (Some might even [block JavaScript completely](https://www.gnu.org/philosophy/javascript-trap.en.html) using extensions like [NoScript](https://addons.mozilla.org/en-US/firefox/addon/noscript/).)
<Image
src="/static/images/notes/netlify-analytics-review/pages.png"
src="/public/static/images/notes/netlify-analytics-review/pages.png"
width="865"
height="390"
alt="Pageview and 404 tracking on Netlify Analytics"
@@ -76,7 +74,7 @@ It makes sense that Netlify needs to subsidize the cost of providing free enterp
### 📈&nbsp; Accuracy
<Image
src="/static/images/notes/netlify-analytics-review/sources-bandwidth.png"
src="/public/static/images/notes/netlify-analytics-review/sources-bandwidth.png"
width="865"
height="422"
alt="Referrer and bandwidth tracking on Netlify Analytics"
@@ -93,7 +91,7 @@ One more note: since Netlify doesn't process IP addresses or user agents, bots c
### ⏱️&nbsp; Historical Data
<Image
src="/static/images/notes/netlify-analytics-review/overview.png"
src="/public/static/images/notes/netlify-analytics-review/overview.png"
width="865"
height="355"
alt="Overview of Netlify Analytics stats"

View File

@@ -10,12 +10,8 @@ tags:
image: "/static/images/notes/presidential-candidates-404-pages/obama-laughing.jpg"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
import Video from "../components/media/Video";
<Figure
src="/static/images/notes/presidential-candidates-404-pages/obama-laughing.jpg"
src="/public/static/images/notes/presidential-candidates-404-pages/obama-laughing.jpg"
width="865"
height="460"
priority
@@ -32,7 +28,7 @@ More recently, though, little-known hidden Easter eggs on ["404 Not Found"](http
I'm a _huge_ sucker for Kate McKinnon's spot-on impression of Warren on Saturday Night Live. And [unfortunately](https://twitter.com/realdonaldtrump/status/1097116612279316480), seeing a campaign embrace SNL is like a breath of fresh air these days. [Watch all of the Kate McWarren videos so far here; you won't regret it.](https://www.nbc.com/saturday-night-live/cast/kate-mckinnon-15056/impersonation/elizabeth-warren-287903)
<Image
src="/static/images/notes/presidential-candidates-404-pages/warren.png"
src="/public/static/images/notes/presidential-candidates-404-pages/warren.png"
width="865"
height="579"
alt="Elizabeth Warren"
@@ -43,21 +39,9 @@ I'm a _huge_ sucker for Kate McKinnon's spot-on impression of Warren on Saturday
Although the designer who selected this GIF likely had _thousands_ of choices when searching "[Bernie finger wagging GIF](https://www.google.com/search?q=Bernie+finger+wagging+GIF&tbm=isch&tbs=itp:animated)," the text beside it is well-written and funny — even though we both know putting a page at [berniesanders.com/zxcliaosid](https://berniesanders.com/zxcliaosid/) probably won't be a top priority of a President Sanders.
<Video
url={[
{ src: "/static/images/notes/presidential-candidates-404-pages/sanders.webm", type: "video/webm" },
{ src: "/static/images/notes/presidential-candidates-404-pages/sanders.mp4", type: "video/mp4" },
]}
config={{
file: {
attributes: {
autoPlay: true,
loop: true,
volume: 0,
muted: true,
},
},
}}
controls={false}
webm="/static/images/notes/presidential-candidates-404-pages/sanders.webm"
mp4="/static/images/notes/presidential-candidates-404-pages/sanders.mp4"
autoplay
/>
## 3. Joe Biden — [joebiden.com](https://joebiden.com/asdfasdf404)
@@ -65,7 +49,7 @@ Although the designer who selected this GIF likely had _thousands_ of choices wh
Uncle Joe has a nice and simple 404 page. I like it, along with the Ray-Bans and his choice of vanilla ice cream.
<Image
src="/static/images/notes/presidential-candidates-404-pages/biden.png"
src="/public/static/images/notes/presidential-candidates-404-pages/biden.png"
width="865"
height="539"
alt="Joe Biden"
@@ -76,21 +60,9 @@ Uncle Joe has a nice and simple 404 page. I like it, along with the Ray-Bans and
A ballsy move, considering Beto's infamous [DUI arrest](https://www.politifact.com/texas/statements/2019/mar/14/club-growth/beto-orourke-arrested-dwi-flee-scene/) in the '90s — but still a clever ask for a donation and a great use of a GIF, even if it's left over from his Senate campaign.
<Video
url={[
{ src: "/static/images/notes/presidential-candidates-404-pages/orourke.webm", type: "video/webm" },
{ src: "/static/images/notes/presidential-candidates-404-pages/orourke.mp4", type: "video/mp4" },
]}
config={{
file: {
attributes: {
autoPlay: true,
loop: true,
volume: 0,
muted: true,
},
},
}}
controls={false}
webm="/static/images/notes/presidential-candidates-404-pages/orourke.webm"
mp4="/static/images/notes/presidential-candidates-404-pages/orourke.mp4"
autoplay
/>
## 5. Kamala Harris — [kamalaharris.org](https://kamalaharris.org/asdfasdf404)
@@ -98,21 +70,9 @@ A ballsy move, considering Beto's infamous [DUI arrest](https://www.politifact.c
Another clean and simple page with a top-notch GIF. It injected some emotion into visiting [kamalaharris.com/alskdjf](https://kamalaharris.com/alskdjf).
<Video
url={[
{ src: "/static/images/notes/presidential-candidates-404-pages/harris.webm", type: "video/webm" },
{ src: "/static/images/notes/presidential-candidates-404-pages/harris.mp4", type: "video/mp4" },
]}
config={{
file: {
attributes: {
autoPlay: true,
loop: true,
volume: 0,
muted: true,
},
},
}}
controls={false}
webm="/static/images/notes/presidential-candidates-404-pages/harris.webm"
mp4="/static/images/notes/presidential-candidates-404-pages/harris.mp4"
autoplay
/>
## 6. Pete Buttigeg — [peteforamerica.com](https://peteforamerica.com/asdfasdf404/)
@@ -120,7 +80,7 @@ Another clean and simple page with a top-notch GIF. It injected some emotion int
I love, love, _love_ Pete's design for his whole campaign, and his beautiful 404 page is no exception. In case you didn't know, Pete for America has an entire ["Design Toolkit"](https://design.peteforamerica.com/) publicly available for all to view and use, with really cool and in-depth explanations for all of their choices — even their [color palette](https://design.peteforamerica.com/colors). Very progressive indeed.
<Image
src="/static/images/notes/presidential-candidates-404-pages/buttigeg.png"
src="/public/static/images/notes/presidential-candidates-404-pages/buttigeg.png"
width="865"
height="344"
alt="Pete Buttigeg"
@@ -131,7 +91,7 @@ I love, love, _love_ Pete's design for his whole campaign, and his beautiful 404
Love the photo choice. But although pains me to go against my Senator from my home state, I still _cannot stand_ his choice of font. Oh well, I guess that's now a criterion for running for president in 2020.
<Image
src="/static/images/notes/presidential-candidates-404-pages/booker.png"
src="/public/static/images/notes/presidential-candidates-404-pages/booker.png"
width="865"
height="503"
alt="Cory Booker"
@@ -142,7 +102,7 @@ Love the photo choice. But although pains me to go against my Senator from my ho
Not sure if donating to Yang 2020 will help put a page at [yang2020.com/alsdjfzoif](https://www.yang2020.com/alsdjfzoif) — the actual URL I visited to grab this screenshot — but the Bitmoji Andrew looks pretty chill.
<Image
src="/static/images/notes/presidential-candidates-404-pages/yang.png"
src="/public/static/images/notes/presidential-candidates-404-pages/yang.png"
width="865"
height="470"
alt="Andrew Yang"
@@ -153,7 +113,7 @@ Not sure if donating to Yang 2020 will help put a page at [yang2020.com/alsdjfzo
This is the 404 page of someone who won't forget the [Midwestern roots](https://en.wikipedia.org/wiki/Uff_da) she comes from once she moves into the White House...or writes a memoir about her campaign from her Minnesota home.
<Image
src="/static/images/notes/presidential-candidates-404-pages/klobuchar.png"
src="/public/static/images/notes/presidential-candidates-404-pages/klobuchar.png"
width="865"
height="456"
alt="Amy Klobuchar"
@@ -164,7 +124,7 @@ This is the 404 page of someone who won't forget the [Midwestern roots](https://
I'll never publicly say anything against a good Dad joke. This is no exception.
<Image
src="/static/images/notes/presidential-candidates-404-pages/bullock.png"
src="/public/static/images/notes/presidential-candidates-404-pages/bullock.png"
width="865"
height="467"
alt="Steve Bullock"
@@ -175,7 +135,7 @@ I'll never publicly say anything against a good Dad joke. This is no exception.
Another quality Dad joke here.
<Image
src="/static/images/notes/presidential-candidates-404-pages/bennet.png"
src="/public/static/images/notes/presidential-candidates-404-pages/bennet.png"
width="865"
height="543"
alt="Michael Bennet"
@@ -186,7 +146,7 @@ Another quality Dad joke here.
Yet another Dad joke? I honestly had the hardest time ranking these three.
<Image
src="/static/images/notes/presidential-candidates-404-pages/delaney.png"
src="/public/static/images/notes/presidential-candidates-404-pages/delaney.png"
width="865"
height="405"
alt="John Delaney"
@@ -197,7 +157,7 @@ Yet another Dad joke? I honestly had the hardest time ranking these three.
A 404 page only a motivational author and speaker running for president could envision.
<Image
src="/static/images/notes/presidential-candidates-404-pages/williamson.png"
src="/public/static/images/notes/presidential-candidates-404-pages/williamson.png"
width="865"
height="357"
alt="Marianne Williamson"
@@ -208,7 +168,7 @@ A 404 page only a motivational author and speaker running for president could en
I guess this would be slightly humorous...four years ago. Time to move on from your middle-school crush, Donny.
<Image
src="/static/images/notes/presidential-candidates-404-pages/trump.png"
src="/public/static/images/notes/presidential-candidates-404-pages/trump.png"
width="865"
height="524"
alt="Trump/Pence"
@@ -223,7 +183,7 @@ These candidates haven't configured a custom 404 page, settling for the default
### 15. Julián Castro — [julianforthefuture.com](https://www.julianforthefuture.com/asdfasdf404)
<Image
src="/static/images/notes/presidential-candidates-404-pages/castro.png"
src="/public/static/images/notes/presidential-candidates-404-pages/castro.png"
width="865"
height="316"
alt="Julián Castro"
@@ -232,7 +192,7 @@ These candidates haven't configured a custom 404 page, settling for the default
### 16. Wayne Messam — [wayneforusa.com](https://wayneforusa.com/asdfasdf404)
<Image
src="/static/images/notes/presidential-candidates-404-pages/messam.png"
src="/public/static/images/notes/presidential-candidates-404-pages/messam.png"
width="865"
height="529"
alt="Wayne Messam"
@@ -241,7 +201,7 @@ These candidates haven't configured a custom 404 page, settling for the default
### 17. Tulsi Gabbard — [tulsi2020.com](https://www.tulsi2020.com/asdfasdf404)
<Image
src="/static/images/notes/presidential-candidates-404-pages/gabbard.png"
src="/public/static/images/notes/presidential-candidates-404-pages/gabbard.png"
width="865"
height="333"
alt="Tulsi Gabbard"
@@ -250,7 +210,7 @@ These candidates haven't configured a custom 404 page, settling for the default
### 18. Joe Sestak — [joesestak.com](https://www.joesestak.com/asdfasdf404)
<Image
src="/static/images/notes/presidential-candidates-404-pages/sestak.png"
src="/public/static/images/notes/presidential-candidates-404-pages/sestak.png"
width="865"
height="366"
alt="Joe Sestak"

View File

@@ -11,11 +11,8 @@ tags:
image: "/static/images/notes/security-headers-cloudflare-workers/security-headers.png"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
<Figure
src="/static/images/notes/security-headers-cloudflare-workers/security-headers.png"
src="/public/static/images/notes/security-headers-cloudflare-workers/security-headers.png"
width="700"
height="275"
priority
@@ -28,7 +25,7 @@ In 2019, it's becoming more and more important to harden websites via HTTP respo
[Cloudflare Workers](https://www.cloudflare.com/products/cloudflare-workers/) are a great feature of [Cloudflare](https://www.cloudflare.com/) that allows you to modify responses on-the-fly between your origin server and the user, similar to [AWS Lambda](https://aws.amazon.com/lambda/) (but much simpler). We'll use a Worker to add the headers.
<Image
src="/static/images/notes/security-headers-cloudflare-workers/cf-workers.png"
src="/public/static/images/notes/security-headers-cloudflare-workers/cf-workers.png"
width="650"
height="325"
alt="Cloudflare Workers"

View File

@@ -11,12 +11,9 @@ tags:
image: "/static/images/notes/shodan-search-queries/shodan.png"
---
import Image from "../components/media/Image";
import Figure from "../components/media/Figure";
Over time, I've collected an assortment of interesting, funny, and depressing search queries to plug into [Shodan](https://www.shodan.io/), the ([literal](https://www.vice.com/en_uk/article/9bvxmd/shodan-exposes-the-dark-side-of-the-net)) internet search engine. Some return facepalm-inducing results, while others return serious and/or ancient vulnerabilities in the wild.
<Figure src="/static/images/notes/shodan-search-queries/shodan.png" width="865" height="248" priority>
<Figure src="/public/static/images/notes/shodan-search-queries/shodan.png" width="865" height="248" priority>
[**Most search filters require a Shodan account.**](https://account.shodan.io/register)
</Figure>
@@ -50,7 +47,7 @@ The world and its devices are quickly becoming more connected through the shiny
```
<Image
src="/static/images/notes/shodan-search-queries/billboard3.png"
src="/public/static/images/notes/shodan-search-queries/billboard3.png"
width="450"
height="329"
alt="Example: Electronic Billboards"
@@ -63,7 +60,7 @@ The world and its devices are quickly becoming more connected through the shiny
```
<Image
src="/static/images/notes/shodan-search-queries/7-11.png"
src="/public/static/images/notes/shodan-search-queries/7-11.png"
width="600"
height="226"
alt="Example: Gas Station Pump Inventories"
@@ -76,7 +73,7 @@ P372 "ANPR enabled"
```
<Image
src="/static/images/notes/shodan-search-queries/plate-reader.png"
src="/public/static/images/notes/shodan-search-queries/plate-reader.png"
width="680"
height="284"
alt="Example: Automatic License Plate Reader"
@@ -117,7 +114,7 @@ http.title:"Tesla PowerPack System" http.component:"d3" -ga3ca4f2
```
<Image
src="/static/images/notes/shodan-search-queries/tesla.png"
src="/public/static/images/notes/shodan-search-queries/tesla.png"
width="865"
height="543"
alt="Example: Tesla PowerPack Charging Status"
@@ -138,7 +135,7 @@ Shodan made a pretty sweet [Ship Tracker](https://shiptracker.shodan.io/) that m
```
<Image
src="/static/images/notes/shodan-search-queries/sailor-vsat.png"
src="/public/static/images/notes/shodan-search-queries/sailor-vsat.png"
width="700"
height="361"
alt="Example: Maritime Satellites"
@@ -157,7 +154,7 @@ title:"Slocum Fleet Mission Control"
```
<Image
src="/static/images/notes/shodan-search-queries/refrigeration.png"
src="/public/static/images/notes/shodan-search-queries/refrigeration.png"
width="865"
height="456"
alt="Example: CAREL PlantVisor Refrigeration Units"
@@ -176,7 +173,7 @@ http.title:"Nordex Control" "Windows 2000 5.0 x86" "Jetty/3.1 (JSP 1.1; Servlet
```
<Image
src="/static/images/notes/shodan-search-queries/c4max.png"
src="/public/static/images/notes/shodan-search-queries/c4max.png"
width="865"
height="171"
alt="Example: C4 Max Vehicle GPS"
@@ -197,7 +194,7 @@ Secured by default, thankfully, but these 1,700+ machines still [have no busines
```
<Image
src="/static/images/notes/shodan-search-queries/power-gaugetech.png"
src="/public/static/images/notes/shodan-search-queries/power-gaugetech.png"
width="500"
height="246"
alt="Example: GaugeTech Electricity Meters"
@@ -239,7 +236,7 @@ Secured by default, thankfully, but these 1,700+ machines still [have no busines
[Shodan Images](https://images.shodan.io/) is a great supplementary tool to browse screenshots, by the way! [🔎 →](https://images.shodan.io/?query=%22authentication+disabled%22+%21screenshot.label%3Ablank)
<Figure src="/static/images/notes/shodan-search-queries/vnc.png" width="500" height="375">
<Figure src="/public/static/images/notes/shodan-search-queries/vnc.png" width="500" height="375">
The first result right now. 😞
</Figure>
@@ -264,7 +261,7 @@ title:"Weave Scope" http.favicon.hash:567176827
```
<Image
src="/static/images/notes/shodan-search-queries/weavescope.png"
src="/public/static/images/notes/shodan-search-queries/weavescope.png"
width="865"
height="465"
alt="Example: Weave Scope Dashboards"
@@ -278,7 +275,12 @@ Older versions were insecure by default. [Very scary.](https://krebsonsecurity.c
"MongoDB Server Information" port:27017 -authentication
```
<Image src="/static/images/notes/shodan-search-queries/mongo.png" width="500" height="238" alt="Example: MongoDB" />
<Image
src="/public/static/images/notes/shodan-search-queries/mongo.png"
width="500"
height="238"
alt="Example: MongoDB"
/>
### [Mongo Express](https://github.com/mongo-express/mongo-express) Web GUI [🔎 →](https://www.shodan.io/search?query=%22Set-Cookie%3A+mongo-express%3D%22+%22200+OK%22)
@@ -289,7 +291,7 @@ Like the [infamous phpMyAdmin](https://www.cvedetails.com/vulnerability-list/ven
```
<Image
src="/static/images/notes/shodan-search-queries/mongo-express.png"
src="/public/static/images/notes/shodan-search-queries/mongo-express.png"
width="700"
height="395"
alt="Example: Mongo Express GUI"
@@ -302,7 +304,7 @@ Like the [infamous phpMyAdmin](https://www.cvedetails.com/vulnerability-list/ven
```
<Image
src="/static/images/notes/shodan-search-queries/jenkins.png"
src="/public/static/images/notes/shodan-search-queries/jenkins.png"
width="700"
height="225"
alt="Example: Jenkins CI"
@@ -353,7 +355,7 @@ Lantronix password port:30718 -secured
```
<Image
src="/static/images/notes/shodan-search-queries/citrix.png"
src="/public/static/images/notes/shodan-search-queries/citrix.png"
width="700"
height="273"
alt="Example: Citrix Virtual Apps"
@@ -386,7 +388,7 @@ Telnet Configuration: [🔎 →](https://www.shodan.io/search?query=%22Polycom+C
```
<Image
src="/static/images/notes/shodan-search-queries/polycom.png"
src="/public/static/images/notes/shodan-search-queries/polycom.png"
width="550"
height="251"
alt="Example: Polycom Video Conferencing"
@@ -419,7 +421,7 @@ HP-ILO-4 !"HP-ILO-4/2.53" !"HP-ILO-4/2.54" !"HP-ILO-4/2.55" !"HP-ILO-4/2.60" !"H
```
<Image
src="/static/images/notes/shodan-search-queries/owa2007.png"
src="/public/static/images/notes/shodan-search-queries/owa2007.png"
width="450"
height="494"
alt="Example: OWA for Exchange 2007"
@@ -432,7 +434,7 @@ HP-ILO-4 !"HP-ILO-4/2.53" !"HP-ILO-4/2.54" !"HP-ILO-4/2.55" !"HP-ILO-4/2.60" !"H
```
<Image
src="/static/images/notes/shodan-search-queries/owa2010.png"
src="/public/static/images/notes/shodan-search-queries/owa2010.png"
width="450"
height="429"
alt="Example: OWA for Exchange 2010"
@@ -445,7 +447,7 @@ HP-ILO-4 !"HP-ILO-4/2.53" !"HP-ILO-4/2.54" !"HP-ILO-4/2.55" !"HP-ILO-4/2.60" !"H
```
<Image
src="/static/images/notes/shodan-search-queries/owa2013.png"
src="/public/static/images/notes/shodan-search-queries/owa2013.png"
width="580"
height="230"
alt="Example: OWA for Exchange 2013/2016"
@@ -494,7 +496,7 @@ Concerning [default network shares of QuickBooks](https://quickbooks.intuit.com/
```
<Image
src="/static/images/notes/shodan-search-queries/iomega.png"
src="/public/static/images/notes/shodan-search-queries/iomega.png"
width="600"
height="215"
alt="Example: Iomega / LenovoEMC NAS Drives"
@@ -507,7 +509,7 @@ Redirecting sencha port:9000
```
<Image
src="/static/images/notes/shodan-search-queries/buffalo.png"
src="/public/static/images/notes/shodan-search-queries/buffalo.png"
width="580"
height="140"
alt="Example: Buffalo TeraStation NAS Drives"
@@ -520,7 +522,7 @@ Redirecting sencha port:9000
```
<Image
src="/static/images/notes/shodan-search-queries/logitech.png"
src="/public/static/images/notes/shodan-search-queries/logitech.png"
width="500"
height="224"
alt="Example: Logitech Media Servers"
@@ -539,7 +541,7 @@ Redirecting sencha port:9000
```
<Image
src="/static/images/notes/shodan-search-queries/plexpy.png"
src="/public/static/images/notes/shodan-search-queries/plexpy.png"
width="560"
height="266"
alt="Example: PlexPy / Tautulli Dashboards"
@@ -585,7 +587,12 @@ html:"DVR_H264 ActiveX"
"Serial Number:" "Built:" "Server: HP HTTP"
```
<Image src="/static/images/notes/shodan-search-queries/hp.png" width="700" height="272" alt="Example: HP Printers" />
<Image
src="/public/static/images/notes/shodan-search-queries/hp.png"
width="700"
height="272"
alt="Example: HP Printers"
/>
### Xerox Copiers/Printers [🔎 →](https://www.shodan.io/search?query=ssl%3A%22Xerox+Generic+Root%22)
@@ -594,7 +601,7 @@ ssl:"Xerox Generic Root"
```
<Image
src="/static/images/notes/shodan-search-queries/xerox.png"
src="/public/static/images/notes/shodan-search-queries/xerox.png"
width="620"
height="263"
alt="Example: Xerox Copiers/Printers"
@@ -611,7 +618,7 @@ ssl:"Xerox Generic Root"
```
<Image
src="/static/images/notes/shodan-search-queries/epson.png"
src="/public/static/images/notes/shodan-search-queries/epson.png"
width="550"
height="308"
alt="Example: Epson Printers"
@@ -628,7 +635,7 @@ ssl:"Xerox Generic Root"
```
<Image
src="/static/images/notes/shodan-search-queries/canon.png"
src="/public/static/images/notes/shodan-search-queries/canon.png"
width="550"
height="195"
alt="Example: Canon Printers"
@@ -645,7 +652,7 @@ ssl:"Xerox Generic Root"
```
<Image
src="/static/images/notes/shodan-search-queries/yamaha.png"
src="/public/static/images/notes/shodan-search-queries/yamaha.png"
width="550"
height="349"
alt="Example: Yamaha Stereos"
@@ -682,7 +689,7 @@ title:"OctoPrint" -title:"Login" http.favicon.hash:1307375944
```
<Image
src="/static/images/notes/shodan-search-queries/octoprint.png"
src="/public/static/images/notes/shodan-search-queries/octoprint.png"
width="700"
height="335"
alt="Example: OctoPrint 3D Printers"
@@ -695,7 +702,7 @@ title:"OctoPrint" -title:"Login" http.favicon.hash:1307375944
```
<Image
src="/static/images/notes/shodan-search-queries/eth.png"
src="/public/static/images/notes/shodan-search-queries/eth.png"
width="800"
height="141"
alt="Example: Etherium Miners"

View File

@@ -10,13 +10,11 @@ tags:
image: "/static/images/notes/y2k-sandbox/screenshot.png"
---
import Figure from "../components/media/Figure";
A few months ago, I stumbled upon [my first website ever](https://jakejarvis.github.io/my-first-website/) on an old floppy disk. Despite the instant cringing, I [uploaded it](https://github.com/jakejarvis/my-first-website) to GitHub, [collected other iterations](/previously/), and made an [#awesome-list](https://github.com/jakejarvis/awesome-first-code) of others who were brave and/or shameless enough to do the same. But why not take that ~~one~~ 1,000 steps further?
Introducing the [**Y2K Sandbox**](https://y2k.app/) — with fully-featured, fully-isolated, on-demand [**Windows Millennium Edition®**](https://www.youtube.com/watch?v=CaNDeyYP98A) virtual machines, simply to experience my first website in its natural Internet Explorer 5 habitat. And maybe play some [3D Pinball: Space Cadet](https://en.wikipedia.org/wiki/Full_Tilt!_Pinball#3D_Pinball_for_Windows_%E2%80%93_Space_Cadet). Oh, and [Microsoft Bob](https://en.wikipedia.org/wiki/Microsoft_Bob) is there too if you want to say hello and catch up. 🤓
<Figure src="/static/images/notes/y2k-sandbox/screenshot.png" width="865" height="649" priority>
<Figure src="/public/static/images/notes/y2k-sandbox/screenshot.png" width="865" height="649" priority>
[**Play in the Y2K Sandbox, at your own risk.**](https://y2k.app/)
</Figure>
@@ -28,7 +26,7 @@ The frontend is _much_ simpler with [a few lines of JavaScript](https://github.c
I must give credit to both [charlie.bz](https://charlie.bz/) and [benjojo.co.uk](https://benjojo.co.uk/), similar websites I was enamored with when they were posted on Hacker News a few years ago. Think we'll see some websites like these with Windows 29 in a decade?
<Figure src="/static/images/notes/y2k-sandbox/windows-me.png" width="320" height="92">
<Figure src="/public/static/images/notes/y2k-sandbox/windows-me.png" width="320" height="92">
**@microsoft** Please don't sue me.
</Figure>

View File

@@ -45,10 +45,10 @@
"is-absolute-url": "^4.0.1",
"is-email-like": "^2.0.0",
"markdown-to-jsx": "^7.1.6",
"mdx-bundler": "^8.0.1",
"modern-normalize": "github:sindresorhus/modern-normalize#1fc6b5a86676b7ac8abc62d04d6080f92debc70f",
"next": "v12.0.8",
"next": "^12.0.8",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "4.0.0-rc.1",
"next-seo": "^4.28.1",
"next-themes": "^0.0.15",
"node-fetch": "^3.1.1",
@@ -85,11 +85,10 @@
"@types/react-is": "^17.0.3",
"@types/remove-markdown": "^0.3.1",
"@types/sanitize-html": "^2.6.2",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"autoprefixer": "^10.4.2",
"cross-env": "^7.0.3",
"esbuild": "^0.14.11",
"eslint": "~8.7.0",
"eslint-config-next": "~12.0.8",
"eslint-config-prettier": "~8.3.0",

View File

@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { ThemeProvider } from "next-themes";
import { DefaultSeo, SocialProfileJsonLd } from "next-seo";
import * as Fathom from "fathom-client";
import Layout from "../components/Layout";
import Layout from "../components/Layout/Layout";
import * as config from "../lib/config";
import type { AppProps } from "next/app";

View File

@@ -1,8 +1,8 @@
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import Video from "../components/media/Video";
import { TapeIcon } from "../components/icons";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import Video from "../components/Video/Video";
import { TapeIcon } from "../components/Icons";
import thumbnail from "../public/static/images/birthday/thumb.png";
@@ -22,22 +22,9 @@ const Birthday = () => (
<Content>
<Video
url={[
{ src: "/static/images/birthday/birthday.webm", type: "video/webm" },
{ src: "/static/images/birthday/birthday.mp4", type: "video/mp4" },
]}
config={{
// @ts-ignore
file: {
attributes: {
poster: thumbnail.src,
controlsList: "nodownload",
preload: "metadata",
autoPlay: false,
},
},
}}
controls={true}
webm="/static/images/birthday/birthday.webm"
mp4="/static/images/birthday/birthday.mp4"
thumbnail={thumbnail.src}
/>
</Content>
</>

View File

@@ -1,8 +1,8 @@
import Image from "next/image";
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import { BotIcon } from "../components/icons";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import { BotIcon } from "../components/Icons";
import cliImg from "../public/static/images/cli/screenshot.png";

View File

@@ -1,8 +1,8 @@
import { NextSeo } from "next-seo";
import Title from "../components/title/Title";
import ContactForm from "../components/contact/ContactForm";
import { MailIcon, LockIcon } from "../components/icons";
import Content from "../components/Content";
import Title from "../components/Title/Title";
import ContactForm from "../components/ContactForm/ContactForm";
import { MailIcon, LockIcon } from "../components/Icons";
import Content from "../components/Content/Content";
const Contact = () => (
<>

View File

@@ -1,7 +1,7 @@
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import Video from "../components/media/Video";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import Video from "../components/Video/Video";
import thumbnail from "../public/static/images/hillary/thumb.png";
@@ -18,31 +18,10 @@ const Hillary = () => (
<Title>My Brief Apperance in Hillary Clinton's DNC Video</Title>
<Content>
<Video
url={[
{ src: "/static/images/hillary/convention-720p.webm", type: "video/webm" },
{ src: "/static/images/hillary/convention-720p.mp4", type: "video/mp4" },
]}
config={{
// @ts-ignore
file: {
attributes: {
poster: thumbnail.src,
controlsList: "nodownload",
preload: "metadata",
autoPlay: false,
},
tracks: [
{
kind: "subtitles",
src: "/static/images/hillary/subs.en.vtt",
srcLang: "en",
label: "English",
default: true,
},
],
},
}}
controls={true}
webm="/static/images/hillary/convention-720p.webm"
mp4="/static/images/hillary/convention-720p.mp4"
thumbnail={thumbnail.src}
subs="/static/images/hillary/subs.en.vtt"
/>
<p className="copyright">

View File

@@ -1,5 +1,5 @@
import ColorLink from "../components/home/ColorLink";
import { WaveIcon, LockIcon } from "../components/icons";
import ColorfulLink from "../components/ColorfulLink/ColorfulLink";
import { WaveIcon, LockIcon } from "../components/Icons";
const Index = () => (
<>
@@ -12,7 +12,7 @@ const Index = () => (
<h2>
I'm a frontend web developer based in{" "}
<ColorLink
<ColorfulLink
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"
@@ -20,13 +20,13 @@ const Index = () => (
external
>
Boston
</ColorLink>
</ColorfulLink>
.
</h2>
<p>
I specialize in{" "}
<ColorLink
<ColorfulLink
href="https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/"
title='"The Brutal Lifecycle of JavaScript Frameworks" by Ian Allen'
lightColor="#1091b3"
@@ -34,9 +34,9 @@ const Index = () => (
external
>
modern JS frameworks
</ColorLink>{" "}
</ColorfulLink>{" "}
and{" "}
<ColorLink
<ColorfulLink
href="http://vanilla-js.com/"
title="The best JS framework in the world by Eric Wastl"
lightColor="#f48024"
@@ -44,9 +44,9 @@ const Index = () => (
external
>
vanilla JavaScript
</ColorLink>{" "}
</ColorfulLink>{" "}
to make nifty{" "}
<ColorLink
<ColorfulLink
href="https://jamstack.wtf/"
title="WTF is JAMstack?"
lightColor="#04a699"
@@ -54,9 +54,9 @@ const Index = () => (
external
>
JAMstack sites
</ColorLink>{" "}
</ColorfulLink>{" "}
with dynamic{" "}
<ColorLink
<ColorfulLink
href="https://nodejs.org/en/"
title="Node.js Official Website"
lightColor="#6fbc4e"
@@ -64,9 +64,9 @@ const Index = () => (
external
>
Node.js
</ColorLink>{" "}
</ColorfulLink>{" "}
services. But I'm fluent in non-buzzwords like{" "}
<ColorLink
<ColorfulLink
href="https://stitcher.io/blog/php-in-2020"
title='"PHP in 2020" by Brent Roose'
lightColor="#8892bf"
@@ -74,9 +74,9 @@ const Index = () => (
external
>
PHP
</ColorLink>
</ColorfulLink>
,{" "}
<ColorLink
<ColorfulLink
href="https://www.ruby-lang.org/en/"
title="Ruby Official Website"
lightColor="#d34135"
@@ -84,9 +84,9 @@ const Index = () => (
external
>
Ruby
</ColorLink>
</ColorfulLink>
, and{" "}
<ColorLink
<ColorfulLink
href="https://golang.org/"
title="Golang Official Website"
lightColor="#00acd7"
@@ -94,13 +94,13 @@ const Index = () => (
external
>
Go
</ColorLink>{" "}
</ColorfulLink>{" "}
too.
</p>
<p>
Whenever possible, I also apply my experience in{" "}
<ColorLink
<ColorfulLink
href="https://github.com/jakejarvis/awesome-shodan-queries"
title="jakejarvis/awesome-shodan-queries on GitHub"
lightColor="#00b81a"
@@ -108,9 +108,9 @@ const Index = () => (
external
>
application security
</ColorLink>
</ColorfulLink>
,{" "}
<ColorLink
<ColorfulLink
href="https://www.cloudflare.com/learning/serverless/what-is-serverless/"
title='"What is serverless computing?" on Cloudflare'
lightColor="#0098ec"
@@ -118,9 +118,9 @@ const Index = () => (
external
>
serverless stacks
</ColorLink>
</ColorfulLink>
, and{" "}
<ColorLink
<ColorfulLink
href="https://xkcd.com/1319/"
title='"Automation" on xkcd'
lightColor="#ff6200"
@@ -128,46 +128,46 @@ const Index = () => (
external
>
DevOps automation
</ColorLink>
</ColorfulLink>
.
</p>
<p>
I fell in love with{" "}
<ColorLink
<ColorfulLink
href="/previously/"
title="My Terrible, Horrible, No Good, Very Bad First Websites"
lightColor="#4169e1"
darkColor="#8ca9ff"
>
frontend web design
</ColorLink>{" "}
</ColorfulLink>{" "}
and{" "}
<ColorLink
<ColorfulLink
href="/notes/my-first-code/"
title="Jake's Bulletin Board, circa 2003"
lightColor="#9932cc"
darkColor="#d588fb"
>
backend programming
</ColorLink>{" "}
</ColorfulLink>{" "}
back when my only source of income was{" "}
<span className="birthday">
<ColorLink
<ColorfulLink
href="/birthday/"
title="🎉 Cranky Birthday Boy on VHS Tape 📼"
lightColor="#e40088"
darkColor="#fd40b1"
>
the Tooth Fairy
</ColorLink>
</ColorfulLink>
</span>
. <span className="quiet">I've improved a bit since then, I think...</span>
</p>
<p>
Over the years, some of my side projects{" "}
<ColorLink
<ColorfulLink
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"
@@ -175,16 +175,16 @@ const Index = () => (
external
>
have
</ColorLink>{" "}
<ColorLink
</ColorfulLink>{" "}
<ColorfulLink
href="/leo/"
title="Powncer segment on The Lab with Leo Laporte (G4techTV)"
lightColor="#f78200"
darkColor="#fd992a"
>
been
</ColorLink>{" "}
<ColorLink
</ColorfulLink>{" "}
<ColorfulLink
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"
@@ -192,8 +192,8 @@ const Index = () => (
external
>
featured
</ColorLink>{" "}
<ColorLink
</ColorfulLink>{" "}
<ColorfulLink
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"
@@ -201,8 +201,8 @@ const Index = () => (
external
>
by
</ColorLink>{" "}
<ColorLink
</ColorfulLink>{" "}
<ColorfulLink
href="https://www.wired.com/2007/04/our-web-servers/"
title='"Middio: A YouTube Scraper for Major Label Music Videos" on Wired'
lightColor="#009cdf"
@@ -210,8 +210,8 @@ const Index = () => (
external
>
various
</ColorLink>{" "}
<ColorLink
</ColorfulLink>{" "}
<ColorfulLink
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"
@@ -219,8 +219,8 @@ const Index = () => (
external
>
media
</ColorLink>{" "}
<ColorLink
</ColorfulLink>{" "}
<ColorfulLink
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"
@@ -228,13 +228,13 @@ const Index = () => (
external
>
outlets
</ColorLink>
</ColorfulLink>
.
</p>
<p>
You can find more of my work on{" "}
<ColorLink
<ColorfulLink
href="https://github.com/jakejarvis"
title="Jake Jarvis on GitHub"
lightColor="#8d4eff"
@@ -242,9 +242,9 @@ const Index = () => (
external
>
GitHub
</ColorLink>{" "}
</ColorfulLink>{" "}
and{" "}
<ColorLink
<ColorfulLink
href="https://www.linkedin.com/in/jakejarvis/"
title="Jake Jarvis on LinkedIn"
lightColor="#0073b1"
@@ -252,18 +252,18 @@ const Index = () => (
external
>
LinkedIn
</ColorLink>
</ColorfulLink>
. I'm always available to connect over{" "}
<ColorLink href="/contact/" title="Send an email" lightColor="#de0c0c" darkColor="#ff5050">
<ColorfulLink href="/contact/" title="Send an email" lightColor="#de0c0c" darkColor="#ff5050">
email
</ColorLink>{" "}
</ColorfulLink>{" "}
<sup className="monospace pgp_key">
<ColorLink href="/pubkey.asc" title="My Public Key" lightColor="#757575" darkColor="#959595" external>
<ColorfulLink href="/pubkey.asc" title="My Public Key" lightColor="#757575" darkColor="#959595" external>
<LockIcon className="icon" /> 2B0C 9CF2 51E6 9A39
</ColorLink>
</ColorfulLink>
</sup>
,{" "}
<ColorLink
<ColorfulLink
href="https://twitter.com/jakejarvis"
title="Jake Jarvis on Twitter"
lightColor="#00acee"
@@ -271,16 +271,16 @@ const Index = () => (
external
>
Twitter
</ColorLink>
</ColorfulLink>
, or{" "}
<ColorLink
<ColorfulLink
href="sms:+1-617-917-3737"
title="Send SMS to +1 (617) 917-3737"
lightColor="#6fcc01"
darkColor="#8edb34"
>
SMS
</ColorLink>{" "}
</ColorfulLink>{" "}
as well!
</p>

View File

@@ -1,7 +1,7 @@
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import Video from "../components/media/Video";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import Video from "../components/Video/Video";
import thumbnail from "../public/static/images/leo/thumb.png";
@@ -19,31 +19,10 @@ const Leo = () => (
<Content>
<Video
url={[
{ src: "/static/images/leo/leo.webm", type: "video/webm" },
{ src: "/static/images/leo/leo.mp4", type: "video/mp4" },
]}
config={{
// @ts-ignore
file: {
attributes: {
poster: thumbnail.src,
controlsList: "nodownload",
preload: "metadata",
autoPlay: false,
},
tracks: [
{
kind: "subtitles",
src: "/static/images/leo/subs.en.vtt",
srcLang: "en",
label: "English",
default: true,
},
],
},
}}
controls={true}
webm="/static/images/leo/leo.webm"
mp4="/static/images/leo/leo.mp4"
thumbnail={thumbnail.src}
subs="/static/images/leo/subs.en.vtt"
/>
<p className="copyright">

View File

@@ -1,7 +1,7 @@
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import { LicenseIcon } from "../components/icons";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import { LicenseIcon } from "../components/Icons";
const License = () => (
<>

View File

@@ -1,20 +1,17 @@
import { useMemo } from "react";
import { InView } from "react-intersection-observer";
import { NextSeo, ArticleJsonLd } from "next-seo";
import { MDXRemote } from "next-mdx-remote";
import { escape } from "html-escaper";
import { getMDXComponent } from "mdx-bundler/client";
import Content from "../../components/Content";
import Meta from "../../components/notes/Meta";
import Comments from "../../components/notes/Comments";
import CustomCode from "../../components/code-block/Code";
import Content from "../../components/Content/Content";
import Meta from "../../components/NoteMeta/NoteMeta";
import Comments from "../../components/Comments/Comments";
import * as mdxComponents from "../../lib/mdx-components";
import { getNote, getNoteSlugs } from "../../lib/parse-notes";
import * as config from "../../lib/config";
import type { GetStaticProps, GetStaticPaths } from "next";
import type { NoteType } from "../../types";
const Note = ({ frontMatter, mdxSource }: NoteType) => {
const MDXComponent = useMemo(() => getMDXComponent(mdxSource, { process }), [mdxSource]);
const Note = ({ frontMatter, source }: NoteType) => {
return (
<>
<NextSeo
@@ -56,7 +53,7 @@ const Note = ({ frontMatter, mdxSource }: NoteType) => {
<Meta {...frontMatter} />
<Content>
<MDXComponent components={{ code: CustomCode }} />
<MDXRemote {...source} components={{ ...mdxComponents }} lazy />
</Content>
{frontMatter.noComments !== true && (
<InView rootMargin="140px" triggerOnce={true} fallbackInView={true}>
@@ -72,12 +69,12 @@ const Note = ({ frontMatter, mdxSource }: NoteType) => {
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const { frontMatter, mdxSource } = await getNote(params.slug as string);
const { frontMatter, source } = await getNote(params.slug as string);
return {
props: {
frontMatter,
mdxSource,
source,
},
};
};

View File

@@ -1,6 +1,6 @@
import { NextSeo } from "next-seo";
import { format } from "date-fns";
import List from "../../components/notes/List";
import List from "../../components/NotesList/NotesList";
import { getAllNotes } from "../../lib/parse-notes";
import type { GetStaticProps } from "next";

View File

@@ -1,8 +1,8 @@
import Image from "next/image";
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import { FloppyIcon, SirenIcon } from "../components/icons";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import { FloppyIcon, SirenIcon } from "../components/Icons";
/* eslint-disable camelcase */
import img_wayback from "../public/static/images/previously/wayback.png";

View File

@@ -1,9 +1,9 @@
import Image from "next/image";
import Link from "next/link";
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import { PrivacyIcon } from "../components/icons";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import { PrivacyIcon } from "../components/Icons";
import faunaImg from "../public/static/images/privacy/fauna_hits.png";
@@ -75,7 +75,7 @@ const Privacy = () => (
</a>{" "}
and{" "}
<a
href="https://github.com/jakejarvis/jarv.is/blob/main/components/hits/Hits.tsx"
href="https://github.com/jakejarvis/jarv.is/blob/main/components/notes/HitCounter.tsx"
target="_blank"
rel="noopener noreferrer"
>

View File

@@ -1,8 +1,8 @@
import { graphql } from "@octokit/graphql";
import { NextSeo } from "next-seo";
import Title from "../components/title/Title";
import RepoCard from "../components/projects/RepoCard";
import { ProjectsIcon } from "../components/icons";
import Title from "../components/Title/Title";
import RepoCard from "../components/RepositoryCard/RepositoryCard";
import { ProjectsIcon } from "../components/Icons";
import type { GetStaticProps } from "next";
import { RepoType } from "../types";

View File

@@ -1,9 +1,9 @@
import Image from "next/image";
import Link from "next/link";
import { NextSeo } from "next-seo";
import Content from "../components/Content";
import Title from "../components/title/Title";
import { LaptopIcon } from "../components/icons";
import Content from "../components/Content/Content";
import Title from "../components/Title/Title";
import { LaptopIcon } from "../components/Icons";
import desktopImg from "../public/static/images/uses/bigsur.png";

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 903 KiB

After

Width:  |  Height:  |  Size: 903 KiB

View File

Before

Width:  |  Height:  |  Size: 501 KiB

After

Width:  |  Height:  |  Size: 501 KiB

View File

Before

Width:  |  Height:  |  Size: 442 KiB

After

Width:  |  Height:  |  Size: 442 KiB

View File

Before

Width:  |  Height:  |  Size: 408 KiB

After

Width:  |  Height:  |  Size: 408 KiB

View File

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 236 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 393 KiB

Some files were not shown because too many files have changed in this diff Show More