mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-04-26 09:05:22 -04:00
properly import and optimize/cache images in markdown files
This commit is contained in:
parent
36faa6c234
commit
ba10742c9b
@ -1,7 +1,3 @@
|
|||||||
# required. falls back to relative URLs in most places, but still must be set in the Vercel dashboard for production.
|
|
||||||
# include https:// and leave out the trailing slash, e.g. "https://jarv.is".
|
|
||||||
NEXT_PUBLIC_BASE_URL=
|
|
||||||
|
|
||||||
# required. storage for hit counter endpoints at /api/count and /api/hits.
|
# required. storage for hit counter endpoints at /api/count and /api/hits.
|
||||||
# currently uses Postgres, but this can be changed in prisma/schema.prisma.
|
# currently uses Postgres, but this can be changed in prisma/schema.prisma.
|
||||||
# https://www.prisma.io/docs/postgres/overview
|
# https://www.prisma.io/docs/postgres/overview
|
||||||
|
@ -31,8 +31,8 @@ export default function Page() {
|
|||||||
src={{
|
src={{
|
||||||
webm: "/static/images/birthday/birthday.webm",
|
webm: "/static/images/birthday/birthday.webm",
|
||||||
mp4: "/static/images/birthday/birthday.mp4",
|
mp4: "/static/images/birthday/birthday.mp4",
|
||||||
image: thumbnail.src,
|
|
||||||
}}
|
}}
|
||||||
|
poster={thumbnail.src}
|
||||||
/>
|
/>
|
||||||
</Content>
|
</Content>
|
||||||
</>
|
</>
|
||||||
|
@ -37,7 +37,9 @@ export default function Page() {
|
|||||||
anyone, ever).
|
anyone, ever).
|
||||||
</Blockquote>
|
</Blockquote>
|
||||||
|
|
||||||
<Image src={cliImg} href="https://www.npmjs.com/package/@jakejarvis/cli" alt="Terminal Screenshot" priority />
|
<Link href="https://www.npmjs.com/package/@jakejarvis/cli">
|
||||||
|
<Image src={cliImg} alt="Terminal Screenshot" priority />
|
||||||
|
</Link>
|
||||||
|
|
||||||
<H2 id="usage">Usage</H2>
|
<H2 id="usage">Usage</H2>
|
||||||
<CodeBlock withCopyButton>npx @jakejarvis/cli</CodeBlock>
|
<CodeBlock withCopyButton>npx @jakejarvis/cli</CodeBlock>
|
||||||
|
@ -15,7 +15,7 @@ export async function sendMessage(
|
|||||||
try {
|
try {
|
||||||
// these are both backups to client-side validations just in case someone squeezes through without them. the codes
|
// these are both backups to client-side validations just in case someone squeezes through without them. the codes
|
||||||
// are identical so they're caught in the same fashion.
|
// are identical so they're caught in the same fashion.
|
||||||
if (!formData.get("name") || !formData.get("email") || !formData.get("message")) {
|
if (!formData || !formData.get("name") || !formData.get("email") || !formData.get("message")) {
|
||||||
return { success: false, message: "Please make sure that all fields are properly filled in.", payload: formData };
|
return { success: false, message: "Please make sure that all fields are properly filled in.", payload: formData };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export async function sendMessage(
|
|||||||
});
|
});
|
||||||
const turnstileData = await turnstileResponse.json();
|
const turnstileData = await turnstileResponse.json();
|
||||||
|
|
||||||
if (!turnstileData.success) {
|
if (!turnstileData || !turnstileData.success) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Did you complete the CAPTCHA? (If you're human, that is...)",
|
message: "Did you complete the CAPTCHA? (If you're human, that is...)",
|
||||||
@ -44,13 +44,14 @@ export async function sendMessage(
|
|||||||
from: `${formData.get("name")} <${process.env.RESEND_DOMAIN ? `noreply@${process.env.RESEND_DOMAIN}` : "onboarding@resend.dev"}>`,
|
from: `${formData.get("name")} <${process.env.RESEND_DOMAIN ? `noreply@${process.env.RESEND_DOMAIN}` : "onboarding@resend.dev"}>`,
|
||||||
replyTo: `${formData.get("name")} <${formData.get("email")}>`,
|
replyTo: `${formData.get("name")} <${formData.get("email")}>`,
|
||||||
to: [config.authorEmail],
|
to: [config.authorEmail],
|
||||||
subject: `[${config.siteDomain}] Contact Form Submission`,
|
subject: `[${config.siteName}] Contact Form Submission`,
|
||||||
text: formData.get("message") as string,
|
text: formData.get("message") as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, message: "Thanks! You should hear from me soon.", payload: formData };
|
return { success: true, message: "Thanks! You should hear from me soon.", payload: formData };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Internal server error... Try again later or shoot me an old-fashioned email?",
|
message: "Internal server error... Try again later or shoot me an old-fashioned email?",
|
||||||
|
@ -27,8 +27,8 @@ const ContactForm = () => {
|
|||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
required
|
required
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
defaultValue={(formState.payload?.get("name") || "") as string}
|
defaultValue={(formState?.payload?.get("name") || "") as string}
|
||||||
disabled={formState.success}
|
disabled={formState?.success}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@ -38,8 +38,8 @@ const ContactForm = () => {
|
|||||||
required
|
required
|
||||||
inputMode="email"
|
inputMode="email"
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
defaultValue={(formState.payload?.get("email") || "") as string}
|
defaultValue={(formState?.payload?.get("email") || "") as string}
|
||||||
disabled={formState.success}
|
disabled={formState?.success}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
@ -48,8 +48,8 @@ const ContactForm = () => {
|
|||||||
minRows={5}
|
minRows={5}
|
||||||
required
|
required
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
defaultValue={(formState.payload?.get("message") || "") as string}
|
defaultValue={(formState?.payload?.get("message") || "") as string}
|
||||||
disabled={formState.success}
|
disabled={formState?.success}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -72,7 +72,7 @@ const ContactForm = () => {
|
|||||||
Markdown syntax
|
Markdown syntax
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
is allowed here, e.g.: <strong>**bold**</strong>, <em>_italics_</em>, [
|
is allowed here, e.g.: <strong>**bold**</strong>, <em>_italics_</em>, [
|
||||||
<Link href="https://jarv.is" underline={false} openInNewTab>
|
<Link href="https://jarv.is" plain>
|
||||||
links
|
links
|
||||||
</Link>
|
</Link>
|
||||||
](https://jarv.is), and <code>`code`</code>.
|
](https://jarv.is), and <code>`code`</code>.
|
||||||
@ -86,7 +86,7 @@ const ContactForm = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.actionRow}>
|
<div className={styles.actionRow}>
|
||||||
{!formState.success && (
|
{!formState?.success && (
|
||||||
<button type="submit" disabled={pending} className={styles.submitButton}>
|
<button type="submit" disabled={pending} className={styles.submitButton}>
|
||||||
{pending ? (
|
{pending ? (
|
||||||
<span>Sending...</span>
|
<span>Sending...</span>
|
||||||
@ -98,10 +98,10 @@ const ContactForm = () => {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{formState.message && (
|
{formState?.message && (
|
||||||
<div className={clsx(styles.result, formState.success ? styles.success : styles.error)}>
|
<div className={clsx(styles.result, formState?.success ? styles.success : styles.error)}>
|
||||||
{formState.success ? <GoCheck className={styles.resultIcon} /> : <GoX className={styles.resultIcon} />}{" "}
|
{formState?.success ? <GoCheck className={styles.resultIcon} /> : <GoX className={styles.resultIcon} />}{" "}
|
||||||
{formState.message}
|
{formState?.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,9 +31,8 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Fill out this quick form and I'll get back to you as soon as I can! You can also{" "}
|
Fill out this quick form and I'll get back to you as soon as I can! You can also{" "}
|
||||||
<Link href="mailto:jake@jarv.is">email me directly</Link>, send me a{" "}
|
<Link href="mailto:jake@jarv.is">email me directly</Link> or send me a{" "}
|
||||||
<Link href="https://fediverse.jarv.is/@jake">direct message on Mastodon</Link>, or{" "}
|
<Link href="https://fediverse.jarv.is/@jake">direct message on Mastodon</Link>.
|
||||||
<Link href="sms:+1-617-917-3737">text me</Link>.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
🔐 You can grab my public key here:{" "}
|
🔐 You can grab my public key here:{" "}
|
||||||
|
@ -33,8 +33,8 @@ export default function Page() {
|
|||||||
webm: "/static/images/hillary/convention-720p.webm",
|
webm: "/static/images/hillary/convention-720p.webm",
|
||||||
mp4: "/static/images/hillary/convention-720p.mp4",
|
mp4: "/static/images/hillary/convention-720p.mp4",
|
||||||
vtt: "/static/images/hillary/subs.en.vtt",
|
vtt: "/static/images/hillary/subs.en.vtt",
|
||||||
image: thumbnail.src,
|
|
||||||
}}
|
}}
|
||||||
|
poster={thumbnail.src}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Analytics } from "@vercel/analytics/react";
|
import { Analytics } from "@vercel/analytics/next";
|
||||||
import { ThemeProvider } from "../contexts/ThemeContext";
|
import { ThemeProvider } from "../contexts/ThemeContext";
|
||||||
import Layout from "../components/Layout";
|
import Layout from "../components/Layout";
|
||||||
import config from "../lib/config";
|
import config from "../lib/config";
|
||||||
@ -11,10 +11,10 @@ import "modern-normalize/modern-normalize.css"; // https://github.com/sindresorh
|
|||||||
import "./themes.css";
|
import "./themes.css";
|
||||||
import "./global.css";
|
import "./global.css";
|
||||||
|
|
||||||
import { meJpg } from "../lib/config/favicons";
|
import meJpg from "../public/static/images/me.jpg";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL(process.env.NEXT_PUBLIC_BASE_URL || `https://${config.siteDomain}`),
|
metadataBase: new URL(config.baseUrl),
|
||||||
title: {
|
title: {
|
||||||
template: `%s – ${config.siteName}`,
|
template: `%s – ${config.siteName}`,
|
||||||
default: `${config.siteName} – ${config.shortDescription}`,
|
default: `${config.siteName} – ${config.shortDescription}`,
|
||||||
@ -23,7 +23,7 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
siteName: config.siteName,
|
siteName: config.siteName,
|
||||||
title: {
|
title: {
|
||||||
template: `%s – ${config.siteName}`,
|
template: "%s",
|
||||||
default: `${config.siteName} – ${config.shortDescription}`,
|
default: `${config.siteName} – ${config.shortDescription}`,
|
||||||
},
|
},
|
||||||
url: "/",
|
url: "/",
|
||||||
@ -37,11 +37,21 @@ export const metadata: Metadata = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
alternates: {
|
alternates: {
|
||||||
types: {
|
|
||||||
"application/rss+xml": "/feed.xml",
|
|
||||||
"application/atom+xml": "/feed.atom",
|
|
||||||
},
|
|
||||||
canonical: "/",
|
canonical: "/",
|
||||||
|
types: {
|
||||||
|
"application/rss+xml": [
|
||||||
|
{
|
||||||
|
title: `${config.siteName} (RSS)`,
|
||||||
|
url: "/feed.xml",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"application/atom+xml": [
|
||||||
|
{
|
||||||
|
title: `${config.siteName} (Atom)`,
|
||||||
|
url: "/feed.atom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
other: {
|
other: {
|
||||||
humans: "/humans.txt",
|
humans: "/humans.txt",
|
||||||
@ -53,10 +63,10 @@ const jsonLd: WithContext<Person> = {
|
|||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Person",
|
"@type": "Person",
|
||||||
name: config.authorName,
|
name: config.authorName,
|
||||||
url: metadata.metadataBase?.href || `https://${config.siteDomain}/`,
|
url: config.baseUrl,
|
||||||
image: new URL(meJpg.src, metadata.metadataBase || `https://${config.siteDomain}`).href,
|
image: `${config.baseUrl}${meJpg.src}`,
|
||||||
sameAs: [
|
sameAs: [
|
||||||
metadata.metadataBase?.href || `https://${config.siteDomain}/`,
|
config.baseUrl,
|
||||||
`https://github.com/${config.authorSocial?.github}`,
|
`https://github.com/${config.authorSocial?.github}`,
|
||||||
`https://keybase.io/${config.authorSocial?.keybase}`,
|
`https://keybase.io/${config.authorSocial?.keybase}`,
|
||||||
`https://twitter.com/${config.authorSocial?.twitter}`,
|
`https://twitter.com/${config.authorSocial?.twitter}`,
|
||||||
@ -73,13 +83,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
return (
|
return (
|
||||||
<html lang={config.siteLocale} suppressHydrationWarning>
|
<html lang={config.siteLocale} suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
<script
|
|
||||||
// unminified: https://gist.github.com/jakejarvis/79b0ec8506bc843023546d0d29861bf0
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: `(()=>{try{const e=document.documentElement,t="undefined"!=typeof Storage?window.localStorage.getItem("theme"):null,a=(t&&"dark"===t)??window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";e.dataset.theme=a,e.style.colorScheme=a}catch(e){}})()`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ export default function Page() {
|
|||||||
webm: "/static/images/leo/leo.webm",
|
webm: "/static/images/leo/leo.webm",
|
||||||
mp4: "/static/images/leo/leo.mp4",
|
mp4: "/static/images/leo/leo.mp4",
|
||||||
vtt: "/static/images/leo/subs.en.vtt",
|
vtt: "/static/images/leo/subs.en.vtt",
|
||||||
image: thumbnail.src,
|
|
||||||
}}
|
}}
|
||||||
|
poster={thumbnail.src}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
|
@ -45,11 +45,7 @@ export default function Page() {
|
|||||||
<H2 id="full-text">Creative Commons Attribution 4.0 International Public License</H2>
|
<H2 id="full-text">Creative Commons Attribution 4.0 International Public License</H2>
|
||||||
|
|
||||||
<p style={{ textAlign: "center", lineHeight: 0 }}>
|
<p style={{ textAlign: "center", lineHeight: 0 }}>
|
||||||
<Link
|
<Link href="https://creativecommons.org/licenses/by/4.0/" title="Creative Commons Attribution 4.0" plain>
|
||||||
href="https://creativecommons.org/licenses/by/4.0/"
|
|
||||||
title="Creative Commons Attribution 4.0"
|
|
||||||
underline={false}
|
|
||||||
>
|
|
||||||
<svg width="120" height="42">
|
<svg width="120" height="42">
|
||||||
<path d="M3.1.5l113.4.2c1.6 0 3-.2 3 3.2l-.1 37.3H.3V3.7C.3 2.1.4.5 3 .5z" fill="#aab2ab"></path>
|
<path d="M3.1.5l113.4.2c1.6 0 3-.2 3 3.2l-.1 37.3H.3V3.7C.3 2.1.4.5 3 .5z" fill="#aab2ab"></path>
|
||||||
<path d="M117.8 0H2.2C1 0 0 1 0 2.2v39.3c0 .3.2.5.5.5h119c.3 0 .5-.2.5-.5V2.2c0-1.2-1-2.2-2.2-2.2zM2.2 1h115.6c.6 0 1.2.6 1.2 1.2v27.3H36.2a17.8 17.8 0 01-31.1 0H1V2.2C1 1.6 1.5 1 2.1 1z"></path>
|
<path d="M117.8 0H2.2C1 0 0 1 0 2.2v39.3c0 .3.2.5.5.5h119c.3 0 .5-.2.5-.5V2.2c0-1.2-1-2.2-2.2-2.2zM2.2 1h115.6c.6 0 1.2.6 1.2 1.2v27.3H36.2a17.8 17.8 0 01-31.1 0H1V2.2C1 1.6 1.5 1 2.1 1z"></path>
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import config from "../lib/config";
|
import config from "../lib/config";
|
||||||
import { chrome512Png, chrome192Png, maskable512Png, maskable192Png } from "../lib/config/favicons";
|
|
||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
|
import chrome512Png from "../public/static/favicons/android-chrome-512x512.png";
|
||||||
|
import chrome192Png from "../public/static/favicons/android-chrome-192x192.png";
|
||||||
|
import maskable512Png from "../public/static/favicons/maskable-512x512.png";
|
||||||
|
import maskable192Png from "../public/static/favicons/maskable-192x192.png";
|
||||||
|
|
||||||
const manifest = (): MetadataRoute.Manifest => {
|
const manifest = (): MetadataRoute.Manifest => {
|
||||||
return {
|
return {
|
||||||
name: config.siteName,
|
name: config.siteName,
|
||||||
short_name: config.siteDomain,
|
short_name: config.siteName,
|
||||||
description: config.longDescription,
|
description: config.longDescription,
|
||||||
lang: config.siteLocale,
|
lang: config.siteLocale,
|
||||||
icons: [
|
icons: [
|
||||||
|
@ -7,16 +7,16 @@
|
|||||||
color: var(--colors-medium);
|
color: var(--colors-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta .item {
|
.meta .metaItem {
|
||||||
margin-right: 1.6em;
|
margin-right: 1.6em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta .link {
|
.meta .metaLink {
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta .icon {
|
.meta .metaIcon {
|
||||||
display: inline;
|
display: inline;
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
@ -24,23 +24,23 @@
|
|||||||
margin-right: 0.6em;
|
margin-right: 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta .tags {
|
.meta .metaTags {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta .tag {
|
.meta .metaTag {
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-right: 0.75em;
|
margin-right: 0.75em;
|
||||||
}
|
}
|
||||||
.meta .tag:before {
|
.meta .metaTag:before {
|
||||||
content: "\0023"; /* cosmetically hashtagify tags */
|
content: "\0023"; /* cosmetically hashtagify tags */
|
||||||
padding-right: 0.125em;
|
padding-right: 0.125em;
|
||||||
color: var(--colors-light);
|
color: var(--colors-light);
|
||||||
}
|
}
|
||||||
.meta .tag:last-of-type {
|
.meta .metaTag:last-of-type {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import * as runtime from "react/jsx-runtime";
|
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { evaluate } from "@mdx-js/mdx";
|
|
||||||
import Content from "../../../components/Content";
|
import Content from "../../../components/Content";
|
||||||
import Link from "../../../components/Link";
|
import Link from "../../../components/Link";
|
||||||
import Time from "../../../components/Time";
|
import Time from "../../../components/Time";
|
||||||
@ -9,7 +7,6 @@ import Comments from "../../../components/Comments";
|
|||||||
import Loading from "../../../components/Loading";
|
import Loading from "../../../components/Loading";
|
||||||
import HitCounter from "./counter";
|
import HitCounter from "./counter";
|
||||||
import { getPostSlugs, getPostData } from "../../../lib/helpers/posts";
|
import { getPostSlugs, getPostData } from "../../../lib/helpers/posts";
|
||||||
import * as mdxComponents from "../../../lib/helpers/mdx-components";
|
|
||||||
import { metadata as defaultMetadata } from "../../layout";
|
import { metadata as defaultMetadata } from "../../layout";
|
||||||
import config from "../../../lib/config";
|
import config from "../../../lib/config";
|
||||||
import { FiCalendar, FiTag, FiEdit, FiEye } from "react-icons/fi";
|
import { FiCalendar, FiTag, FiEdit, FiEye } from "react-icons/fi";
|
||||||
@ -62,7 +59,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
|
|||||||
|
|
||||||
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { frontMatter, markdown } = await getPostData(slug);
|
const { frontMatter } = await getPostData(slug);
|
||||||
|
|
||||||
const jsonLd: WithContext<Article> = {
|
const jsonLd: WithContext<Article> = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
@ -76,49 +73,30 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|||||||
author: {
|
author: {
|
||||||
"@type": "Person",
|
"@type": "Person",
|
||||||
name: config.authorName,
|
name: config.authorName,
|
||||||
url: defaultMetadata.metadataBase?.href || `https://${config.siteDomain}`,
|
url: config.baseUrl,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const { remarkGfm, remarkSmartypants, rehypeSlug, rehypeUnwrapImages, rehypePrism } = await import(
|
const { default: MDXContent } = await import(`../../../notes/${slug}.mdx`);
|
||||||
"../../../lib/helpers/remark-rehype-plugins"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { default: MDXContent } = await evaluate(markdown, {
|
|
||||||
...runtime,
|
|
||||||
remarkPlugins: [
|
|
||||||
[remarkGfm, { singleTilde: false }],
|
|
||||||
[
|
|
||||||
remarkSmartypants,
|
|
||||||
{
|
|
||||||
quotes: true,
|
|
||||||
dashes: "oldschool",
|
|
||||||
backticks: false,
|
|
||||||
ellipses: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
rehypePlugins: [rehypeSlug, rehypeUnwrapImages, [rehypePrism, { ignoreMissing: true }]],
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||||
|
|
||||||
<div className={styles.meta}>
|
<div className={styles.meta}>
|
||||||
<div className={styles.item}>
|
<div className={styles.metaItem}>
|
||||||
<Link href={`/notes/${frontMatter.slug}` as Route} underline={false} className={styles.link}>
|
<Link href={`/notes/${frontMatter.slug}` as Route} plain className={styles.metaLink}>
|
||||||
<FiCalendar className={styles.icon} />
|
<FiCalendar className={styles.metaIcon} />
|
||||||
<Time date={frontMatter.date} format="MMMM D, YYYY" />
|
<Time date={frontMatter.date} format="MMMM D, YYYY" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{frontMatter.tags && (
|
{frontMatter.tags && (
|
||||||
<div className={styles.item}>
|
<div className={styles.metaItem}>
|
||||||
<FiTag className={styles.icon} />
|
<FiTag className={styles.metaIcon} />
|
||||||
<span className={styles.tags}>
|
<span className={styles.metaTags}>
|
||||||
{frontMatter.tags.map((tag) => (
|
{frontMatter.tags.map((tag) => (
|
||||||
<span key={tag} title={tag} className={styles.tag} aria-label={`Tagged with ${tag}`}>
|
<span key={tag} title={tag} className={styles.metaTag} aria-label={`Tagged with ${tag}`}>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@ -126,14 +104,14 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.item}>
|
<div className={styles.metaItem}>
|
||||||
<Link
|
<Link
|
||||||
href={`https://github.com/${config.githubRepo}/blob/main/notes/${frontMatter.slug}.mdx`}
|
href={`https://github.com/${config.githubRepo}/blob/main/notes/${frontMatter.slug}.mdx`}
|
||||||
title={`Edit "${frontMatter.title}" on GitHub`}
|
title={`Edit "${frontMatter.title}" on GitHub`}
|
||||||
underline={false}
|
plain
|
||||||
className={styles.link}
|
className={styles.metaLink}
|
||||||
>
|
>
|
||||||
<FiEdit className={styles.icon} />
|
<FiEdit className={styles.metaIcon} />
|
||||||
<span>Improve This Post</span>
|
<span>Improve This Post</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@ -142,14 +120,14 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|||||||
{process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && (
|
{process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && (
|
||||||
<ErrorBoundary fallback={null}>
|
<ErrorBoundary fallback={null}>
|
||||||
<div
|
<div
|
||||||
className={styles.item}
|
className={styles.metaItem}
|
||||||
style={{
|
style={{
|
||||||
// fix potential layout shift when number of hits loads
|
// fix potential layout shift when number of hits loads
|
||||||
minWidth: "7em",
|
minWidth: "7em",
|
||||||
marginRight: 0,
|
marginRight: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FiEye className={styles.icon} />
|
<FiEye className={styles.metaIcon} />
|
||||||
<Suspense fallback={<Loading boxes={3} width={20} />}>
|
<Suspense fallback={<Loading boxes={3} width={20} />}>
|
||||||
<HitCounter slug={`notes/${frontMatter.slug}`} />
|
<HitCounter slug={`notes/${frontMatter.slug}`} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@ -162,16 +140,13 @@ export default async function Page({ params }: { params: Promise<{ slug: string
|
|||||||
<Link
|
<Link
|
||||||
href={`/notes/${frontMatter.slug}` as Route}
|
href={`/notes/${frontMatter.slug}` as Route}
|
||||||
dangerouslySetInnerHTML={{ __html: frontMatter.htmlTitle || frontMatter.title }}
|
dangerouslySetInnerHTML={{ __html: frontMatter.htmlTitle || frontMatter.title }}
|
||||||
underline={false}
|
plain
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
/>
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<Content>
|
<Content>
|
||||||
<MDXContent
|
<MDXContent />
|
||||||
// @ts-ignore
|
|
||||||
components={{ ...mdxComponents }}
|
|
||||||
/>
|
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
{!frontMatter.noComments && (
|
{!frontMatter.noComments && (
|
||||||
|
@ -251,7 +251,7 @@ export default function Page() {
|
|||||||
title="My Public Key"
|
title="My Public Key"
|
||||||
lightColor="#757575"
|
lightColor="#757575"
|
||||||
darkColor="#959595"
|
darkColor="#959595"
|
||||||
underline={false}
|
plain
|
||||||
openInNewTab
|
openInNewTab
|
||||||
>
|
>
|
||||||
<GoLock
|
<GoLock
|
||||||
|
@ -33,7 +33,3 @@
|
|||||||
.screenshot:last-of-type {
|
.screenshot:last-of-type {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
|
||||||
margin: 1em auto;
|
|
||||||
}
|
|
||||||
|
@ -52,13 +52,7 @@ export default async function Page() {
|
|||||||
fontFamily: `${ComicNeue.style.fontFamily}, var(--fonts-sans)`,
|
fontFamily: `${ComicNeue.style.fontFamily}, var(--fonts-sans)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Figure
|
<Figure src={img_wayback} alt="Timeline of this website's past." priority className={styles.screenshot}>
|
||||||
src={img_wayback}
|
|
||||||
href="https://web.archive.org/web/20010501000000*/jakejarvis.com"
|
|
||||||
alt="Timeline of this website's past."
|
|
||||||
priority
|
|
||||||
className={styles.screenshot}
|
|
||||||
>
|
|
||||||
...the{" "}
|
...the{" "}
|
||||||
<Link href="https://web.archive.org/web/20010501000000*/jakejarvis.com">Cringey Chronicles™</Link> of
|
<Link href="https://web.archive.org/web/20010501000000*/jakejarvis.com">Cringey Chronicles™</Link> of
|
||||||
this website's past.
|
this website's past.
|
||||||
@ -181,36 +175,21 @@ export default async function Page() {
|
|||||||
|
|
||||||
<HorizontalRule className={styles.divider} />
|
<HorizontalRule className={styles.divider} />
|
||||||
|
|
||||||
<Figure
|
<Figure src={img_2012_09} alt="September 2012" className={styles.screenshot}>
|
||||||
src={img_2012_09}
|
|
||||||
href="https://focused-knuth-7bc10d.netlify.app/"
|
|
||||||
alt="September 2012"
|
|
||||||
className={styles.screenshot}
|
|
||||||
>
|
|
||||||
<Link href="https://focused-knuth-7bc10d.netlify.app/">September 2012</Link> (
|
<Link href="https://focused-knuth-7bc10d.netlify.app/">September 2012</Link> (
|
||||||
<Link href="https://github.com/jakejarvis/jarv.is/tree/v1">view source</Link>)
|
<Link href="https://github.com/jakejarvis/jarv.is/tree/v1">view source</Link>)
|
||||||
</Figure>
|
</Figure>
|
||||||
|
|
||||||
<HorizontalRule className={styles.divider} />
|
<HorizontalRule className={styles.divider} />
|
||||||
|
|
||||||
<Figure
|
<Figure src={img_2018_04} alt="April 2018" className={styles.screenshot}>
|
||||||
src={img_2018_04}
|
|
||||||
href="https://hungry-mayer-40e790.netlify.app/"
|
|
||||||
alt="April 2018"
|
|
||||||
className={styles.screenshot}
|
|
||||||
>
|
|
||||||
<Link href="https://hungry-mayer-40e790.netlify.app/">April 2018</Link> (
|
<Link href="https://hungry-mayer-40e790.netlify.app/">April 2018</Link> (
|
||||||
<Link href="https://github.com/jakejarvis/jarv.is/tree/v2">view source</Link>)
|
<Link href="https://github.com/jakejarvis/jarv.is/tree/v2">view source</Link>)
|
||||||
</Figure>
|
</Figure>
|
||||||
|
|
||||||
<HorizontalRule className={styles.divider} />
|
<HorizontalRule className={styles.divider} />
|
||||||
|
|
||||||
<Figure
|
<Figure src={img_2020_03} alt="March 2020" className={styles.screenshot}>
|
||||||
src={img_2020_03}
|
|
||||||
href="https://quiet-truffle-92842d.netlify.app/"
|
|
||||||
alt="March 2020"
|
|
||||||
className={styles.screenshot}
|
|
||||||
>
|
|
||||||
<Link href="https://quiet-truffle-92842d.netlify.app/">March 2020</Link> (
|
<Link href="https://quiet-truffle-92842d.netlify.app/">March 2020</Link> (
|
||||||
<Link href="https://github.com/jakejarvis/jarv.is-hugo">view source</Link>)
|
<Link href="https://github.com/jakejarvis/jarv.is-hugo">view source</Link>)
|
||||||
</Figure>
|
</Figure>
|
||||||
|
@ -143,7 +143,7 @@ export default async function Page() {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
href={`${repo.url}/stargazers`}
|
href={`${repo.url}/stargazers`}
|
||||||
title={`${commaNumber(repo.stars)} ${repo.stars === 1 ? "star" : "stars"}`}
|
title={`${commaNumber(repo.stars)} ${repo.stars === 1 ? "star" : "stars"}`}
|
||||||
underline={false}
|
plain
|
||||||
className={styles.metaLink}
|
className={styles.metaLink}
|
||||||
>
|
>
|
||||||
<GoStar className={styles.metaIcon} />
|
<GoStar className={styles.metaIcon} />
|
||||||
@ -158,7 +158,7 @@ export default async function Page() {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
href={`${repo.url}/network/members`}
|
href={`${repo.url}/network/members`}
|
||||||
title={`${commaNumber(repo.forks)} ${repo.forks === 1 ? "fork" : "forks"}`}
|
title={`${commaNumber(repo.forks)} ${repo.forks === 1 ? "fork" : "forks"}`}
|
||||||
underline={false}
|
plain
|
||||||
className={styles.metaLink}
|
className={styles.metaLink}
|
||||||
>
|
>
|
||||||
<GoRepoForked className={styles.metaIcon} />
|
<GoRepoForked className={styles.metaIcon} />
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import config from "../lib/config";
|
import config from "../lib/config";
|
||||||
import { metadata } from "./layout";
|
|
||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@ -73,7 +72,7 @@ const robots = (): MetadataRoute.Robots => {
|
|||||||
disallow: "/",
|
disallow: "/",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sitemap: new URL("sitemap.xml", metadata.metadataBase?.href || `https://${config.siteDomain}`).href,
|
sitemap: `${config.baseUrl}/sitemap.xml`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import glob from "fast-glob";
|
import glob from "fast-glob";
|
||||||
import { getAllPosts } from "../lib/helpers/posts";
|
import { getAllPosts } from "../lib/helpers/posts";
|
||||||
import { metadata } from "./layout";
|
import config from "../lib/config";
|
||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@ -11,21 +11,19 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
|
|||||||
const routes: MetadataRoute.Sitemap = [
|
const routes: MetadataRoute.Sitemap = [
|
||||||
{
|
{
|
||||||
// homepage
|
// homepage
|
||||||
url: "/",
|
url: config.baseUrl,
|
||||||
priority: 1.0,
|
priority: 1.0,
|
||||||
changeFrequency: "weekly",
|
changeFrequency: "weekly",
|
||||||
lastModified: new Date(process.env.RELEASE_DATE || Date.now()), // timestamp frozen when a new build is deployed
|
lastModified: new Date(process.env.RELEASE_DATE || Date.now()), // timestamp frozen when a new build is deployed
|
||||||
},
|
},
|
||||||
{
|
{ url: `${config.baseUrl}/tweets/` },
|
||||||
url: "/tweets/",
|
|
||||||
changeFrequency: "yearly",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// add each directory in the app folder as a route (excluding special routes)
|
// add each directory in the app folder as a route (excluding special routes)
|
||||||
|
const appDir = path.resolve(process.cwd(), "app");
|
||||||
(
|
(
|
||||||
await glob("*", {
|
await glob("*", {
|
||||||
cwd: path.join(process.cwd(), "app"),
|
cwd: appDir,
|
||||||
deep: 0,
|
deep: 0,
|
||||||
onlyDirectories: true,
|
onlyDirectories: true,
|
||||||
markDirectories: true,
|
markDirectories: true,
|
||||||
@ -39,7 +37,7 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
|
|||||||
).forEach((route) => {
|
).forEach((route) => {
|
||||||
routes.push({
|
routes.push({
|
||||||
// make all URLs absolute
|
// make all URLs absolute
|
||||||
url: route,
|
url: `${config.baseUrl}/${route}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,9 +49,6 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// make all URLs absolute
|
|
||||||
routes.forEach((page) => (page.url = new URL(page.url, metadata.metadataBase || "").href));
|
|
||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
--sizes-maxLayoutWidth: 865px;
|
--sizes-maxLayoutWidth: 865px;
|
||||||
--radii-corner: 0.6rem;
|
--radii-corner: 0.6rem;
|
||||||
--transitions-fade: 0.25s ease;
|
--transitions-fade: 0.25s ease;
|
||||||
--transitions-linkHover: 0.2s ease-in-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
|
@ -40,7 +40,9 @@ export default function Page() {
|
|||||||
❤️
|
❤️
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Image src={desktopImg} href={desktopImg.src as Route} alt="My mess of a desktop." priority />
|
<Link href={desktopImg.src as Route} openInNewTab>
|
||||||
|
<Image src={desktopImg} alt="My mess of a desktop." priority />
|
||||||
|
</Link>
|
||||||
|
|
||||||
<H2 id="hardware">
|
<H2 id="hardware">
|
||||||
<span style={{ marginRight: "0.45em" }}>🚘</span>
|
<span style={{ marginRight: "0.45em" }}>🚘</span>
|
||||||
|
@ -40,7 +40,7 @@ export default async function Page() {
|
|||||||
<span style={{ color: "var(--colors-codeKeyword)" }}>google</span>:
|
<span style={{ color: "var(--colors-codeKeyword)" }}>google</span>:
|
||||||
<span style={{ color: "var(--colors-codeAttribute)" }}>~</span>${" "}
|
<span style={{ color: "var(--colors-codeAttribute)" }}>~</span>${" "}
|
||||||
<span style={{ color: "var(--colors-codeLiteral)" }}>mv</span> /root
|
<span style={{ color: "var(--colors-codeLiteral)" }}>mv</span> /root
|
||||||
<Link href="https://killedbygoogle.com/" style={{ color: "inherit" }} underline={false}>
|
<Link href="https://killedbygoogle.com/" style={{ color: "inherit" }} plain>
|
||||||
/stable_products_that_people_rely_on/
|
/stable_products_that_people_rely_on/
|
||||||
</Link>
|
</Link>
|
||||||
googledomains.zip /tmp/
|
googledomains.zip /tmp/
|
||||||
@ -62,7 +62,7 @@ export default async function Page() {
|
|||||||
<span style={{ color: "var(--colors-codeAttribute)" }}>@monthly</span>
|
<span style={{ color: "var(--colors-codeAttribute)" }}>@monthly</span>
|
||||||
<span style={{ color: "var(--colors-codeLiteral)" }}>rm</span>{" "}
|
<span style={{ color: "var(--colors-codeLiteral)" }}>rm</span>{" "}
|
||||||
<span style={{ color: "var(--colors-codeVariable )" }}>-f</span> /tmp/
|
<span style={{ color: "var(--colors-codeVariable )" }}>-f</span> /tmp/
|
||||||
<Link href="https://fuckyougoogle.zip/" style={{ color: "inherit" }} underline={false}>
|
<Link href="https://fuckyougoogle.zip/" style={{ color: "inherit" }} plain>
|
||||||
*.zip
|
*.zip
|
||||||
</Link>
|
</Link>
|
||||||
<br />
|
<br />
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
.footer {
|
.footer {
|
||||||
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 1.25em 1.5em;
|
padding: 1.25em 1.5em;
|
||||||
border-top: 1px solid var(--colors-kindaLight);
|
border-top: 1px solid var(--colors-kindaLight);
|
||||||
|
@ -15,11 +15,11 @@ const Footer = ({ className, ...rest }: FooterProps) => {
|
|||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<div>
|
<div>
|
||||||
Content{" "}
|
Content{" "}
|
||||||
<Link href="/license" title={config.license} underline={false} className={styles.link}>
|
<Link href="/license" title={config.license} plain className={styles.link}>
|
||||||
licensed under {config.licenseAbbr}
|
licensed under {config.licenseAbbr}
|
||||||
</Link>
|
</Link>
|
||||||
,{" "}
|
,{" "}
|
||||||
<Link href="/previously" title="Previously on..." underline={false} className={styles.link}>
|
<Link href="/previously" title="Previously on..." plain className={styles.link}>
|
||||||
{config.copyrightYearStart}
|
{config.copyrightYearStart}
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
– {new Date(process.env.RELEASE_DATE || Date.now()).getUTCFullYear()}.
|
– {new Date(process.env.RELEASE_DATE || Date.now()).getUTCFullYear()}.
|
||||||
@ -35,7 +35,7 @@ const Footer = ({ className, ...rest }: FooterProps) => {
|
|||||||
href="https://nextjs.org/"
|
href="https://nextjs.org/"
|
||||||
title="Powered by Next.js"
|
title="Powered by Next.js"
|
||||||
aria-label="Next.js"
|
aria-label="Next.js"
|
||||||
underline={false}
|
plain
|
||||||
className={clsx(styles.link, styles.hover)}
|
className={clsx(styles.link, styles.hover)}
|
||||||
>
|
>
|
||||||
<SiNextdotjs className={styles.icon} />
|
<SiNextdotjs className={styles.icon} />
|
||||||
@ -44,7 +44,7 @@ const Footer = ({ className, ...rest }: FooterProps) => {
|
|||||||
<Link
|
<Link
|
||||||
href={`https://github.com/${config.githubRepo}`}
|
href={`https://github.com/${config.githubRepo}`}
|
||||||
title="View Source on GitHub"
|
title="View Source on GitHub"
|
||||||
underline={false}
|
plain
|
||||||
className={clsx(styles.link, styles.underline)}
|
className={clsx(styles.link, styles.underline)}
|
||||||
>
|
>
|
||||||
View source.
|
View source.
|
||||||
|
@ -7,7 +7,11 @@
|
|||||||
transition:
|
transition:
|
||||||
background var(--transitions-fade),
|
background var(--transitions-fade),
|
||||||
border var(--transitions-fade);
|
border var(--transitions-fade);
|
||||||
z-index: 9999px;
|
|
||||||
|
/* make sticky */
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
/* blurry glass-like background effect (except on firefox...?) */
|
/* blurry glass-like background effect (except on firefox...?) */
|
||||||
backdrop-filter: saturate(180%) blur(5px);
|
backdrop-filter: saturate(180%) blur(5px);
|
||||||
|
@ -15,7 +15,7 @@ const Header = ({ className, ...rest }: HeaderProps) => {
|
|||||||
return (
|
return (
|
||||||
<header className={clsx(styles.header, className)} {...rest}>
|
<header className={clsx(styles.header, className)} {...rest}>
|
||||||
<nav className={styles.nav}>
|
<nav className={styles.nav}>
|
||||||
<Link href="/" rel="author" title={config.authorName} underline={false} className={styles.selfieLink}>
|
<Link href="/" rel="author" title={config.authorName} plain className={styles.selfieLink}>
|
||||||
<Image
|
<Image
|
||||||
src={selfieJpg}
|
src={selfieJpg}
|
||||||
alt={`Photo of ${config.authorName}`}
|
alt={`Photo of ${config.authorName}`}
|
||||||
|
@ -9,14 +9,7 @@ export type HeadingAnchorProps = Omit<ComponentPropsWithoutRef<typeof Link>, "hr
|
|||||||
|
|
||||||
const HeadingAnchor = ({ id, title, ...rest }: HeadingAnchorProps) => {
|
const HeadingAnchor = ({ id, title, ...rest }: HeadingAnchorProps) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link href={`#${id}`} title={`Jump to "${title}"`} aria-hidden plain style={{ lineHeight: 1 }} {...rest}>
|
||||||
href={`#${id}`}
|
|
||||||
title={`Jump to "${title}"`}
|
|
||||||
aria-hidden
|
|
||||||
underline={false}
|
|
||||||
style={{ lineHeight: 1 }}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<FiLink size="0.8em" />
|
<FiLink size="0.8em" />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -1,70 +1,32 @@
|
|||||||
import NextImage from "next/image";
|
import NextImage from "next/image";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Link, { LinkProps } from "../Link";
|
|
||||||
import type { ComponentPropsWithoutRef } from "react";
|
import type { ComponentPropsWithoutRef } from "react";
|
||||||
import type { ImageProps as NextImageProps, StaticImageData } from "next/image";
|
import type { StaticImageData } from "next/image";
|
||||||
|
|
||||||
import styles from "./Image.module.css";
|
import styles from "./Image.module.css";
|
||||||
|
|
||||||
const DEFAULT_QUALITY = 60;
|
const MAX_WIDTH = 865;
|
||||||
const DEFAULT_WIDTH = 865;
|
|
||||||
|
|
||||||
export type ImageProps = ComponentPropsWithoutRef<typeof NextImage> &
|
export type ImageProps = ComponentPropsWithoutRef<typeof NextImage> & {
|
||||||
Partial<Pick<LinkProps, "href">> & {
|
inline?: boolean; // don't wrap everything in a `<div>` block
|
||||||
inline?: boolean; // don't wrap everything in a `<div>` block
|
};
|
||||||
|
|
||||||
|
const Image = ({ src, height, width, quality, inline, className, ...rest }: ImageProps) => {
|
||||||
|
const constrainWidth = (width?: number | `${number}`) => {
|
||||||
|
if (!width) return MAX_WIDTH;
|
||||||
|
|
||||||
|
return Math.min(typeof width === "string" ? parseInt(width, 10) : width, MAX_WIDTH);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Image = ({
|
const imageProps = {
|
||||||
src,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
quality = DEFAULT_QUALITY,
|
|
||||||
placeholder,
|
|
||||||
href,
|
|
||||||
inline,
|
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: ImageProps) => {
|
|
||||||
const imageProps: NextImageProps = {
|
|
||||||
// strip "px" from dimensions: https://stackoverflow.com/a/4860249/1438024
|
|
||||||
width: typeof width === "string" ? Number.parseInt(width, 10) : width,
|
|
||||||
height: typeof height === "string" ? Number.parseInt(height, 10) : height,
|
|
||||||
quality,
|
|
||||||
src,
|
src,
|
||||||
placeholder,
|
height,
|
||||||
|
width: constrainWidth(width || (src as StaticImageData).width),
|
||||||
|
quality: quality || 75,
|
||||||
...rest,
|
...rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof src === "object" && (src as StaticImageData).src !== undefined) {
|
const StyledImageWithProps = <NextImage className={clsx(styles.image, className)} {...imageProps} />;
|
||||||
const staticImg = src as StaticImageData;
|
|
||||||
|
|
||||||
// all data for statically imported images is extracted from the object itself.
|
|
||||||
imageProps.src = staticImg;
|
|
||||||
// set image width to max layout width; height is calculated automatically via aspect ratio:
|
|
||||||
// https://github.com/vercel/next.js/pull/40278
|
|
||||||
imageProps.width = staticImg.width > DEFAULT_WIDTH ? DEFAULT_WIDTH : imageProps.width;
|
|
||||||
// default to blur placeholder while loading if it's been generated for us.
|
|
||||||
imageProps.placeholder = placeholder || (staticImg.blurDataURL !== undefined ? "blur" : "empty");
|
|
||||||
} else if (typeof src === "string") {
|
|
||||||
// regular path to a file was passed in, which makes explicit width and height required.
|
|
||||||
// https://nextjs.org/docs/api-reference/next/future/image#width
|
|
||||||
if (!(width && height)) {
|
|
||||||
throw new Error("'width' and 'height' are required for non-statically imported images.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// optionally prepending src with "/public" makes images resolve properly in GitHub markdown previews, etc.
|
|
||||||
imageProps.src = src.replace(/^\/public/g, "");
|
|
||||||
} else {
|
|
||||||
throw new TypeError("'src' should be a string or a valid StaticImageData object.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledImageWithProps = href ? (
|
|
||||||
<Link href={href} underline={false}>
|
|
||||||
<NextImage className={clsx(styles.image, className)} {...imageProps} />
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<NextImage className={clsx(styles.image, className)} {...imageProps} />
|
|
||||||
);
|
|
||||||
|
|
||||||
return inline ? StyledImageWithProps : <div className={styles.block}>{StyledImageWithProps}</div>;
|
return inline ? StyledImageWithProps : <div className={styles.block}>{StyledImageWithProps}</div>;
|
||||||
};
|
};
|
||||||
|
@ -14,13 +14,3 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stickyHeader {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexedFooter {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
@ -14,14 +14,14 @@ const Layout = ({ className, children, ...rest }: LayoutProps) => {
|
|||||||
<SkipToContentLink />
|
<SkipToContentLink />
|
||||||
|
|
||||||
<div className={clsx(styles.flex, className)} {...rest}>
|
<div className={clsx(styles.flex, className)} {...rest}>
|
||||||
<Header className={styles.stickyHeader} />
|
<Header />
|
||||||
|
|
||||||
<main className={styles.default}>
|
<main className={styles.default}>
|
||||||
<SkipToContentTarget />
|
<SkipToContentTarget />
|
||||||
<div className={styles.container}>{children}</div>
|
<div className={styles.container}>{children}</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Footer className={styles.flexedFooter} />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
.link {
|
.link {
|
||||||
color: var(--colors-link);
|
color: var(--colors-link);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
|
||||||
|
|
||||||
.link.underline {
|
/* fancy underline */
|
||||||
background-image: linear-gradient(var(--colors-linkUnderline), var(--colors-linkUnderline));
|
background-image: linear-gradient(var(--colors-linkUnderline), var(--colors-linkUnderline));
|
||||||
background-position: 0% 100%;
|
background-position: 0% 100%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 0% 2px;
|
background-size: 0% 2px;
|
||||||
|
transition: background-size 0.2s ease-in-out;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link.underline:hover,
|
.link:hover,
|
||||||
.link.underline:focus-visible {
|
.link:focus-visible {
|
||||||
background-size: 100% 2px;
|
background-size: 100% 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
.link.plain {
|
||||||
.link.underline {
|
background: none !important;
|
||||||
transition: background-size var(--transitions-linkHover);
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.link {
|
||||||
|
transition: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,20 @@
|
|||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import objStr from "obj-str";
|
import objStr from "obj-str";
|
||||||
|
import config from "../../lib/config";
|
||||||
import type { ComponentPropsWithoutRef } from "react";
|
import type { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
import styles from "./Link.module.css";
|
import styles from "./Link.module.css";
|
||||||
|
|
||||||
export type LinkProps = ComponentPropsWithoutRef<typeof NextLink> & {
|
export type LinkProps = ComponentPropsWithoutRef<typeof NextLink> & {
|
||||||
underline?: boolean;
|
plain?: boolean; // disable fancy text-decoration effect
|
||||||
openInNewTab?: boolean;
|
openInNewTab?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Link = ({
|
const Link = ({ href, rel, target, prefetch = false, plain, openInNewTab, className, ...rest }: LinkProps) => {
|
||||||
href,
|
|
||||||
rel,
|
|
||||||
target,
|
|
||||||
prefetch = false,
|
|
||||||
underline = true,
|
|
||||||
openInNewTab,
|
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: LinkProps) => {
|
|
||||||
// This component auto-detects whether or not this link should open in the same window (the default for internal
|
// This component auto-detects whether or not this link should open in the same window (the default for internal
|
||||||
// links) or a new tab (the default for external links). Defaults can be overridden with `openInNewTab={true}`.
|
// links) or a new tab (the default for external links). Defaults can be overridden with `openInNewTab={true}`.
|
||||||
const isExternal =
|
const isExternal = typeof href === "string" && !(["/", "#"].includes(href[0]) || href.startsWith(config.baseUrl));
|
||||||
typeof href === "string" &&
|
|
||||||
!(
|
|
||||||
["/", "#"].includes(href[0]) ||
|
|
||||||
(process.env.NEXT_PUBLIC_BASE_URL && href.startsWith(process.env.NEXT_PUBLIC_BASE_URL))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (openInNewTab || isExternal) {
|
if (openInNewTab || isExternal) {
|
||||||
return (
|
return (
|
||||||
@ -40,7 +27,7 @@ const Link = ({
|
|||||||
noreferrer: isExternal, // don't add "noreferrer" if link isn't external, and only opening in a new tab
|
noreferrer: isExternal, // don't add "noreferrer" if link isn't external, and only opening in a new tab
|
||||||
})}
|
})}
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
className={clsx(styles.link, underline && styles.underline, className)}
|
className={clsx(styles.link, plain && styles.plain, className)}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -49,7 +36,7 @@ const Link = ({
|
|||||||
// If link is to an internal page, simply pass *everything* along as-is to next/link.
|
// If link is to an internal page, simply pass *everything* along as-is to next/link.
|
||||||
return (
|
return (
|
||||||
<NextLink
|
<NextLink
|
||||||
className={clsx(styles.link, underline && styles.underline, className)}
|
className={clsx(styles.link, plain && styles.plain, className)}
|
||||||
{...{ href, rel, target, prefetch, ...rest }}
|
{...{ href, rel, target, prefetch, ...rest }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -28,7 +28,7 @@ const Menu = ({ className, ...rest }: MenuProps) => {
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
<li className={styles.menuItem}>
|
<li className={styles.menuItem}>
|
||||||
<MenuItem Icon={ThemeToggle} />
|
<MenuItem icon={ThemeToggle} />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
@ -6,14 +6,16 @@ import type { IconType } from "react-icons";
|
|||||||
import styles from "./MenuItem.module.css";
|
import styles from "./MenuItem.module.css";
|
||||||
|
|
||||||
export type MenuItemProps = {
|
export type MenuItemProps = {
|
||||||
Icon?: IconType;
|
|
||||||
text?: string;
|
text?: string;
|
||||||
href?: Route;
|
href?: Route;
|
||||||
|
icon?: IconType;
|
||||||
current?: boolean;
|
current?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItem = ({ Icon, text, href, current, className }: MenuItemProps) => {
|
const MenuItem = ({ text, href, icon, current, className }: MenuItemProps) => {
|
||||||
|
const Icon = icon;
|
||||||
|
|
||||||
const item = (
|
const item = (
|
||||||
<>
|
<>
|
||||||
{Icon && <Icon className={styles.icon} />}
|
{Icon && <Icon className={styles.icon} />}
|
||||||
@ -28,7 +30,7 @@ const MenuItem = ({ Icon, text, href, current, className }: MenuItemProps) => {
|
|||||||
href={href}
|
href={href}
|
||||||
className={clsx(styles.link, current && styles.current, className)}
|
className={clsx(styles.link, current && styles.current, className)}
|
||||||
title={text}
|
title={text}
|
||||||
underline={false}
|
plain
|
||||||
aria-label={text}
|
aria-label={text}
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
|
@ -11,7 +11,7 @@ export type OctocatLinkProps = Omit<ComponentPropsWithoutRef<typeof Link>, "href
|
|||||||
|
|
||||||
const OctocatLink = ({ repo, className, ...rest }: OctocatLinkProps) => {
|
const OctocatLink = ({ repo, className, ...rest }: OctocatLinkProps) => {
|
||||||
return (
|
return (
|
||||||
<Link href={`https://github.com/${repo}`} underline={false} className={styles.octocatLink} {...rest}>
|
<Link href={`https://github.com/${repo}`} plain className={styles.octocatLink} {...rest}>
|
||||||
<SiGithub className={clsx(styles.octocat, className)} />
|
<SiGithub className={clsx(styles.octocat, className)} />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,7 @@ const PageTitle = ({ className, children, ...rest }: PageTitleProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<h1 className={clsx(styles.title, className)} {...rest}>
|
<h1 className={clsx(styles.title, className)} {...rest}>
|
||||||
<Link href={pathname as Route} underline={false} className={styles.link}>
|
<Link href={pathname as Route} plain className={styles.link}>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.tweet {
|
.tweet {
|
||||||
/* help with layout shift */
|
/* help with layout shift */
|
||||||
min-height: 300px;
|
min-height: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tweet :global(.react-tweet-theme) {
|
.tweet :global(.react-tweet-theme) {
|
||||||
|
@ -1,32 +1,47 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Tweet } from "react-tweet";
|
import { EmbeddedTweet, TweetSkeleton, TweetNotFound } from "react-tweet";
|
||||||
|
import { getTweet } from "react-tweet/api";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import type { ComponentPropsWithoutRef } from "react";
|
import type { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
import styles from "./TweetEmbed.module.css";
|
import styles from "./TweetEmbed.module.css";
|
||||||
|
|
||||||
export type TweetEmbedProps = ComponentPropsWithoutRef<typeof Tweet> & {
|
export type TweetEmbedProps = Omit<ComponentPropsWithoutRef<typeof EmbeddedTweet>, "tweet"> & {
|
||||||
id: string;
|
id: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TweetEmbed = ({ id, className, ...rest }: TweetEmbedProps) => {
|
const TweetEmbed = async ({ id, className, ...rest }: TweetEmbedProps) => {
|
||||||
return (
|
try {
|
||||||
<div className={clsx(styles.tweet, className)}>
|
const tweet = await getTweet(id);
|
||||||
<Tweet
|
|
||||||
key={`tweet-${id}`}
|
return (
|
||||||
id={id}
|
<div className={clsx(styles.tweet, className)}>
|
||||||
components={{
|
<Suspense fallback={<TweetSkeleton />}>
|
||||||
// https://react-tweet.vercel.app/twitter-theme/api-reference#custom-tweet-components
|
{tweet ? (
|
||||||
// eslint-disable-next-line jsx-a11y/alt-text
|
<EmbeddedTweet
|
||||||
AvatarImg: (props) => <Image {...props} />,
|
tweet={tweet}
|
||||||
// eslint-disable-next-line jsx-a11y/alt-text
|
components={{
|
||||||
MediaImg: (props) => <Image {...props} fill />,
|
// https://react-tweet.vercel.app/twitter-theme/api-reference#custom-tweet-components
|
||||||
}}
|
// eslint-disable-next-line jsx-a11y/alt-text
|
||||||
{...rest}
|
AvatarImg: (props) => <Image {...props} unoptimized />,
|
||||||
/>
|
// eslint-disable-next-line jsx-a11y/alt-text
|
||||||
</div>
|
MediaImg: (props) => <Image {...props} fill unoptimized />,
|
||||||
);
|
}}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TweetNotFound />
|
||||||
|
)}
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} catch (
|
||||||
|
error // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
) {
|
||||||
|
return <TweetNotFound />;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TweetEmbed;
|
export default TweetEmbed;
|
||||||
|
@ -10,14 +10,14 @@ export type VideoProps = Omit<Partial<ComponentPropsWithoutRef<"video">>, "src">
|
|||||||
mp4?: string;
|
mp4?: string;
|
||||||
// optional:
|
// optional:
|
||||||
vtt?: string;
|
vtt?: string;
|
||||||
image?: string;
|
|
||||||
};
|
};
|
||||||
|
poster?: string;
|
||||||
autoplay?: boolean;
|
autoplay?: boolean;
|
||||||
responsive?: boolean;
|
responsive?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Video = ({ src, autoplay = false, responsive = true, className, ...rest }: VideoProps) => {
|
const Video = ({ src, poster, autoplay = false, responsive = true, className, ...rest }: VideoProps) => {
|
||||||
if (!src || (!src.mp4 && !src.webm)) {
|
if (!src || (!src.mp4 && !src.webm)) {
|
||||||
throw new Error("'src' prop must include either 'mp4' or 'webm' URL.");
|
throw new Error("'src' prop must include either 'mp4' or 'webm' URL.");
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ const Video = ({ src, autoplay = false, responsive = true, className, ...rest }:
|
|||||||
playsInline={autoplay} // safari autoplay workaround
|
playsInline={autoplay} // safari autoplay workaround
|
||||||
loop={autoplay || undefined}
|
loop={autoplay || undefined}
|
||||||
muted={autoplay || undefined}
|
muted={autoplay || undefined}
|
||||||
poster={src.image}
|
poster={poster}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{src.webm && <source key={src.webm} src={src.webm} type="video/webm" />}
|
{src.webm && <source key={src.webm} src={src.webm} type="video/webm" />}
|
||||||
|
@ -74,7 +74,18 @@ export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ThemeContext.Provider value={providerValues}>{children}</ThemeContext.Provider>;
|
return (
|
||||||
|
<ThemeContext.Provider value={providerValues}>
|
||||||
|
<script
|
||||||
|
// unminified: https://gist.github.com/jakejarvis/79b0ec8506bc843023546d0d29861bf0
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `(()=>{try{const e=document.documentElement,t="undefined"!=typeof Storage?window.localStorage.getItem("theme"):null,a=(t&&"dark"===t)??window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";e.dataset.theme=a,e.style.colorScheme=a}catch(e){}})()`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// debugging help pls
|
// debugging help pls
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
// here we simply import some common image files and re-export them as StaticImageData.
|
|
||||||
|
|
||||||
// favicons
|
|
||||||
export { default as faviconIco } from "../../public/static/favicons/favicon.ico";
|
|
||||||
export { default as faviconPng } from "../../public/static/favicons/favicon.png";
|
|
||||||
export { default as appleTouchIconPng } from "../../public/static/favicons/apple-touch-icon.png";
|
|
||||||
export { default as chrome512Png } from "../../public/static/favicons/android-chrome-512x512.png";
|
|
||||||
export { default as chrome192Png } from "../../public/static/favicons/android-chrome-192x192.png";
|
|
||||||
export { default as maskable512Png } from "../../public/static/favicons/maskable-512x512.png";
|
|
||||||
export { default as maskable192Png } from "../../public/static/favicons/maskable-192x192.png";
|
|
||||||
|
|
||||||
// other
|
|
||||||
export { default as meJpg } from "../../public/static/images/me.jpg";
|
|
@ -3,8 +3,13 @@
|
|||||||
const config = {
|
const config = {
|
||||||
// Site info
|
// Site info
|
||||||
siteName: "Jake Jarvis",
|
siteName: "Jake Jarvis",
|
||||||
siteDomain: "jarv.is",
|
|
||||||
siteLocale: "en-US",
|
siteLocale: "en-US",
|
||||||
|
baseUrl:
|
||||||
|
process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL
|
||||||
|
? `https://${process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL}`
|
||||||
|
: process.env.NEXT_PUBLIC_VERCEL_ENV === "preview" && process.env.NEXT_PUBLIC_VERCEL_URL
|
||||||
|
? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
|
||||||
|
: `http://localhost:${process.env.PORT || 3000}`,
|
||||||
timeZone: "America/New_York", // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
|
timeZone: "America/New_York", // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
|
||||||
onionDomain: "http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion",
|
onionDomain: "http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion",
|
||||||
shortDescription: "Front-End Web Developer in Boston, MA",
|
shortDescription: "Front-End Web Developer in Boston, MA",
|
||||||
|
@ -3,23 +3,23 @@ import type { MenuItemProps } from "../../components/MenuItem";
|
|||||||
|
|
||||||
export const menuItems: MenuItemProps[] = [
|
export const menuItems: MenuItemProps[] = [
|
||||||
{
|
{
|
||||||
Icon: FiHome,
|
|
||||||
text: "Home",
|
text: "Home",
|
||||||
href: "/",
|
href: "/",
|
||||||
|
icon: FiHome,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: FiEdit3,
|
|
||||||
text: "Notes",
|
text: "Notes",
|
||||||
href: "/notes",
|
href: "/notes",
|
||||||
|
icon: FiEdit3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: FiCode,
|
|
||||||
text: "Projects",
|
text: "Projects",
|
||||||
href: "/projects",
|
href: "/projects",
|
||||||
|
icon: FiCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: FiMail,
|
|
||||||
text: "Contact",
|
text: "Contact",
|
||||||
href: "/contact",
|
href: "/contact",
|
||||||
|
icon: FiMail,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,28 +1,26 @@
|
|||||||
import { Feed } from "feed";
|
import { Feed } from "feed";
|
||||||
import { getAllPosts } from "./posts";
|
import { getAllPosts } from "./posts";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import { meJpg } from "../config/favicons";
|
|
||||||
import { metadata } from "../../app/layout";
|
import meJpg from "../../public/static/images/me.jpg";
|
||||||
|
|
||||||
export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Promise<string> => {
|
export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Promise<string> => {
|
||||||
const baseUrl = metadata.metadataBase?.href || `https://${config.siteDomain}/`;
|
|
||||||
|
|
||||||
// https://github.com/jpmonette/feed#example
|
// https://github.com/jpmonette/feed#example
|
||||||
const feed = new Feed({
|
const feed = new Feed({
|
||||||
id: baseUrl,
|
id: config.baseUrl,
|
||||||
link: baseUrl,
|
link: config.baseUrl,
|
||||||
title: config.siteName,
|
title: config.siteName,
|
||||||
description: config.longDescription,
|
description: config.longDescription,
|
||||||
copyright: config.licenseUrl,
|
copyright: config.licenseUrl,
|
||||||
updated: new Date(process.env.RELEASE_DATE || Date.now()),
|
updated: new Date(process.env.RELEASE_DATE || Date.now()),
|
||||||
image: new URL(meJpg.src, baseUrl).href,
|
image: `${config.baseUrl}${meJpg.src}`,
|
||||||
feedLinks: {
|
feedLinks: {
|
||||||
rss: new URL("feed.xml", baseUrl).href,
|
rss: `${config.baseUrl}/feed.xml`,
|
||||||
atom: new URL("feed.atom", baseUrl).href,
|
atom: `${config.baseUrl}/feed.atom`,
|
||||||
},
|
},
|
||||||
author: {
|
author: {
|
||||||
name: config.authorName,
|
name: config.authorName,
|
||||||
link: baseUrl,
|
link: config.baseUrl,
|
||||||
email: config.authorEmail,
|
email: config.authorEmail,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -38,7 +36,7 @@ export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Pro
|
|||||||
author: [
|
author: [
|
||||||
{
|
{
|
||||||
name: config.authorName,
|
name: config.authorName,
|
||||||
link: baseUrl,
|
link: config.baseUrl,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
date: new Date(post.date),
|
date: new Date(post.date),
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import dynamic from "next/dynamic";
|
|
||||||
|
|
||||||
// Bundle these components by default:
|
|
||||||
export { default as Image } from "../../components/Image";
|
|
||||||
export { default as Figure } from "../../components/Figure";
|
|
||||||
|
|
||||||
// These (mostly very small) components are direct replacements for HTML tags generated by remark:
|
|
||||||
export { default as a } from "../../components/Link";
|
|
||||||
export { default as code } from "../../components/Code";
|
|
||||||
export { default as blockquote } from "../../components/Blockquote";
|
|
||||||
export { default as hr } from "../../components/HorizontalRule";
|
|
||||||
export { H1 as h1, H2 as h2, H3 as h3, H4 as h4, H5 as h5, H6 as h6 } from "../../components/Heading";
|
|
||||||
export { UnorderedList as ul, OrderedList as ol, ListItem as li } from "../../components/List";
|
|
||||||
|
|
||||||
// ...and 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 IFrame = dynamic(() => import("../../components/IFrame"));
|
|
||||||
export const Video = dynamic(() => import("../../components/Video"));
|
|
||||||
export const YouTube = dynamic(() => import("../../components/YouTubeEmbed"));
|
|
||||||
export const Tweet = dynamic(() => import("../../components/TweetEmbed"));
|
|
||||||
export const Gist = dynamic(() => import("../../components/GistEmbed"));
|
|
||||||
export const CodePen = dynamic(() => import("../../components/CodePenEmbed"));
|
|
||||||
|
|
||||||
// One-offs for specific posts:
|
|
||||||
export const OctocatLink = dynamic(() => import("../../components/OctocatLink"));
|
|
@ -5,7 +5,7 @@ import pMap from "p-map";
|
|||||||
import pMemoize from "p-memoize";
|
import pMemoize from "p-memoize";
|
||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
import { formatDate } from "./format-date";
|
import { formatDate } from "./format-date";
|
||||||
import { metadata as defaultMetadata } from "../../app/layout";
|
import config from "../config";
|
||||||
|
|
||||||
// path to directory with .mdx files, relative to project root
|
// path to directory with .mdx files, relative to project root
|
||||||
const POSTS_DIR = "notes";
|
const POSTS_DIR = "notes";
|
||||||
@ -71,8 +71,8 @@ export const getPostData = async (
|
|||||||
htmlTitle,
|
htmlTitle,
|
||||||
slug,
|
slug,
|
||||||
date: formatDate(data.date), // validate/normalize the date string provided from front matter
|
date: formatDate(data.date), // validate/normalize the date string provided from front matter
|
||||||
permalink: new URL(`/${POSTS_DIR}/${slug}/`, defaultMetadata.metadataBase || "").href,
|
permalink: `${config.baseUrl}/${POSTS_DIR}/${slug}/`,
|
||||||
image: data.image ? new URL(data.image, defaultMetadata.metadataBase || "").href : undefined,
|
image: data.image ? `${config.baseUrl}${data.image}` : undefined,
|
||||||
},
|
},
|
||||||
markdown: content,
|
markdown: content,
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,11 @@ export { default as rehypeSanitize } from "rehype-sanitize";
|
|||||||
export { default as rehypeSlug } from "rehype-slug";
|
export { default as rehypeSlug } from "rehype-slug";
|
||||||
export { default as rehypeStringify } from "rehype-stringify";
|
export { default as rehypeStringify } from "rehype-stringify";
|
||||||
export { default as rehypeUnwrapImages } from "rehype-unwrap-images";
|
export { default as rehypeUnwrapImages } from "rehype-unwrap-images";
|
||||||
|
export { default as remarkFrontmatter } from "remark-frontmatter";
|
||||||
export { default as remarkGfm } from "remark-gfm";
|
export { default as remarkGfm } from "remark-gfm";
|
||||||
export { default as remarkParse } from "remark-parse";
|
export { default as remarkParse } from "remark-parse";
|
||||||
export { default as remarkRehype } from "remark-rehype";
|
export { default as remarkRehype } from "remark-rehype";
|
||||||
export { default as remarkSmartypants } from "remark-smartypants";
|
export { default as remarkSmartypants } from "remark-smartypants";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
export { default as rehypeMdxImportMedia } from "rehype-mdx-import-media";
|
||||||
|
49
mdx-components.ts
Normal file
49
mdx-components.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import type { MDXComponents } from "mdx/types";
|
||||||
|
|
||||||
|
import Image from "./components/Image";
|
||||||
|
import Figure from "./components/Figure";
|
||||||
|
import Link from "./components/Link";
|
||||||
|
import Code from "./components/Code";
|
||||||
|
import Blockquote from "./components/Blockquote";
|
||||||
|
import HorizontalRule from "./components/HorizontalRule";
|
||||||
|
import * as Heading from "./components/Heading";
|
||||||
|
import * as List from "./components/List";
|
||||||
|
import Tweet from "./components/TweetEmbed";
|
||||||
|
import IFrame from "./components/IFrame";
|
||||||
|
import Video from "./components/Video";
|
||||||
|
import YouTube from "./components/YouTubeEmbed";
|
||||||
|
import Gist from "./components/GistEmbed";
|
||||||
|
import CodePen from "./components/CodePenEmbed";
|
||||||
|
import OctocatLink from "./components/OctocatLink";
|
||||||
|
|
||||||
|
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||||
|
return {
|
||||||
|
...components,
|
||||||
|
|
||||||
|
// direct replacements for HTML tags generated by remark:
|
||||||
|
img: Image,
|
||||||
|
a: Link,
|
||||||
|
code: Code,
|
||||||
|
blockquote: Blockquote,
|
||||||
|
hr: HorizontalRule,
|
||||||
|
h1: Heading.H1,
|
||||||
|
h2: Heading.H2,
|
||||||
|
h3: Heading.H3,
|
||||||
|
h4: Heading.H4,
|
||||||
|
h5: Heading.H5,
|
||||||
|
h6: Heading.H6,
|
||||||
|
ul: List.UnorderedList,
|
||||||
|
ol: List.OrderedList,
|
||||||
|
li: List.ListItem,
|
||||||
|
|
||||||
|
// non-native components:
|
||||||
|
Figure,
|
||||||
|
IFrame,
|
||||||
|
Video,
|
||||||
|
YouTube,
|
||||||
|
Tweet,
|
||||||
|
Gist,
|
||||||
|
CodePen,
|
||||||
|
OctocatLink,
|
||||||
|
};
|
||||||
|
}
|
@ -1,15 +1,18 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
import siteConfig from "./lib/config";
|
import config from "./lib/config";
|
||||||
|
|
||||||
export function middleware(request: NextRequest) {
|
export function middleware(request: NextRequest) {
|
||||||
const response = NextResponse.next();
|
const response = NextResponse.next();
|
||||||
|
|
||||||
// https://gitweb.torproject.org/tor-browser-spec.git/tree/proposals/100-onion-location-header.txt
|
// https://gitweb.torproject.org/tor-browser-spec.git/tree/proposals/100-onion-location-header.txt
|
||||||
if (siteConfig.onionDomain) {
|
if (config.onionDomain) {
|
||||||
response.headers.set("Onion-Location", new URL(request.nextUrl.pathname, siteConfig.onionDomain).href);
|
response.headers.set("Onion-Location", `${config.onionDomain}${request.nextUrl.pathname}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// debugging 🥛
|
||||||
|
response.headers.set("x-got-milk", "2%");
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
import createMDX from "@next/mdx";
|
||||||
|
import createBundleAnalyzer from "@next/bundle-analyzer";
|
||||||
|
import * as mdxPlugins from "./lib/helpers/remark-rehype-plugins";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
@ -10,8 +13,8 @@ const nextConfig: NextConfig = {
|
|||||||
// pages using getServerSideProps will return the current(ish) time instead, which is usually not what we want.
|
// pages using getServerSideProps will return the current(ish) time instead, which is usually not what we want.
|
||||||
RELEASE_DATE: new Date().toISOString(),
|
RELEASE_DATE: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
|
pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
|
||||||
images: {
|
images: {
|
||||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
|
|
||||||
formats: ["image/avif", "image/webp"],
|
formats: ["image/avif", "image/webp"],
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{ protocol: "https", hostname: "pbs.twimg.com" },
|
{ protocol: "https", hostname: "pbs.twimg.com" },
|
||||||
@ -20,8 +23,6 @@ const nextConfig: NextConfig = {
|
|||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
ppr: "incremental", // https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering#using-partial-prerendering
|
ppr: "incremental", // https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering#using-partial-prerendering
|
||||||
cssChunking: true,
|
|
||||||
typedRoutes: true,
|
|
||||||
largePageDataBytes: 512 * 1000, // raise getStaticProps limit to 512 kB since compiled MDX will exceed the default.
|
largePageDataBytes: 512 * 1000, // raise getStaticProps limit to 512 kB since compiled MDX will exceed the default.
|
||||||
},
|
},
|
||||||
eslint: {
|
eslint: {
|
||||||
@ -29,16 +30,6 @@ const nextConfig: NextConfig = {
|
|||||||
dirs: ["app", "components", "contexts", "hooks", "lib"],
|
dirs: ["app", "components", "contexts", "hooks", "lib"],
|
||||||
},
|
},
|
||||||
headers: async () => [
|
headers: async () => [
|
||||||
{
|
|
||||||
source: "/:path(.*)",
|
|
||||||
headers: [
|
|
||||||
{
|
|
||||||
// 🥛
|
|
||||||
key: "x-got-milk",
|
|
||||||
value: "2%",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
source: "/pubkey.asc",
|
source: "/pubkey.asc",
|
||||||
headers: [
|
headers: [
|
||||||
@ -138,9 +129,32 @@ const nextConfig: NextConfig = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
const withBundleAnalyzer = createBundleAnalyzer({
|
||||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
|
||||||
enabled: process.env.ANALYZE === "true",
|
enabled: process.env.ANALYZE === "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withBundleAnalyzer(nextConfig);
|
const withMDX = createMDX({
|
||||||
|
options: {
|
||||||
|
remarkPlugins: [
|
||||||
|
mdxPlugins.remarkFrontmatter,
|
||||||
|
[mdxPlugins.remarkGfm, { singleTilde: false }],
|
||||||
|
[
|
||||||
|
mdxPlugins.remarkSmartypants,
|
||||||
|
{
|
||||||
|
quotes: true,
|
||||||
|
dashes: "oldschool",
|
||||||
|
backticks: false,
|
||||||
|
ellipses: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
rehypePlugins: [
|
||||||
|
mdxPlugins.rehypeUnwrapImages,
|
||||||
|
mdxPlugins.rehypeSlug,
|
||||||
|
[mdxPlugins.rehypePrism, { ignoreMissing: true }],
|
||||||
|
mdxPlugins.rehypeMdxImportMedia,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withBundleAnalyzer(withMDX(nextConfig));
|
||||||
|
@ -13,13 +13,7 @@ image: "/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg"
|
|||||||
|
|
||||||
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.
|
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="/public/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg"
|
|
||||||
width="865"
|
|
||||||
height="433"
|
|
||||||
alt="Sad Bernie"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
BERN has two features: one called "Friend-to-Friend" (described as "add everyone in your network") and another called "Community Canvassing" (described as "talk to people around you every day, e.g. on the bus, outside the grocery store, at a park"). Both of these involve phoning home to Sanders HQ with the following information on anybody you know or meet:
|
BERN has two features: one called "Friend-to-Friend" (described as "add everyone in your network") and another called "Community Canvassing" (described as "talk to people around you every day, e.g. on the bus, outside the grocery store, at a park"). Both of these involve phoning home to Sanders HQ with the following information on anybody you know or meet:
|
||||||
|
|
||||||
@ -50,77 +44,41 @@ Here's one of the instructional videos provided internally to volunteers:
|
|||||||
src={{
|
src={{
|
||||||
webm: "/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.webm",
|
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",
|
mp4: "/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.mp4",
|
||||||
image: "/static/images/notes/bernie-sanders-bern-app-data/poster-friend-to-friend.png",
|
|
||||||
}}
|
}}
|
||||||
|
poster="/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:
|
...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="/public/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-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.
|
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.
|
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="/public/static/images/notes/bernie-sanders-bern-app-data/json-response.jpg" width="865" height="369">
|

|
||||||
BERN's API response in Chrome DevTools
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
Others have noted that web-based organizing tools like BERN have been used by campaigns at all levels since President Obama's well-oiled, futuristic machine in 2007. This is also true, and I'm a big fan of the trend they started.
|
Others have noted that web-based organizing tools like BERN have been used by campaigns at all levels since President Obama's well-oiled, futuristic machine in 2007. This is also true, and I'm a big fan of the trend they started.
|
||||||
|
|
||||||
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.)
|
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](/static/images/notes/bernie-sanders-bern-app-data/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
|

|
||||||
width="750"
|
|
||||||
height="447"
|
|
||||||
href="/static/images/notes/bernie-sanders-bern-app-data/sanders-campaign-audit.pdf"
|
|
||||||
>
|
|
||||||
[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.
|
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
|

|
||||||
width="600"
|
|
||||||
height="301"
|
|
||||||
href="https://www.reddit.com/r/SandersForPresident/comments/bi15la/new_get_the_official_bernie_sanders_2020_app_bern/elxi85m/"
|
|
||||||
>
|
|
||||||
[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>
|
|
||||||
|
|
||||||
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.
|
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="/public/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-1.png"
|
|
||||||
width="700"
|
|
||||||
height="451"
|
|
||||||
alt="Webinar Slide 1"
|
|
||||||
/>
|
|
||||||
|
|
||||||
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.
|
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="/public/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-2.png"
|
|
||||||
width="700"
|
|
||||||
height="451"
|
|
||||||
alt="Webinar Slide 2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
The [Privacy Policy link](https://berniesanders.com/privacy-policy/) at the bottom of the app links to a generic policy that looks like it's been copied from a default Wix website. There's no mention of the BERN app, no details of how they explicitly use our information, and no sign of an opt-out procedure.
|
The [Privacy Policy link](https://berniesanders.com/privacy-policy/) at the bottom of the app links to a generic policy that looks like it's been copied from a default Wix website. There's no mention of the BERN app, no details of how they explicitly use our information, and no sign of an opt-out procedure.
|
||||||
|
|
||||||
|
@ -13,13 +13,7 @@ image: "/static/images/notes/cloudflare-dns-archive-is-blocked/archive-is.png"
|
|||||||
|
|
||||||
**tl;dr:** No. Quite the opposite, actually — [Archive.is](https://archive.is/)'s owner is intentionally blocking 1.1.1.1 users.
|
**tl;dr:** No. Quite the opposite, actually — [Archive.is](https://archive.is/)'s owner is intentionally blocking 1.1.1.1 users.
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/cloudflare-dns-archive-is-blocked/archive-is.png"
|
|
||||||
width="865"
|
|
||||||
height="180"
|
|
||||||
alt="Archive.today screenshot"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
A [recent post on Hacker News](https://news.ycombinator.com/item?id=19828317) pointed out something I've noticed myself over the past year — the [Archive.is](https://archive.is/) website archiving tool (aka [Archive.today](https://archive.today/) and a few other TLDs) appears unresponsive when I'm on my home network, where I use Cloudflare's fantastic public DNS service, [1.1.1.1](https://1.1.1.1/). I didn't connect the two variables until I read this post, where somebody noticed that the Archive.is domain resolves for [Google's 8.8.8.8](https://developers.google.com/speed/public-dns/) DNS, but not 1.1.1.1. An interesting and timeless debate on [privacy versus convenience](https://www.adweek.com/digital/why-consumers-are-increasingly-willing-to-trade-privacy-for-convenience/) ensued.
|
A [recent post on Hacker News](https://news.ycombinator.com/item?id=19828317) pointed out something I've noticed myself over the past year — the [Archive.is](https://archive.is/) website archiving tool (aka [Archive.today](https://archive.today/) and a few other TLDs) appears unresponsive when I'm on my home network, where I use Cloudflare's fantastic public DNS service, [1.1.1.1](https://1.1.1.1/). I didn't connect the two variables until I read this post, where somebody noticed that the Archive.is domain resolves for [Google's 8.8.8.8](https://developers.google.com/speed/public-dns/) DNS, but not 1.1.1.1. An interesting and timeless debate on [privacy versus convenience](https://www.adweek.com/digital/why-consumers-are-increasingly-willing-to-trade-privacy-for-convenience/) ensued.
|
||||||
|
|
||||||
|
@ -12,13 +12,7 @@ tags:
|
|||||||
image: "/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png"
|
image: "/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png"
|
|
||||||
width="320"
|
|
||||||
height="284"
|
|
||||||
alt="Terminal.app on macOS"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
You may have noticed the recent trend of techies [posting their "dotfiles" on GitHub](https://github.com/topics/dotfiles) for the world to see. These usually contain shortcuts compatible with Bash terminals to automate convoluted commands that, I'll admit, I needed to Google every single time.
|
You may have noticed the recent trend of techies [posting their "dotfiles" on GitHub](https://github.com/topics/dotfiles) for the world to see. These usually contain shortcuts compatible with Bash terminals to automate convoluted commands that, I'll admit, I needed to Google every single time.
|
||||||
|
|
||||||
|
@ -21,13 +21,7 @@ Now that Americans are _finally_ starting to get tested for the coronavirus, inf
|
|||||||
|
|
||||||
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.
|
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="/public/static/images/notes/coronavirus-open-source/covidtracking.png"
|
|
||||||
width="680"
|
|
||||||
height="328"
|
|
||||||
alt="The COVID Tracking Project"
|
|
||||||
href="https://covidtracking.com/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [#findthemasks](https://findthemasks.com/) <OctocatLink repo="r-pop/findthemasks" />
|
## [#findthemasks](https://findthemasks.com/) <OctocatLink repo="r-pop/findthemasks" />
|
||||||
|
|
||||||
@ -35,13 +29,7 @@ This one might be my favorite, simply because of its laser-like focus on solving
|
|||||||
|
|
||||||
_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.
|
_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="/public/static/images/notes/coronavirus-open-source/findthemasks.png"
|
|
||||||
width="600"
|
|
||||||
height="295"
|
|
||||||
alt="#findthemasks"
|
|
||||||
href="https://findthemasks.com/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [#StayTheFuckHome](https://staythefuckhome.com/) <OctocatLink repo="flore2003/staythefuckhome" />
|
## [#StayTheFuckHome](https://staythefuckhome.com/) <OctocatLink repo="flore2003/staythefuckhome" />
|
||||||
|
|
||||||
@ -49,73 +37,37 @@ I figured I'd throw in this cheeky website broadcasting a simple but serious mes
|
|||||||
|
|
||||||
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!_** 😉
|
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="/public/static/images/notes/coronavirus-open-source/staythefuckhome.png"
|
|
||||||
width="600"
|
|
||||||
height="215"
|
|
||||||
alt="#StayTheFuckHome"
|
|
||||||
href="https://staythefuckhome.com/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [COVID-19 Dashboards](https://covid19dashboards.com/) <OctocatLink 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).
|
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="/public/static/images/notes/coronavirus-open-source/covid19dashboards.png"
|
|
||||||
width="580"
|
|
||||||
height="442"
|
|
||||||
alt="COVID-19 Dashboards"
|
|
||||||
href="https://covid19dashboards.com/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [CoronaTracker](https://coronatracker.samabox.com/) <OctocatLink 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.
|
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="/public/static/images/notes/coronavirus-open-source/coronatracker.png"
|
|
||||||
width="865"
|
|
||||||
height="417"
|
|
||||||
alt="CoronaTracker"
|
|
||||||
href="https://coronatracker.samabox.com/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [Staying Home Club](https://stayinghome.club/) <OctocatLink 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.
|
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="/public/static/images/notes/coronavirus-open-source/stayinghome.png"
|
|
||||||
width="600"
|
|
||||||
height="137"
|
|
||||||
alt="Staying Home Club"
|
|
||||||
href="https://stayinghome.club/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [Nextstrain for nCoV](https://nextstrain.org/ncov) <OctocatLink 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.
|
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="/public/static/images/notes/coronavirus-open-source/nextstrain.png"
|
|
||||||
width="865"
|
|
||||||
height="345"
|
|
||||||
alt="Nextstrain for nCOV"
|
|
||||||
href="https://nextstrain.org/ncov"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [Johns Hopkins 2019-nCoV Data](https://systems.jhu.edu/research/public-health/ncov/) <OctocatLink 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.
|
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="/public/static/images/notes/coronavirus-open-source/hopkins.png"
|
|
||||||
width="865"
|
|
||||||
height="426"
|
|
||||||
alt="Johns Hopkins 2019-nCoV Data"
|
|
||||||
href="https://systems.jhu.edu/research/public-health/ncov/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [COVID-19 Scenarios](https://neherlab.org/covid19/) <OctocatLink repo="neherlab/covid19_scenarios" />
|
## [COVID-19 Scenarios](https://neherlab.org/covid19/) <OctocatLink repo="neherlab/covid19_scenarios" />
|
||||||
|
|
||||||
@ -123,25 +75,13 @@ COVID-19 Scenarios will probably hit everyone in a different way, depending on y
|
|||||||
|
|
||||||
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!
|
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="/public/static/images/notes/coronavirus-open-source/scenarios.png"
|
|
||||||
width="740"
|
|
||||||
height="433"
|
|
||||||
alt="COVID-19 Scenarios"
|
|
||||||
href="https://neherlab.org/covid19/"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [Corona Data Scraper](https://coronadatascraper.com/#home) <OctocatLink 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.
|
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="/public/static/images/notes/coronavirus-open-source/coronadatascraper.png"
|
|
||||||
width="750"
|
|
||||||
height="358"
|
|
||||||
alt="Corona Data Scraper"
|
|
||||||
href="https://coronadatascraper.com/#home"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## [Folding@home](https://foldingathome.org/covid19/) <OctocatLink repo="FoldingAtHome/coronavirus" />
|
## [Folding@home](https://foldingathome.org/covid19/) <OctocatLink repo="FoldingAtHome/coronavirus" />
|
||||||
|
|
||||||
@ -155,8 +95,8 @@ You can [download their software here](https://foldingathome.org/start-folding/)
|
|||||||
src={{
|
src={{
|
||||||
webm: "/static/images/notes/coronavirus-open-source/folding.webm",
|
webm: "/static/images/notes/coronavirus-open-source/folding.webm",
|
||||||
mp4: "/static/images/notes/coronavirus-open-source/folding.mp4",
|
mp4: "/static/images/notes/coronavirus-open-source/folding.mp4",
|
||||||
image: "/static/images/notes/coronavirus-open-source/folding-thumb.png",
|
|
||||||
}}
|
}}
|
||||||
|
poster="/static/images/notes/coronavirus-open-source/folding-thumb.png"
|
||||||
autoplay
|
autoplay
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -164,12 +104,6 @@ You can [download their software here](https://foldingathome.org/start-folding/)
|
|||||||
|
|
||||||
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.
|
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="/public/static/images/notes/coronavirus-open-source/tracker-api.png"
|
|
||||||
width="712"
|
|
||||||
height="371"
|
|
||||||
alt="Coronavirus Tracker API"
|
|
||||||
href="https://coronavirus-tracker-api.herokuapp.com/v2/locations"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Stay safe (and [home](https://staythefuckhome.com/ "One last time...")), friends! ❤️
|
### Stay safe (and [home](https://staythefuckhome.com/ "One last time...")), friends! ❤️
|
||||||
|
@ -13,9 +13,7 @@ image: "/static/images/notes/dropping-dropbox/email.png"
|
|||||||
|
|
||||||
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.
|
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="/public/static/images/notes/dropping-dropbox/email.png" width="504" height="223" priority={true}>
|

|
||||||
Deleting 401,907 files from Dropbox... 😬
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
## Five strikes, you're out...
|
## Five strikes, you're out...
|
||||||
|
|
||||||
@ -34,29 +32,19 @@ Decisions made by the top folks at Dropbox gave me an increasingly sour taste in
|
|||||||
src={{
|
src={{
|
||||||
webm: "/static/images/notes/dropping-dropbox/cancel.webm",
|
webm: "/static/images/notes/dropping-dropbox/cancel.webm",
|
||||||
mp4: "/static/images/notes/dropping-dropbox/cancel.mp4",
|
mp4: "/static/images/notes/dropping-dropbox/cancel.mp4",
|
||||||
image: "/static/images/notes/dropping-dropbox/cancel.png",
|
|
||||||
}}
|
}}
|
||||||
|
poster="/static/images/notes/dropping-dropbox/cancel.png"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
## Seeking an alternative...
|
## Seeking an alternative...
|
||||||
|
|
||||||
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.
|
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="/public/static/images/notes/dropping-dropbox/icloud-storage.png"
|
|
||||||
width="865"
|
|
||||||
height="137"
|
|
||||||
alt="iCloud Drive storage"
|
|
||||||
/>
|
|
||||||
|
|
||||||
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.
|
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="/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).
|
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).
|
||||||
|
|
||||||
|
@ -15,16 +15,8 @@ A **subdomain takeover** occurs when a subdomain (like _example_.jarv.is) points
|
|||||||
|
|
||||||
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.
|
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
|

|
||||||
width="620"
|
|
||||||
height="347"
|
|
||||||
href="https://hackerone.com/hacktivity?querystring=subdomain%20takeover"
|
|
||||||
priority
|
|
||||||
>
|
|
||||||
[Huge rewards for subdomain takeovers on
|
|
||||||
HackerOne!](https://hackerone.com/hacktivity?querystring=subdomain%20takeover)
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
For a deep dive on the implications of takeovers, which can be a pretty serious vector of attack for malicious actors to obtain information from users of the targeted company, [Patrik Hudak](https://twitter.com/0xpatrik) wrote a [great post here](https://0xpatrik.com/subdomain-takeover/). Definitely take some time to skim through it and come back here when you're ready to hunt for a potential takeover yourself.
|
For a deep dive on the implications of takeovers, which can be a pretty serious vector of attack for malicious actors to obtain information from users of the targeted company, [Patrik Hudak](https://twitter.com/0xpatrik) wrote a [great post here](https://0xpatrik.com/subdomain-takeover/). Definitely take some time to skim through it and come back here when you're ready to hunt for a potential takeover yourself.
|
||||||
|
|
||||||
|
@ -11,21 +11,13 @@ tags:
|
|||||||
image: "/static/images/notes/github-actions/actions-flow.png"
|
image: "/static/images/notes/github-actions/actions-flow.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/github-actions/actions-flow.png"
|
|
||||||
width="780"
|
|
||||||
height="322"
|
|
||||||
alt="Example workflow for a GitHub Action"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
Since being accepted into the beta for [GitHub Actions](https://github.com/features/actions) a few months ago, I've found a new side hobby of whipping up new (and ideally creative) actions for anybody to add to their CI pipeline. Actions are modular steps that interact with a GitHub repository and can be coded with [Docker](https://github.com/actions/hello-world-docker-action) or [JavaScript/Node](https://github.com/actions/hello-world-javascript-action) — and either way, they can be as [simple](https://github.com/jakejarvis/wait-action) or as [complex](https://github.com/jakejarvis/lighthouse-action) as you want. But in both cases, they're incredibly fun to make and the results always scratch my itch for instant gratification.
|
Since being accepted into the beta for [GitHub Actions](https://github.com/features/actions) a few months ago, I've found a new side hobby of whipping up new (and ideally creative) actions for anybody to add to their CI pipeline. Actions are modular steps that interact with a GitHub repository and can be coded with [Docker](https://github.com/actions/hello-world-docker-action) or [JavaScript/Node](https://github.com/actions/hello-world-javascript-action) — and either way, they can be as [simple](https://github.com/jakejarvis/wait-action) or as [complex](https://github.com/jakejarvis/lighthouse-action) as you want. But in both cases, they're incredibly fun to make and the results always scratch my itch for instant gratification.
|
||||||
|
|
||||||
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.
|
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="/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>
|
|
||||||
|
|
||||||
It's also been a fantastic avenue to dip my feet into the collaborative nature of GitHub and the open-source community. I've made some small apps in the past but these are the first projects where I'm regularly receiving new issues to help out with and impressive pull requests to merge. It's a great feeling!
|
It's also been a fantastic avenue to dip my feet into the collaborative nature of GitHub and the open-source community. I've made some small apps in the past but these are the first projects where I'm regularly receiving new issues to help out with and impressive pull requests to merge. It's a great feeling!
|
||||||
|
|
||||||
@ -57,12 +49,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)).
|
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="/public/static/images/notes/github-actions/hugo-logs.png"
|
|
||||||
width="865"
|
|
||||||
height="418"
|
|
||||||
alt="Build logs for my Hugo fork"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Then another workflow, which [lives in this website's repository](https://github.com/jakejarvis/jarv.is/blob/master/.github/workflows/gh-pages.yml), pulls that Docker image, builds the Hugo site, and pushes it to GitHub Pages. All astoundingly fast. All for free.
|
Then another workflow, which [lives in this website's repository](https://github.com/jakejarvis/jarv.is/blob/master/.github/workflows/gh-pages.yml), pulls that Docker image, builds the Hugo site, and pushes it to GitHub Pages. All astoundingly fast. All for free.
|
||||||
|
|
||||||
|
@ -12,18 +12,14 @@ tags:
|
|||||||
image: "/static/images/notes/github-rename-master/github-default.png"
|
image: "/static/images/notes/github-rename-master/github-default.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/github-rename-master/blm-topic.png"
|
|
||||||
width="865"
|
|
||||||
height="162"
|
|
||||||
alt="Black lives matter."
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
In the midst of this year's long-overdue support of the [**Black Lives Matter**](https://blacklivesmatters.carrd.co/) movement and calls to action in the US and around the world, a [new spotlight](https://mail.gnome.org/archives/desktop-devel-list/2019-May/msg00066.html) has been placed on unchecked invocations of racially charged language in the computer science world, no matter how big or small — like the long-standing and, until recently, widely accepted terms ["master" and "slave"](https://tools.ietf.org/id/draft-knodel-terminology-00.html#master-slave) as an oppressive metaphor for ownership/importance.
|
In the midst of this year's long-overdue support of the [**Black Lives Matter**](https://blacklivesmatters.carrd.co/) movement and calls to action in the US and around the world, a [new spotlight](https://mail.gnome.org/archives/desktop-devel-list/2019-May/msg00066.html) has been placed on unchecked invocations of racially charged language in the computer science world, no matter how big or small — like the long-standing and, until recently, widely accepted terms ["master" and "slave"](https://tools.ietf.org/id/draft-knodel-terminology-00.html#master-slave) as an oppressive metaphor for ownership/importance.
|
||||||
|
|
||||||
When somebody pointed out the negative connotations of Git projects being created with a branch named `master` by default, and the possibility of this making minorities feel even more unwelcome in an industry already [lacking diversity](https://www.informationisbeautiful.net/visualizations/diversity-in-tech/), GitHub CEO [Nat Friedman](https://github.com/nat) quietly [announced a plan](https://twitter.com/natfriedman/status/1271253144442253312) to change this on Twitter (ignore the replies for your sanity).
|
When somebody pointed out the negative connotations of Git projects being created with a branch named `master` by default, and the possibility of this making minorities feel even more unwelcome in an industry already [lacking diversity](https://www.informationisbeautiful.net/visualizations/diversity-in-tech/), GitHub CEO [Nat Friedman](https://github.com/nat) quietly [announced a plan](https://twitter.com/natfriedman/status/1271253144442253312) to change this on Twitter (ignore the replies for your sanity).
|
||||||
|
|
||||||
|
<Tweet id="1271253144442253312" />
|
||||||
|
|
||||||
I think many people misunderstood this tweet to mean GitHub will forcefully rename the `master` branch of all existing projects, which would break _millions_ of programmers' workflows. If anything, it's more likely a name such as `main` will replace `master` as **the default when creating a new repository**, but that change hasn't been made yet. [GitLab is also discussing](https://gitlab.com/gitlab-org/gitlab/-/issues/221164) a similar switch to `main` as the default name. (Ideally, these changes would be made in tandem with the actual Git codebase, too. [~~But this doesn't seem likely.~~](https://lore.kernel.org/git/CAOAHyQwyXC1Z3v7BZAC+Bq6JBaM7FvBenA-1fcqeDV==apdWDg@mail.gmail.com/t/))
|
I think many people misunderstood this tweet to mean GitHub will forcefully rename the `master` branch of all existing projects, which would break _millions_ of programmers' workflows. If anything, it's more likely a name such as `main` will replace `master` as **the default when creating a new repository**, but that change hasn't been made yet. [GitLab is also discussing](https://gitlab.com/gitlab-org/gitlab/-/issues/221164) a similar switch to `main` as the default name. (Ideally, these changes would be made in tandem with the actual Git codebase, too. [~~But this doesn't seem likely.~~](https://lore.kernel.org/git/CAOAHyQwyXC1Z3v7BZAC+Bq6JBaM7FvBenA-1fcqeDV==apdWDg@mail.gmail.com/t/))
|
||||||
|
|
||||||
> **Update:** GitHub has [published more details about their plan](https://github.com/github/renaming) to move from `master` to `main` and it will indeed be voluntary and configurable. To my surprise, the Git maintainers have [also agreed to add](https://sfconservancy.org/news/2020/jun/23/gitbranchname/) a `init.defaultBranch` setting to customize the default branch for new repositories in Git 2.28.
|
> **Update:** GitHub has [published more details about their plan](https://github.com/github/renaming) to move from `master` to `main` and it will indeed be voluntary and configurable. To my surprise, the Git maintainers have [also agreed to add](https://sfconservancy.org/news/2020/jun/23/gitbranchname/) a `init.defaultBranch` setting to customize the default branch for new repositories in Git 2.28.
|
||||||
@ -57,12 +53,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).
|
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="/public/static/images/notes/github-rename-master/github-default.png"
|
|
||||||
width="810"
|
|
||||||
height="405"
|
|
||||||
alt="Changing the default branch of a GitHub repository"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### 4. Delete the old `master` branch on GitHub:
|
### 4. Delete the old `master` branch on GitHub:
|
||||||
|
|
||||||
@ -80,12 +71,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:
|
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="/public/static/images/notes/github-rename-master/netlify-deploy.png"
|
|
||||||
width="720"
|
|
||||||
height="460"
|
|
||||||
alt="Netlify auto-deployment branch setting"
|
|
||||||
/>
|
|
||||||
|
|
||||||
~~Unfortunately, GitHub won't redirect links containing `master` to the new branch (as of now), so look for any [github.com](https://github.com/) URLs as well.~~
|
~~Unfortunately, GitHub won't redirect links containing `master` to the new branch (as of now), so look for any [github.com](https://github.com/) URLs as well.~~
|
||||||
|
|
||||||
|
@ -12,17 +12,12 @@ tags:
|
|||||||
image: "/static/images/notes/how-to-backup-linux-server/apocalypse.png"
|
image: "/static/images/notes/how-to-backup-linux-server/apocalypse.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Figure
|

|
||||||
src="/public/static/images/notes/how-to-backup-linux-server/apocalypse.png"
|
|
||||||
width="865"
|
|
||||||
height="303"
|
|
||||||
priority={true}
|
|
||||||
>
|
|
||||||
**The Cloud-pocalypse:** Coming soon(er than you think) to a server near you.
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
Last month, the founder of [a small startup](https://raisup.com/) got quite a bit of [attention on Twitter](https://twitter.com/w3Nicolas/status/1134529316904153089) (and [Hacker News](https://news.ycombinator.com/item?id=20064169)) when he called out [DigitalOcean](https://www.digitalocean.com/) who, in his words, "killed" his company. Long story short: DigitalOcean's automated abuse system flagged the startup's account after they spun up about ten powerful droplets for some CPU-intensive jobs and deleted them shortly after — which is literally **the biggest selling point** of a "servers by the hour" company like DigitalOcean, by the way — and, after replying to the support ticket, an unsympathetic customer support agent [declined to reactivate](https://twitter.com/w3Nicolas/status/1134529372172509184) the account without explanation. [Nicolas](https://twitter.com/w3Nicolas) had no way of even accessing his data, turning the inconvenient but trivial task of migrating servers into a potentially fatal situation for his company.
|
Last month, the founder of [a small startup](https://raisup.com/) got quite a bit of [attention on Twitter](https://twitter.com/w3Nicolas/status/1134529316904153089) (and [Hacker News](https://news.ycombinator.com/item?id=20064169)) when he called out [DigitalOcean](https://www.digitalocean.com/) who, in his words, "killed" his company. Long story short: DigitalOcean's automated abuse system flagged the startup's account after they spun up about ten powerful droplets for some CPU-intensive jobs and deleted them shortly after — which is literally **the biggest selling point** of a "servers by the hour" company like DigitalOcean, by the way — and, after replying to the support ticket, an unsympathetic customer support agent [declined to reactivate](https://twitter.com/w3Nicolas/status/1134529372172509184) the account without explanation. [Nicolas](https://twitter.com/w3Nicolas) had no way of even accessing his data, turning the inconvenient but trivial task of migrating servers into a potentially fatal situation for his company.
|
||||||
|
|
||||||
|
<Tweet id="1134529316904153089" />
|
||||||
|
|
||||||
Predictably, there were [a](https://twitter.com/kolaente/status/1134897543643615238) [lot](https://twitter.com/hwkfr/status/1135164281731911681) [of](https://twitter.com/joestarstuff/status/1135406188114276352) [Monday](https://twitter.com/FearbySoftware/status/1134717875351052288)-[morning](https://twitter.com/mkozak/status/1134557954785587200) [quarterbacks](https://twitter.com/MichMich/status/1134547174447026181) who weighed in, scolding him for not having backups ([he did](https://twitter.com/w3Nicolas/status/1134529374676500482), but they were also stored on DigitalOcean) and not paying a boatload of non-existent money for expensive load balancers pointing to multiple cloud providers. Hindsight is always 20/20, of course, but if we're talking about a small side project that exploded into a full-fledged startup with Fortune 500 clients seemingly overnight, I _completely_ understand Nicolas' thought process. _"Let's just take advantage of cloud computing's #1 selling point: press a few buttons to make our servers [harder, better, faster, stronger](https://www.youtube.com/watch?v=x84m3YyO2oU) and get back to coding!"_
|
Predictably, there were [a](https://twitter.com/kolaente/status/1134897543643615238) [lot](https://twitter.com/hwkfr/status/1135164281731911681) [of](https://twitter.com/joestarstuff/status/1135406188114276352) [Monday](https://twitter.com/FearbySoftware/status/1134717875351052288)-[morning](https://twitter.com/mkozak/status/1134557954785587200) [quarterbacks](https://twitter.com/MichMich/status/1134547174447026181) who weighed in, scolding him for not having backups ([he did](https://twitter.com/w3Nicolas/status/1134529374676500482), but they were also stored on DigitalOcean) and not paying a boatload of non-existent money for expensive load balancers pointing to multiple cloud providers. Hindsight is always 20/20, of course, but if we're talking about a small side project that exploded into a full-fledged startup with Fortune 500 clients seemingly overnight, I _completely_ understand Nicolas' thought process. _"Let's just take advantage of cloud computing's #1 selling point: press a few buttons to make our servers [harder, better, faster, stronger](https://www.youtube.com/watch?v=x84m3YyO2oU) and get back to coding!"_
|
||||||
|
|
||||||
Most of the popular one-click server providers (including [DigitalOcean](https://www.digitalocean.com/docs/images/backups/overview/), as well as [Linode](https://www.linode.com/backups), [Vultr](https://www.vultr.com/docs/vps-automatic-backups), and [OVH](https://www.ovh.com/world/vps/backup-vps.xml)) provide their own backup offerings for an additional monthly cost (usually proportional to your plan). But as Nicolas learned the hard way, any amount of backups are just more eggs in the same basket if everything is under one account with one credit card on one provider.
|
Most of the popular one-click server providers (including [DigitalOcean](https://www.digitalocean.com/docs/images/backups/overview/), as well as [Linode](https://www.linode.com/backups), [Vultr](https://www.vultr.com/docs/vps-automatic-backups), and [OVH](https://www.ovh.com/world/vps/backup-vps.xml)) provide their own backup offerings for an additional monthly cost (usually proportional to your plan). But as Nicolas learned the hard way, any amount of backups are just more eggs in the same basket if everything is under one account with one credit card on one provider.
|
||||||
|
@ -34,12 +34,7 @@ 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 right. This will create an exact copy of the repository (and all of its branches) under your own username.
|
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 right. This will create an exact copy of the repository (and all of its branches) under your own username.
|
||||||
|
|
||||||
<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
|
## 2. Clone your new fork locally
|
||||||
|
|
||||||
@ -49,12 +44,7 @@ GitHub will automatically redirect you to the forked repository under your usern
|
|||||||
git clone git@github.com:jakejarvis/react-native.git
|
git clone git@github.com:jakejarvis/react-native.git
|
||||||
```
|
```
|
||||||
|
|
||||||
<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
|
## 3. Track the original repository as a remote of the fork
|
||||||
|
|
||||||
@ -102,19 +92,9 @@ 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.
|
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="/public/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-2.png"
|
|
||||||
width="700"
|
|
||||||
height="354"
|
|
||||||
alt="Step 7.2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -11,14 +11,7 @@ tags:
|
|||||||
image: "/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-2-04-04-pm.png"
|
image: "/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-2-04-04-pm.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Figure
|

|
||||||
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
|
|
||||||
>
|
|
||||||
`df -dh` = WTF
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
**[VMware Workstation](https://www.vmware.com/products/workstation-pro.html)** and **[Fusion](https://www.vmware.com/products/fusion.html)** normally work hard to minimize the size of virtual hard disks for optimizing the amount of storage needed on your host machine . On Windows virtual machines, [VMware has a "clean up" function](https://docs.vmware.com/en/VMware-Fusion/11/com.vmware.fusion.using.doc/GUID-6BB29187-F47F-41D1-AD92-1754036DACD9.html), which detects newly unused space and makes the size of the virtual hard disk smaller accordingly. You'll notice that even if you create a virtual machine with a capacity of 60 GB, for example, the actual size of the VMDK file will dynamically resize to fit the usage of the guest operating system. 60 GB is simply the maximum amount of storage allowed; if your guest operating system and its files amount to 20 GB, the VMDK file will simply be 20 GB.
|
**[VMware Workstation](https://www.vmware.com/products/workstation-pro.html)** and **[Fusion](https://www.vmware.com/products/fusion.html)** normally work hard to minimize the size of virtual hard disks for optimizing the amount of storage needed on your host machine . On Windows virtual machines, [VMware has a "clean up" function](https://docs.vmware.com/en/VMware-Fusion/11/com.vmware.fusion.using.doc/GUID-6BB29187-F47F-41D1-AD92-1754036DACD9.html), which detects newly unused space and makes the size of the virtual hard disk smaller accordingly. You'll notice that even if you create a virtual machine with a capacity of 60 GB, for example, the actual size of the VMDK file will dynamically resize to fit the usage of the guest operating system. 60 GB is simply the maximum amount of storage allowed; if your guest operating system and its files amount to 20 GB, the VMDK file will simply be 20 GB.
|
||||||
|
|
||||||
@ -78,12 +71,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.
|
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="/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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Now, we're going to run our final command in our **host** terminal, so open that up. Linux installations of VMware Workstation should have a simple map to the _vmware-vdiskmanager_ utility that you can run anywhere, but on macOS we need to tell it exactly where that's located: in the Applications folder, where Fusion is installed.
|
Now, we're going to run our final command in our **host** terminal, so open that up. Linux installations of VMware Workstation should have a simple map to the _vmware-vdiskmanager_ utility that you can run anywhere, but on macOS we need to tell it exactly where that's located: in the Applications folder, where Fusion is installed.
|
||||||
|
|
||||||
|
@ -11,14 +11,7 @@ tags:
|
|||||||
image: "/static/images/notes/hugo-to-nextjs/web-vitals.png"
|
image: "/static/images/notes/hugo-to-nextjs/web-vitals.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/hugo-to-nextjs/pr.png"
|
|
||||||
width="865"
|
|
||||||
height="47"
|
|
||||||
alt="Pull Request #711"
|
|
||||||
href="https://github.com/jakejarvis/jarv.is/pull/711/files"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
I'll say right off the bat: this website has a _loooong_ history of going overboard with its tech stack. I use this domain as a vehicle to [learn new things](https://www.jvt.me/talks/overengineering-your-personal-website/), and given [how frequently](https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/) the tides turn in the frontend development waters these days, things can (and did) get messy pretty quickly.
|
I'll say right off the bat: this website has a _loooong_ history of going overboard with its tech stack. I use this domain as a vehicle to [learn new things](https://www.jvt.me/talks/overengineering-your-personal-website/), and given [how frequently](https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/) the tides turn in the frontend development waters these days, things can (and did) get messy pretty quickly.
|
||||||
|
|
||||||
@ -37,12 +30,7 @@ Enter [**Next.js**](https://nextjs.org/), which caught my eye over other JS-cent
|
|||||||
- **Fewer `devDependencies` and consolidated build tooling.** I don't want to look at another Gulp task for as long as possible. Next's [built-in](https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config) Webpack and Babel support has come in clutch here.
|
- **Fewer `devDependencies` and consolidated build tooling.** I don't want to look at another Gulp task for as long as possible. Next's [built-in](https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config) Webpack and Babel support has come in clutch here.
|
||||||
- **Same (or better) [Lighthouse scores](https://web.dev/learn/#lighthouse).** The heavier load of JS has certainly affected performance a bit, but any modern browser can easily keep up with any React code I'll be using at this scale. And because of Next's static page generation (and [next-seo](https://github.com/garmeeh/next-seo)) nothing has changed in the realm of SEO.
|
- **Same (or better) [Lighthouse scores](https://web.dev/learn/#lighthouse).** The heavier load of JS has certainly affected performance a bit, but any modern browser can easily keep up with any React code I'll be using at this scale. And because of Next's static page generation (and [next-seo](https://github.com/garmeeh/next-seo)) nothing has changed in the realm of SEO.
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/hugo-to-nextjs/web-vitals.png"
|
|
||||||
width="865"
|
|
||||||
height="385"
|
|
||||||
alt="Vercel web vitals analytics"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## Things I still miss from Hugo
|
## Things I still miss from Hugo
|
||||||
|
|
||||||
|
@ -11,17 +11,7 @@ image: "/static/images/notes/millenial-with-hillary-clinton/24707394571_0818d4ab
|
|||||||
noComments: true
|
noComments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
<Figure
|

|
||||||
src="/public/static/images/notes/millenial-with-hillary-clinton/24707394571_0818d4ab83_o-1-copy.jpg"
|
|
||||||
width="865"
|
|
||||||
height="411"
|
|
||||||
href="https://www.flickr.com/photos/hillaryclinton/24707394571/"
|
|
||||||
priority
|
|
||||||
>
|
|
||||||
[Hillary for New Hampshire](https://medium.com/@HillaryForNH) Winter Fellows with [Hillary
|
|
||||||
Clinton](https://medium.com/@HillaryClinton) in Derry, NH ([February 3,
|
|
||||||
2016](https://www.flickr.com/photos/hillaryclinton/24707394571/))
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
## Keeping in mind the big picture...
|
## Keeping in mind the big picture...
|
||||||
|
|
||||||
@ -33,13 +23,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.
|
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="/public/static/images/notes/millenial-with-hillary-clinton/9e58a-1bvweqv_ve2_c1tw5-ihrhw.jpg"
|
|
||||||
width="400"
|
|
||||||
height="500"
|
|
||||||
>
|
|
||||||
The best birthday gift. 🎉
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
I had the incredible chance to meet Hillary the weekend before the New Hampshire primary. Her motorcade plowed through a quiet suburb in Manchester around noon and she hopped out to go knock on the doors of some lucky families. As neighbors started coming out of their houses to shake her hand, I couldn't restrain myself from at least trying to get close and wave hello. (By the way, it's amazing how casual the people in New Hampshire are about meeting presidential candidates.)
|
I had the incredible chance to meet Hillary the weekend before the New Hampshire primary. Her motorcade plowed through a quiet suburb in Manchester around noon and she hopped out to go knock on the doors of some lucky families. As neighbors started coming out of their houses to shake her hand, I couldn't restrain myself from at least trying to get close and wave hello. (By the way, it's amazing how casual the people in New Hampshire are about meeting presidential candidates.)
|
||||||
|
|
||||||
@ -64,6 +48,6 @@ As [Bill Maher](https://medium.com/u/cdc04a9799f6) (an avid Bernie supporter) [s
|
|||||||
webm: "/static/images/hillary/convention-720p.webm",
|
webm: "/static/images/hillary/convention-720p.webm",
|
||||||
mp4: "/static/images/hillary/convention-720p.mp4",
|
mp4: "/static/images/hillary/convention-720p.mp4",
|
||||||
vtt: "/static/images/hillary/subs.en.vtt",
|
vtt: "/static/images/hillary/subs.en.vtt",
|
||||||
image: "/static/images/hillary/thumb.png",
|
|
||||||
}}
|
}}
|
||||||
|
poster="/static/images/hillary/thumb.png"
|
||||||
/>
|
/>
|
||||||
|
@ -11,21 +11,9 @@ tags:
|
|||||||
image: "/static/images/notes/my-first-code/jbb-screen1.png"
|
image: "/static/images/notes/my-first-code/jbb-screen1.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/my-first-code/netscape.png"
|
|
||||||
width="865"
|
|
||||||
height="155"
|
|
||||||
alt="Awesome First Code on GitHub"
|
|
||||||
href="https://github.com/jakejarvis/awesome-first-code"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
<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.
|
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.
|
||||||
|
|
||||||
@ -35,14 +23,7 @@ Hopefully we can all look back at our first projects and be proud of how far we'
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Figure
|

|
||||||
src="/public/static/images/notes/my-first-code/jbb-logo.png"
|
|
||||||
width="640"
|
|
||||||
height="80"
|
|
||||||
href="https://github.com/jakejarvis/jbb"
|
|
||||||
>
|
|
||||||
[Jake's Bulletin Board](https://github.com/jakejarvis/jbb)
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
Aside from my [first HTML creation](https://jakejarvis.github.io/my-first-website/) (circa 2001), my first real coding project was in 2003: a PHP 4 masterpiece creatively titled **Jake's Bulletin Board**. I've published the [source code in full on GitHub](https://github.com/jakejarvis/jbb) for your viewing pleasure and highlighted the best/worst parts below.
|
Aside from my [first HTML creation](https://jakejarvis.github.io/my-first-website/) (circa 2001), my first real coding project was in 2003: a PHP 4 masterpiece creatively titled **Jake's Bulletin Board**. I've published the [source code in full on GitHub](https://github.com/jakejarvis/jbb) for your viewing pleasure and highlighted the best/worst parts below.
|
||||||
|
|
||||||
@ -171,16 +152,10 @@ 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))
|
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="/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))
|
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="/public/static/images/notes/my-first-code/jbb-screen3.png" width="865" height="561">
|

|
||||||
JBB Homepage
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
<Figure src="/public/static/images/notes/my-first-code/jbb-screen4.png" width="865" height="493">
|

|
||||||
JBB Post
|
|
||||||
</Figure>
|
|
||||||
|
@ -14,13 +14,7 @@ image: "/static/images/notes/netlify-analytics-review/overview.png"
|
|||||||
|
|
||||||
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.
|
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="/public/static/images/notes/netlify-analytics-review/pageviews-2.png"
|
|
||||||
width="865"
|
|
||||||
height="361"
|
|
||||||
alt="Pageview charts on Netlify Analytics"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 👍 Pros
|
## 👍 Pros
|
||||||
|
|
||||||
@ -52,12 +46,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/).)
|
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="/public/static/images/notes/netlify-analytics-review/pages.png"
|
|
||||||
width="865"
|
|
||||||
height="390"
|
|
||||||
alt="Pageview and 404 tracking on Netlify Analytics"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Another tangential benefit you simply don't get from JavaScript-based tools like Google Analytics is the "Resources Not Found" box, which separates out URLs that resulted in a 404 Not Found error. Because of the 404 tracking, I discovered how many people were still subscribed to my posts via RSS from when I used WordPress _years_ ago, and I was able to redirect `/feed` and `/rss` to the new location.
|
Another tangential benefit you simply don't get from JavaScript-based tools like Google Analytics is the "Resources Not Found" box, which separates out URLs that resulted in a 404 Not Found error. Because of the 404 tracking, I discovered how many people were still subscribed to my posts via RSS from when I used WordPress _years_ ago, and I was able to redirect `/feed` and `/rss` to the new location.
|
||||||
|
|
||||||
@ -73,12 +62,7 @@ It makes sense that Netlify needs to subsidize the cost of providing free enterp
|
|||||||
|
|
||||||
### 📈 Accuracy
|
### 📈 Accuracy
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/netlify-analytics-review/sources-bandwidth.png"
|
|
||||||
width="865"
|
|
||||||
height="422"
|
|
||||||
alt="Referrer and bandwidth tracking on Netlify Analytics"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Clearly, as much as I wish they did, 60,000+ visitors didn't type my website directly into the URL bar in the past month. Some of my articles have been circulating on Hacker News, Reddit, Twitter, etc. — none of which have even made a blip on the dashboard.
|
Clearly, as much as I wish they did, 60,000+ visitors didn't type my website directly into the URL bar in the past month. Some of my articles have been circulating on Hacker News, Reddit, Twitter, etc. — none of which have even made a blip on the dashboard.
|
||||||
|
|
||||||
@ -90,12 +74,7 @@ One more note: since Netlify doesn't process IP addresses or user agents, bots c
|
|||||||
|
|
||||||
### ⏱️ Historical Data
|
### ⏱️ Historical Data
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/netlify-analytics-review/overview.png"
|
|
||||||
width="865"
|
|
||||||
height="355"
|
|
||||||
alt="Overview of Netlify Analytics stats"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Trying out Netlify Analytics meant switching this site from [GitHub Pages](https://pages.github.com/) to Netlify — something I still have mixed feelings about. But if I had been on Netlify the entire time, I would have gotten thirty days of historical stats backfilled right off the bat, from before I even started paying for Analytics.
|
Trying out Netlify Analytics meant switching this site from [GitHub Pages](https://pages.github.com/) to Netlify — something I still have mixed feelings about. But if I had been on Netlify the entire time, I would have gotten thirty days of historical stats backfilled right off the bat, from before I even started paying for Analytics.
|
||||||
|
|
||||||
|
@ -10,14 +10,7 @@ tags:
|
|||||||
image: "/static/images/notes/presidential-candidates-404-pages/obama-laughing.jpg"
|
image: "/static/images/notes/presidential-candidates-404-pages/obama-laughing.jpg"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Figure
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/obama-laughing.jpg"
|
|
||||||
width="865"
|
|
||||||
height="460"
|
|
||||||
priority
|
|
||||||
>
|
|
||||||
President Barack H. Obama, probably ranking some of these 404 pages.
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
Ever since [President Obama injected technology](https://arstechnica.com/information-technology/2012/11/built-to-win-deep-inside-obamas-campaign-tech/) into presidential politics in a historic way, one of the few bright spots of the incredibly long and exhausting race for me has been inspecting each candidate's campaign website. They end up revealing a great deal about how much each of them is willing to invest in the internet, and how young and innovative (and potentially funny) the staff members they attract are.
|
Ever since [President Obama injected technology](https://arstechnica.com/information-technology/2012/11/built-to-win-deep-inside-obamas-campaign-tech/) into presidential politics in a historic way, one of the few bright spots of the incredibly long and exhausting race for me has been inspecting each candidate's campaign website. They end up revealing a great deal about how much each of them is willing to invest in the internet, and how young and innovative (and potentially funny) the staff members they attract are.
|
||||||
|
|
||||||
@ -27,12 +20,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)
|
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="/public/static/images/notes/presidential-candidates-404-pages/warren.png"
|
|
||||||
width="865"
|
|
||||||
height="579"
|
|
||||||
alt="Elizabeth Warren"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 2. Bernie Sanders — [berniesanders.com](https://berniesanders.com/asdfasdf404/)
|
## 2. Bernie Sanders — [berniesanders.com](https://berniesanders.com/asdfasdf404/)
|
||||||
|
|
||||||
@ -50,12 +38,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.
|
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="/public/static/images/notes/presidential-candidates-404-pages/biden.png"
|
|
||||||
width="865"
|
|
||||||
height="539"
|
|
||||||
alt="Joe Biden"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 4. Beto O'Rourke — [betoorourke.com](https://betoorourke.com/asdfasdf404)
|
## 4. Beto O'Rourke — [betoorourke.com](https://betoorourke.com/asdfasdf404)
|
||||||
|
|
||||||
@ -85,100 +68,55 @@ 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.
|
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="/public/static/images/notes/presidential-candidates-404-pages/buttigeg.png"
|
|
||||||
width="865"
|
|
||||||
height="344"
|
|
||||||
alt="Pete Buttigeg"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 7. Cory Booker — [corybooker.com](https://corybooker.com/asdfasdf404/)
|
## 7. Cory Booker — [corybooker.com](https://corybooker.com/asdfasdf404/)
|
||||||
|
|
||||||
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.
|
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="/public/static/images/notes/presidential-candidates-404-pages/booker.png"
|
|
||||||
width="865"
|
|
||||||
height="503"
|
|
||||||
alt="Cory Booker"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 8. Andrew Yang — [yang2020.com](https://www.yang2020.com/asdfasdf404)
|
## 8. Andrew Yang — [yang2020.com](https://www.yang2020.com/asdfasdf404)
|
||||||
|
|
||||||
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.
|
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="/public/static/images/notes/presidential-candidates-404-pages/yang.png"
|
|
||||||
width="865"
|
|
||||||
height="470"
|
|
||||||
alt="Andrew Yang"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 9. Amy Klobuchar — [amyklobuchar.com](https://amyklobuchar.com/asdfasdf404)
|
## 9. Amy Klobuchar — [amyklobuchar.com](https://amyklobuchar.com/asdfasdf404)
|
||||||
|
|
||||||
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.
|
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="/public/static/images/notes/presidential-candidates-404-pages/klobuchar.png"
|
|
||||||
width="865"
|
|
||||||
height="456"
|
|
||||||
alt="Amy Klobuchar"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 10. Steve Bullock — [stevebullock.com](https://stevebullock.com/asdfasdf404)
|
## 10. Steve Bullock — [stevebullock.com](https://stevebullock.com/asdfasdf404)
|
||||||
|
|
||||||
I'll never publicly say anything against a good Dad joke. This is no exception.
|
I'll never publicly say anything against a good Dad joke. This is no exception.
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/bullock.png"
|
|
||||||
width="865"
|
|
||||||
height="467"
|
|
||||||
alt="Steve Bullock"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 11. Michael Bennet — [michaelbennet.com](https://michaelbennet.com/asdfasdf404)
|
## 11. Michael Bennet — [michaelbennet.com](https://michaelbennet.com/asdfasdf404)
|
||||||
|
|
||||||
Another quality Dad joke here.
|
Another quality Dad joke here.
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/bennet.png"
|
|
||||||
width="865"
|
|
||||||
height="543"
|
|
||||||
alt="Michael Bennet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 12. John Delaney — [johndelaney.com](https://www.johndelaney.com/asdfasdf404)
|
## 12. John Delaney — [johndelaney.com](https://www.johndelaney.com/asdfasdf404)
|
||||||
|
|
||||||
Yet another Dad joke? I honestly had the hardest time ranking these three.
|
Yet another Dad joke? I honestly had the hardest time ranking these three.
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/delaney.png"
|
|
||||||
width="865"
|
|
||||||
height="405"
|
|
||||||
alt="John Delaney"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 13. Marianne Williamson — [marianne2020.com](https://www.marianne2020.com/asdfasdf404)
|
## 13. Marianne Williamson — [marianne2020.com](https://www.marianne2020.com/asdfasdf404)
|
||||||
|
|
||||||
A 404 page only a motivational author and speaker running for president could envision.
|
A 404 page only a motivational author and speaker running for president could envision.
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/williamson.png"
|
|
||||||
width="865"
|
|
||||||
height="357"
|
|
||||||
alt="Marianne Williamson"
|
|
||||||
/>
|
|
||||||
|
|
||||||
## 14. The Donald — [donaldjtrump.com](https://donaldjtrump.com/asdfasdf404)
|
## 14. The Donald — [donaldjtrump.com](https://donaldjtrump.com/asdfasdf404)
|
||||||
|
|
||||||
I guess this would be slightly humorous...four years ago. Time to move on from your middle-school crush, Donny.
|
I guess this would be slightly humorous...four years ago. Time to move on from your middle-school crush, Donny.
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/trump.png"
|
|
||||||
width="865"
|
|
||||||
height="524"
|
|
||||||
alt="Trump/Pence"
|
|
||||||
/>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -188,36 +126,16 @@ These candidates haven't configured a custom 404 page, settling for the default
|
|||||||
|
|
||||||
### 15. Julián Castro — [julianforthefuture.com](https://www.julianforthefuture.com/asdfasdf404)
|
### 15. Julián Castro — [julianforthefuture.com](https://www.julianforthefuture.com/asdfasdf404)
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/castro.png"
|
|
||||||
width="865"
|
|
||||||
height="316"
|
|
||||||
alt="Julián Castro"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### 16. Wayne Messam — [wayneforusa.com](https://wayneforusa.com/asdfasdf404)
|
### 16. Wayne Messam — [wayneforusa.com](https://wayneforusa.com/asdfasdf404)
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/messam.png"
|
|
||||||
width="865"
|
|
||||||
height="529"
|
|
||||||
alt="Wayne Messam"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### 17. Tulsi Gabbard — [tulsi2020.com](https://www.tulsi2020.com/asdfasdf404)
|
### 17. Tulsi Gabbard — [tulsi2020.com](https://www.tulsi2020.com/asdfasdf404)
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/gabbard.png"
|
|
||||||
width="865"
|
|
||||||
height="333"
|
|
||||||
alt="Tulsi Gabbard"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### 18. Joe Sestak — [joesestak.com](https://www.joesestak.com/asdfasdf404)
|
### 18. Joe Sestak — [joesestak.com](https://www.joesestak.com/asdfasdf404)
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/presidential-candidates-404-pages/sestak.png"
|
|
||||||
width="865"
|
|
||||||
height="366"
|
|
||||||
alt="Joe Sestak"
|
|
||||||
/>
|
|
||||||
|
@ -11,26 +11,13 @@ tags:
|
|||||||
image: "/static/images/notes/security-headers-cloudflare-workers/security-headers.png"
|
image: "/static/images/notes/security-headers-cloudflare-workers/security-headers.png"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Figure
|

|
||||||
src="/public/static/images/notes/security-headers-cloudflare-workers/security-headers.png"
|
|
||||||
width="700"
|
|
||||||
height="275"
|
|
||||||
href="https://securityheaders.com/?q=jarv.is&followRedirects=on"
|
|
||||||
priority
|
|
||||||
>
|
|
||||||
An [A+ security grade](https://securityheaders.com/?q=jarv.is&followRedirects=on) for this website!
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
In 2019, it's becoming more and more important to harden websites via HTTP response headers, which all modern browsers parse and enforce. Multiple standards have been introduced over the past few years to protect users from various attack vectors, including `Content-Security-Policy` for injection protection, `Strict-Transport-Security` for HTTPS enforcement, `X-XSS-Protection` for cross-site scripting prevention, `X-Content-Type-Options` to enforce correct MIME types, `Referrer-Policy` to limit information sent with external links, [and many, many more](https://www.netsparker.com/whitepaper-http-security-headers/).
|
In 2019, it's becoming more and more important to harden websites via HTTP response headers, which all modern browsers parse and enforce. Multiple standards have been introduced over the past few years to protect users from various attack vectors, including `Content-Security-Policy` for injection protection, `Strict-Transport-Security` for HTTPS enforcement, `X-XSS-Protection` for cross-site scripting prevention, `X-Content-Type-Options` to enforce correct MIME types, `Referrer-Policy` to limit information sent with external links, [and many, many more](https://www.netsparker.com/whitepaper-http-security-headers/).
|
||||||
|
|
||||||
[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.
|
[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="/public/static/images/notes/security-headers-cloudflare-workers/cf-workers.png"
|
|
||||||
width="650"
|
|
||||||
height="325"
|
|
||||||
alt="Cloudflare Workers"
|
|
||||||
/>
|
|
||||||
|
|
||||||
Workers can be enabled for $5/month via the [Cloudflare Dashboard](https://dash.cloudflare.com/). (It's worth noting, once enabled, Workers can be used on _any zone_ on your account, not just one website!).
|
Workers can be enabled for $5/month via the [Cloudflare Dashboard](https://dash.cloudflare.com/). (It's worth noting, once enabled, Workers can be used on _any zone_ on your account, not just one website!).
|
||||||
|
|
||||||
|
@ -13,15 +13,7 @@ image: "/static/images/notes/shodan-search-queries/shodan.png"
|
|||||||
|
|
||||||
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.
|
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="/public/static/images/notes/shodan-search-queries/shodan.png"
|
|
||||||
width="865"
|
|
||||||
height="248"
|
|
||||||
href="https://www.shodan.io/"
|
|
||||||
priority
|
|
||||||
>
|
|
||||||
[**Most search filters require a Shodan account.**](https://account.shodan.io/register)
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
You can assume these queries only return unsecured/open instances when possible. For your own legal benefit, do not attempt to login (even with default passwords) if they aren't! Narrow down results by adding filters like `country:US` or `org:"Harvard University"` or `hostname:"nasa.gov"` to the end.
|
You can assume these queries only return unsecured/open instances when possible. For your own legal benefit, do not attempt to login (even with default passwords) if they aren't! Narrow down results by adding filters like `country:US` or `org:"Harvard University"` or `hostname:"nasa.gov"` to the end.
|
||||||
|
|
||||||
@ -52,12 +44,7 @@ The world and its devices are quickly becoming more connected through the shiny
|
|||||||
"Server: Prismview Player"
|
"Server: Prismview Player"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/billboard3.png"
|
|
||||||
width="450"
|
|
||||||
height="329"
|
|
||||||
alt="Example: Electronic Billboards"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Gas Station Pump Controllers [🔎 →](https://www.shodan.io/search?query=%22in-tank+inventory%22+port%3A10001)
|
### Gas Station Pump Controllers [🔎 →](https://www.shodan.io/search?query=%22in-tank+inventory%22+port%3A10001)
|
||||||
|
|
||||||
@ -65,12 +52,7 @@ The world and its devices are quickly becoming more connected through the shiny
|
|||||||
"in-tank inventory" port:10001
|
"in-tank inventory" port:10001
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/7-11.png"
|
|
||||||
width="600"
|
|
||||||
height="226"
|
|
||||||
alt="Example: Gas Station Pump Inventories"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Automatic License Plate Readers [🔎 →](https://www.shodan.io/search?query=P372+%22ANPR+enabled%22)
|
### Automatic License Plate Readers [🔎 →](https://www.shodan.io/search?query=P372+%22ANPR+enabled%22)
|
||||||
|
|
||||||
@ -78,12 +60,7 @@ The world and its devices are quickly becoming more connected through the shiny
|
|||||||
P372 "ANPR enabled"
|
P372 "ANPR enabled"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/plate-reader.png"
|
|
||||||
width="680"
|
|
||||||
height="284"
|
|
||||||
alt="Example: Automatic License Plate Reader"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Traffic Light Controllers / Red Light Cameras [🔎 →](https://www.shodan.io/search?query=mikrotik+streetlight)
|
### Traffic Light Controllers / Red Light Cameras [🔎 →](https://www.shodan.io/search?query=mikrotik+streetlight)
|
||||||
|
|
||||||
@ -119,12 +96,7 @@ Wiretapping mechanism outlined by Cisco in [RFC 3924](https://tools.ietf.org/htm
|
|||||||
http.title:"Tesla PowerPack System" http.component:"d3" -ga3ca4f2
|
http.title:"Tesla PowerPack System" http.component:"d3" -ga3ca4f2
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/tesla.png"
|
|
||||||
width="865"
|
|
||||||
height="543"
|
|
||||||
alt="Example: Tesla PowerPack Charging Status"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Electric Vehicle Chargers [🔎 →](https://www.shodan.io/search?query=%22Server%3A+gSOAP%2F2.8%22+%22Content-Length%3A+583%22)
|
### Electric Vehicle Chargers [🔎 →](https://www.shodan.io/search?query=%22Server%3A+gSOAP%2F2.8%22+%22Content-Length%3A+583%22)
|
||||||
|
|
||||||
@ -140,12 +112,7 @@ Shodan made a pretty sweet [Ship Tracker](https://shiptracker.shodan.io/) that m
|
|||||||
"Cobham SATCOM" OR ("Sailor" "VSAT")
|
"Cobham SATCOM" OR ("Sailor" "VSAT")
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/sailor-vsat.png"
|
|
||||||
width="700"
|
|
||||||
height="361"
|
|
||||||
alt="Example: Maritime Satellites"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Submarine Mission Control Dashboards [🔎 →](https://www.shodan.io/search?query=title%3A%22Slocum+Fleet+Mission+Control%22)
|
### Submarine Mission Control Dashboards [🔎 →](https://www.shodan.io/search?query=title%3A%22Slocum+Fleet+Mission+Control%22)
|
||||||
|
|
||||||
@ -159,12 +126,7 @@ title:"Slocum Fleet Mission Control"
|
|||||||
"Server: CarelDataServer" "200 Document follows"
|
"Server: CarelDataServer" "200 Document follows"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/refrigeration.png"
|
|
||||||
width="865"
|
|
||||||
height="456"
|
|
||||||
alt="Example: CAREL PlantVisor Refrigeration Units"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### [Nordex Wind Turbine](https://www.nordex-online.com/en/products-services/wind-turbines.html) Farms [🔎 →](https://www.shodan.io/search?query=http.title%3A%22Nordex+Control%22+%22Windows+2000+5.0+x86%22+%22Jetty%2F3.1+%28JSP+1.1%3B+Servlet+2.2%3B+java+1.6.0_14%29%22)
|
### [Nordex Wind Turbine](https://www.nordex-online.com/en/products-services/wind-turbines.html) Farms [🔎 →](https://www.shodan.io/search?query=http.title%3A%22Nordex+Control%22+%22Windows+2000+5.0+x86%22+%22Jetty%2F3.1+%28JSP+1.1%3B+Servlet+2.2%3B+java+1.6.0_14%29%22)
|
||||||
|
|
||||||
@ -178,12 +140,7 @@ http.title:"Nordex Control" "Windows 2000 5.0 x86" "Jetty/3.1 (JSP 1.1; Servlet
|
|||||||
"[1m[35mWelcome on console"
|
"[1m[35mWelcome on console"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/c4max.png"
|
|
||||||
width="865"
|
|
||||||
height="171"
|
|
||||||
alt="Example: C4 Max Vehicle GPS"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### [DICOM](https://www.dicomstandard.org/about/) Medical X-Ray Machines [🔎 →](https://www.shodan.io/search?query=%22DICOM+Server+Response%22+port%3A104)
|
### [DICOM](https://www.dicomstandard.org/about/) Medical X-Ray Machines [🔎 →](https://www.shodan.io/search?query=%22DICOM+Server+Response%22+port%3A104)
|
||||||
|
|
||||||
@ -199,12 +156,7 @@ Secured by default, thankfully, but these 1,700+ machines still [have no busines
|
|||||||
"Server: EIG Embedded Web Server" "200 Document follows"
|
"Server: EIG Embedded Web Server" "200 Document follows"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/power-gaugetech.png"
|
|
||||||
width="500"
|
|
||||||
height="246"
|
|
||||||
alt="Example: GaugeTech Electricity Meters"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Siemens Industrial Automation [🔎 →](https://www.shodan.io/search?query=%22Siemens%2C+SIMATIC%22+port%3A161)
|
### Siemens Industrial Automation [🔎 →](https://www.shodan.io/search?query=%22Siemens%2C+SIMATIC%22+port%3A161)
|
||||||
|
|
||||||
@ -242,9 +194,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)
|
[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="/public/static/images/notes/shodan-search-queries/vnc.png" width="500" height="375">
|

|
||||||
The first result right now. 😞
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
### Windows RDP [🔎 →](https://www.shodan.io/search?query=%22%5Cx03%5Cx00%5Cx00%5Cx0b%5Cx06%5Cxd0%5Cx00%5Cx00%5Cx124%5Cx00%22)
|
### Windows RDP [🔎 →](https://www.shodan.io/search?query=%22%5Cx03%5Cx00%5Cx00%5Cx0b%5Cx06%5Cxd0%5Cx00%5Cx00%5Cx124%5Cx00%22)
|
||||||
|
|
||||||
@ -266,12 +216,7 @@ Command-line access inside Kubernetes pods and Docker containers, and real-time
|
|||||||
title:"Weave Scope" http.favicon.hash:567176827
|
title:"Weave Scope" http.favicon.hash:567176827
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/weavescope.png"
|
|
||||||
width="865"
|
|
||||||
height="465"
|
|
||||||
alt="Example: Weave Scope Dashboards"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### MongoDB [🔎 →](https://www.shodan.io/search?query=product%3AMongoDB+-authentication)
|
### MongoDB [🔎 →](https://www.shodan.io/search?query=product%3AMongoDB+-authentication)
|
||||||
|
|
||||||
@ -281,12 +226,7 @@ Older versions were insecure by default. [Very scary.](https://krebsonsecurity.c
|
|||||||
"MongoDB Server Information" port:27017 -authentication
|
"MongoDB Server Information" port:27017 -authentication
|
||||||
```
|
```
|
||||||
|
|
||||||
<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)
|
### [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)
|
||||||
|
|
||||||
@ -296,12 +236,7 @@ Like the [infamous phpMyAdmin](https://www.cvedetails.com/vulnerability-list/ven
|
|||||||
"Set-Cookie: mongo-express=" "200 OK"
|
"Set-Cookie: mongo-express=" "200 OK"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/mongo-express.png"
|
|
||||||
width="700"
|
|
||||||
height="395"
|
|
||||||
alt="Example: Mongo Express GUI"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Jenkins CI [🔎 →](https://www.shodan.io/search?query=%22X-Jenkins%22+%22Set-Cookie%3A+JSESSIONID%22+http.title%3A%22Dashboard%22)
|
### Jenkins CI [🔎 →](https://www.shodan.io/search?query=%22X-Jenkins%22+%22Set-Cookie%3A+JSESSIONID%22+http.title%3A%22Dashboard%22)
|
||||||
|
|
||||||
@ -309,12 +244,7 @@ Like the [infamous phpMyAdmin](https://www.cvedetails.com/vulnerability-list/ven
|
|||||||
"X-Jenkins" "Set-Cookie: JSESSIONID" http.title:"Dashboard"
|
"X-Jenkins" "Set-Cookie: JSESSIONID" http.title:"Dashboard"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/jenkins.png"
|
|
||||||
width="700"
|
|
||||||
height="225"
|
|
||||||
alt="Example: Jenkins CI"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Docker APIs [🔎 →](https://www.shodan.io/search?query=%22Docker+Containers%3A%22+port%3A2375)
|
### Docker APIs [🔎 →](https://www.shodan.io/search?query=%22Docker+Containers%3A%22+port%3A2375)
|
||||||
|
|
||||||
@ -360,12 +290,7 @@ Lantronix password port:30718 -secured
|
|||||||
"Citrix Applications:" port:1604
|
"Citrix Applications:" port:1604
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/citrix.png"
|
|
||||||
width="700"
|
|
||||||
height="273"
|
|
||||||
alt="Example: Citrix Virtual Apps"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Cisco Smart Install [🔎 →](https://www.shodan.io/search?query=%22smart+install+client+active%22)
|
### Cisco Smart Install [🔎 →](https://www.shodan.io/search?query=%22smart+install+client+active%22)
|
||||||
|
|
||||||
@ -393,12 +318,7 @@ Telnet Configuration: [🔎 →](https://www.shodan.io/search?query=%22Polycom+C
|
|||||||
"Polycom Command Shell" -failed port:23
|
"Polycom Command Shell" -failed port:23
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/polycom.png"
|
|
||||||
width="550"
|
|
||||||
height="251"
|
|
||||||
alt="Example: Polycom Video Conferencing"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### [Bomgar Help Desk](https://www.beyondtrust.com/remote-support/integrations) Portal [🔎 →](https://www.shodan.io/search?query=%22Server%3A+Bomgar%22+%22200+OK%22)
|
### [Bomgar Help Desk](https://www.beyondtrust.com/remote-support/integrations) Portal [🔎 →](https://www.shodan.io/search?query=%22Server%3A+Bomgar%22+%22200+OK%22)
|
||||||
|
|
||||||
@ -426,12 +346,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
|
|||||||
"x-owa-version" "IE=EmulateIE7" "Server: Microsoft-IIS/7.0"
|
"x-owa-version" "IE=EmulateIE7" "Server: Microsoft-IIS/7.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/owa2007.png"
|
|
||||||
width="450"
|
|
||||||
height="494"
|
|
||||||
alt="Example: OWA for Exchange 2007"
|
|
||||||
/>
|
|
||||||
|
|
||||||
#### Exchange 2010 [🔎 →](https://www.shodan.io/search?query=%22x-owa-version%22+%22IE%3DEmulateIE7%22+http.favicon.hash%3A442749392)
|
#### Exchange 2010 [🔎 →](https://www.shodan.io/search?query=%22x-owa-version%22+%22IE%3DEmulateIE7%22+http.favicon.hash%3A442749392)
|
||||||
|
|
||||||
@ -439,12 +354,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
|
|||||||
"x-owa-version" "IE=EmulateIE7" http.favicon.hash:442749392
|
"x-owa-version" "IE=EmulateIE7" http.favicon.hash:442749392
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/owa2010.png"
|
|
||||||
width="450"
|
|
||||||
height="429"
|
|
||||||
alt="Example: OWA for Exchange 2010"
|
|
||||||
/>
|
|
||||||
|
|
||||||
#### Exchange 2013 / 2016 [🔎 →](https://www.shodan.io/search?query=%22X-AspNet-Version%22+http.title%3A%22Outlook%22+-%22x-owa-version%22)
|
#### Exchange 2013 / 2016 [🔎 →](https://www.shodan.io/search?query=%22X-AspNet-Version%22+http.title%3A%22Outlook%22+-%22x-owa-version%22)
|
||||||
|
|
||||||
@ -452,12 +362,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
|
|||||||
"X-AspNet-Version" http.title:"Outlook" -"x-owa-version"
|
"X-AspNet-Version" http.title:"Outlook" -"x-owa-version"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/owa2013.png"
|
|
||||||
width="580"
|
|
||||||
height="230"
|
|
||||||
alt="Example: OWA for Exchange 2013/2016"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Lync / Skype for Business [🔎 →](https://www.shodan.io/search?query=%22X-MS-Server-Fqdn%22)
|
### Lync / Skype for Business [🔎 →](https://www.shodan.io/search?query=%22X-MS-Server-Fqdn%22)
|
||||||
|
|
||||||
@ -501,12 +406,7 @@ Concerning [default network shares of QuickBooks](https://quickbooks.intuit.com/
|
|||||||
"Set-Cookie: iomega=" -"manage/login.html" -http.title:"Log In"
|
"Set-Cookie: iomega=" -"manage/login.html" -http.title:"Log In"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/iomega.png"
|
|
||||||
width="600"
|
|
||||||
height="215"
|
|
||||||
alt="Example: Iomega / LenovoEMC NAS Drives"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Buffalo TeraStation NAS Drives [🔎 →](https://www.shodan.io/search?query=Redirecting+sencha+port%3A9000)
|
### Buffalo TeraStation NAS Drives [🔎 →](https://www.shodan.io/search?query=Redirecting+sencha+port%3A9000)
|
||||||
|
|
||||||
@ -514,12 +414,7 @@ Concerning [default network shares of QuickBooks](https://quickbooks.intuit.com/
|
|||||||
Redirecting sencha port:9000
|
Redirecting sencha port:9000
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/buffalo.png"
|
|
||||||
width="580"
|
|
||||||
height="140"
|
|
||||||
alt="Example: Buffalo TeraStation NAS Drives"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Logitech Media Servers [🔎 →](https://www.shodan.io/search?query=%22Server%3A+Logitech+Media+Server%22+%22200+OK%22)
|
### Logitech Media Servers [🔎 →](https://www.shodan.io/search?query=%22Server%3A+Logitech+Media+Server%22+%22200+OK%22)
|
||||||
|
|
||||||
@ -527,12 +422,7 @@ Redirecting sencha port:9000
|
|||||||
"Server: Logitech Media Server" "200 OK"
|
"Server: Logitech Media Server" "200 OK"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/logitech.png"
|
|
||||||
width="500"
|
|
||||||
height="224"
|
|
||||||
alt="Example: Logitech Media Servers"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### [Plex](https://www.plex.tv/) Media Servers [🔎 →](https://www.shodan.io/search?query=%22X-Plex-Protocol%22+%22200+OK%22+port%3A32400)
|
### [Plex](https://www.plex.tv/) Media Servers [🔎 →](https://www.shodan.io/search?query=%22X-Plex-Protocol%22+%22200+OK%22+port%3A32400)
|
||||||
|
|
||||||
@ -546,12 +436,7 @@ Redirecting sencha port:9000
|
|||||||
"CherryPy/5.1.0" "/home"
|
"CherryPy/5.1.0" "/home"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/plexpy.png"
|
|
||||||
width="560"
|
|
||||||
height="266"
|
|
||||||
alt="Example: PlexPy / Tautulli Dashboards"
|
|
||||||
/>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -593,12 +478,7 @@ html:"DVR_H264 ActiveX"
|
|||||||
"Serial Number:" "Built:" "Server: HP HTTP"
|
"Serial Number:" "Built:" "Server: HP HTTP"
|
||||||
```
|
```
|
||||||
|
|
||||||
<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)
|
### Xerox Copiers/Printers [🔎 →](https://www.shodan.io/search?query=ssl%3A%22Xerox+Generic+Root%22)
|
||||||
|
|
||||||
@ -606,12 +486,7 @@ html:"DVR_H264 ActiveX"
|
|||||||
ssl:"Xerox Generic Root"
|
ssl:"Xerox Generic Root"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/xerox.png"
|
|
||||||
width="620"
|
|
||||||
height="263"
|
|
||||||
alt="Example: Xerox Copiers/Printers"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Epson Printers [🔎 →](https://www.shodan.io/search?query=%22SERVER%3A+EPSON_Linux+UPnP%22+%22200+OK%22)
|
### Epson Printers [🔎 →](https://www.shodan.io/search?query=%22SERVER%3A+EPSON_Linux+UPnP%22+%22200+OK%22)
|
||||||
|
|
||||||
@ -623,12 +498,7 @@ ssl:"Xerox Generic Root"
|
|||||||
"Server: EPSON-HTTP" "200 OK"
|
"Server: EPSON-HTTP" "200 OK"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/epson.png"
|
|
||||||
width="550"
|
|
||||||
height="308"
|
|
||||||
alt="Example: Epson Printers"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Canon Printers [🔎 →](https://www.shodan.io/search?query=%22Server%3A+KS_HTTP%22+%22200+OK%22)
|
### Canon Printers [🔎 →](https://www.shodan.io/search?query=%22Server%3A+KS_HTTP%22+%22200+OK%22)
|
||||||
|
|
||||||
@ -640,12 +510,7 @@ ssl:"Xerox Generic Root"
|
|||||||
"Server: CANON HTTP Server"
|
"Server: CANON HTTP Server"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/canon.png"
|
|
||||||
width="550"
|
|
||||||
height="195"
|
|
||||||
alt="Example: Canon Printers"
|
|
||||||
/>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -657,12 +522,7 @@ ssl:"Xerox Generic Root"
|
|||||||
"Server: AV_Receiver" "HTTP/1.1 406"
|
"Server: AV_Receiver" "HTTP/1.1 406"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/yamaha.png"
|
|
||||||
width="550"
|
|
||||||
height="349"
|
|
||||||
alt="Example: Yamaha Stereos"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Apple AirPlay Receivers [🔎 →](https://www.shodan.io/search?query=%22%5Cx08_airplay%22+port%3A5353)
|
### Apple AirPlay Receivers [🔎 →](https://www.shodan.io/search?query=%22%5Cx08_airplay%22+port%3A5353)
|
||||||
|
|
||||||
@ -694,12 +554,7 @@ Apple TVs, HomePods, etc.
|
|||||||
title:"OctoPrint" -title:"Login" http.favicon.hash:1307375944
|
title:"OctoPrint" -title:"Login" http.favicon.hash:1307375944
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/octoprint.png"
|
|
||||||
width="700"
|
|
||||||
height="335"
|
|
||||||
alt="Example: OctoPrint 3D Printers"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Etherium Miners [🔎 →](https://www.shodan.io/search?query=%22ETH+-+Total+speed%22)
|
### Etherium Miners [🔎 →](https://www.shodan.io/search?query=%22ETH+-+Total+speed%22)
|
||||||
|
|
||||||
@ -707,12 +562,7 @@ title:"OctoPrint" -title:"Login" http.favicon.hash:1307375944
|
|||||||
"ETH - Total speed"
|
"ETH - Total speed"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Image
|

|
||||||
src="/public/static/images/notes/shodan-search-queries/eth.png"
|
|
||||||
width="800"
|
|
||||||
height="141"
|
|
||||||
alt="Example: Etherium Miners"
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Apache Directory Listings [🔎 →](https://www.shodan.io/search?query=http.title%3A%22Index+of+%2F%22+http.html%3A%22.pem%22)
|
### Apache Directory Listings [🔎 →](https://www.shodan.io/search?query=http.title%3A%22Index+of+%2F%22+http.html%3A%22.pem%22)
|
||||||
|
|
||||||
|
@ -14,15 +14,7 @@ A few months ago, I stumbled upon [my first website ever](https://jakejarvis.git
|
|||||||
|
|
||||||
Introducing the [**Y2K Sandbox**](/y2k/) — 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. 🤓
|
Introducing the [**Y2K Sandbox**](/y2k/) — 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="/public/static/images/notes/y2k-sandbox/screenshot.png"
|
|
||||||
width="865"
|
|
||||||
height="649"
|
|
||||||
href="/y2k/"
|
|
||||||
priority={true}
|
|
||||||
>
|
|
||||||
[**Play in the Y2K Sandbox, at your own risk.**](/y2k/)
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
The backend is powered by [**QEMU**](https://www.qemu.org/) (as a Pentium III emulator) inside isolated **Docker** containers, [**websocketd**](https://github.com/joewalnes/websocketd) (an **_awesome_** lightweight WebSockets server written in Go), and [**Cloudflare Tunnels**](https://www.cloudflare.com/products/tunnel/) (for some protection), all tied together with some [Ruby code](https://github.com/jakejarvis/y2k/blob/main/container/bin/boot.rb) and [shell scripts](https://github.com/jakejarvis/y2k/tree/main/host). ~~I'll push the backend scripts up to GitHub once I have a chance to untangle the spaghetti code. 🍝~~
|
The backend is powered by [**QEMU**](https://www.qemu.org/) (as a Pentium III emulator) inside isolated **Docker** containers, [**websocketd**](https://github.com/joewalnes/websocketd) (an **_awesome_** lightweight WebSockets server written in Go), and [**Cloudflare Tunnels**](https://www.cloudflare.com/products/tunnel/) (for some protection), all tied together with some [Ruby code](https://github.com/jakejarvis/y2k/blob/main/container/bin/boot.rb) and [shell scripts](https://github.com/jakejarvis/y2k/tree/main/host). ~~I'll push the backend scripts up to GitHub once I have a chance to untangle the spaghetti code. 🍝~~
|
||||||
|
|
||||||
@ -32,9 +24,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?
|
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="/public/static/images/notes/y2k-sandbox/windows-me.png" width="320" height="92">
|

|
||||||
**@microsoft** Please don't sue me.
|
|
||||||
</Figure>
|
|
||||||
|
|
||||||
Feel free to [open an issue on GitHub](https://github.com/jakejarvis/y2k/issues) if you run into connection glitches or have any nostalgic inspiration for software you think would be cool to install persistently on the OS image. I certainly can't help with any actual Windows Me crashes, though — it was beyond help a long, long time ago. Like, [the day it came out](https://books.google.com/books?id=Jbft8HXJZwQC&lpg=PP1&pg=PA76#v=onepage&q&f=false). But it will always have a soft spot in my heart.
|
Feel free to [open an issue on GitHub](https://github.com/jakejarvis/y2k/issues) if you run into connection glitches or have any nostalgic inspiration for software you think would be cool to install persistently on the OS image. I certainly can't help with any actual Windows Me crashes, though — it was beyond help a long, long time ago. Like, [the day it came out](https://books.google.com/books?id=Jbft8HXJZwQC&lpg=PP1&pg=PA76#v=onepage&q&f=false). But it will always have a soft spot in my heart.
|
||||||
|
|
||||||
|
18
package.json
18
package.json
@ -10,7 +10,6 @@
|
|||||||
"url": "https://github.com/jakejarvis"
|
"url": "https://github.com/jakejarvis"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -H 0.0.0.0",
|
"dev": "next dev -H 0.0.0.0",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
@ -21,8 +20,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@giscus/react": "^3.1.0",
|
"@giscus/react": "^3.1.0",
|
||||||
"@mdx-js/mdx": "^3.1.0",
|
"@mdx-js/loader": "^3.1.0",
|
||||||
"@next/bundle-analyzer": "15.2.1-canary.4",
|
"@mdx-js/react": "^3.1.0",
|
||||||
|
"@next/bundle-analyzer": "15.2.1-canary.5",
|
||||||
|
"@next/mdx": "15.2.1-canary.5",
|
||||||
"@octokit/graphql": "^8.2.1",
|
"@octokit/graphql": "^8.2.1",
|
||||||
"@octokit/graphql-schema": "^15.26.0",
|
"@octokit/graphql-schema": "^15.26.0",
|
||||||
"@prisma/client": "^6.4.1",
|
"@prisma/client": "^6.4.1",
|
||||||
@ -36,7 +37,7 @@
|
|||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"modern-normalize": "^3.0.1",
|
"modern-normalize": "^3.0.1",
|
||||||
"next": "15.2.1-canary.4",
|
"next": "15.2.1-canary.5",
|
||||||
"obj-str": "^1.1.0",
|
"obj-str": "^1.1.0",
|
||||||
"p-map": "^7.0.3",
|
"p-map": "^7.0.3",
|
||||||
"p-memoize": "^7.1.1",
|
"p-memoize": "^7.1.1",
|
||||||
@ -52,11 +53,13 @@
|
|||||||
"react-textarea-autosize": "^8.5.7",
|
"react-textarea-autosize": "^8.5.7",
|
||||||
"react-turnstile": "^1.1.4",
|
"react-turnstile": "^1.1.4",
|
||||||
"react-tweet": "^3.2.2",
|
"react-tweet": "^3.2.2",
|
||||||
|
"rehype-mdx-import-media": "^1.2.0",
|
||||||
"rehype-prism-plus": "^2.0.0",
|
"rehype-prism-plus": "^2.0.0",
|
||||||
"rehype-sanitize": "^6.0.0",
|
"rehype-sanitize": "^6.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"rehype-stringify": "^10.0.1",
|
"rehype-stringify": "^10.0.1",
|
||||||
"rehype-unwrap-images": "^1.0.0",
|
"rehype-unwrap-images": "^1.0.0",
|
||||||
|
"remark-frontmatter": "^5.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-rehype": "^11.1.1",
|
"remark-rehype": "^11.1.1",
|
||||||
@ -69,19 +72,20 @@
|
|||||||
"@eslint/js": "^9.21.0",
|
"@eslint/js": "^9.21.0",
|
||||||
"@jakejarvis/eslint-config": "~4.0.7",
|
"@jakejarvis/eslint-config": "~4.0.7",
|
||||||
"@types/comma-number": "^2.1.2",
|
"@types/comma-number": "^2.1.2",
|
||||||
"@types/node": "^22.13.8",
|
"@types/mdx": "^2.0.13",
|
||||||
|
"@types/node": "^22.13.9",
|
||||||
"@types/prop-types": "^15.7.14",
|
"@types/prop-types": "^15.7.14",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/react-is": "^19.0.0",
|
"@types/react-is": "^19.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "~9.21.0",
|
"eslint": "~9.21.0",
|
||||||
"eslint-config-next": "15.2.1-canary.4",
|
"eslint-config-next": "15.2.1-canary.5",
|
||||||
"eslint-config-prettier": "~10.0.2",
|
"eslint-config-prettier": "~10.0.2",
|
||||||
"eslint-plugin-mdx": "~3.1.5",
|
"eslint-plugin-mdx": "~3.1.5",
|
||||||
"eslint-plugin-prettier": "~5.2.3",
|
"eslint-plugin-prettier": "~5.2.3",
|
||||||
"lint-staged": "^15.4.3",
|
"lint-staged": "^15.4.3",
|
||||||
"prettier": "^3.5.2",
|
"prettier": "^3.5.3",
|
||||||
"prisma": "^6.4.1",
|
"prisma": "^6.4.1",
|
||||||
"schema-dts": "^1.1.5",
|
"schema-dts": "^1.1.5",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
|
331
pnpm-lock.yaml
generated
331
pnpm-lock.yaml
generated
@ -11,12 +11,18 @@ importers:
|
|||||||
'@giscus/react':
|
'@giscus/react':
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 3.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
'@mdx-js/mdx':
|
'@mdx-js/loader':
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.1.0(acorn@8.14.0)
|
version: 3.1.0(acorn@8.14.0)
|
||||||
|
'@mdx-js/react':
|
||||||
|
specifier: ^3.1.0
|
||||||
|
version: 3.1.0(@types/react@19.0.10)(react@19.0.0)
|
||||||
'@next/bundle-analyzer':
|
'@next/bundle-analyzer':
|
||||||
specifier: 15.2.1-canary.4
|
specifier: 15.2.1-canary.5
|
||||||
version: 15.2.1-canary.4
|
version: 15.2.1-canary.5
|
||||||
|
'@next/mdx':
|
||||||
|
specifier: 15.2.1-canary.5
|
||||||
|
version: 15.2.1-canary.5(@mdx-js/loader@3.1.0(acorn@8.14.0))(@mdx-js/react@3.1.0(@types/react@19.0.10)(react@19.0.0))
|
||||||
'@octokit/graphql':
|
'@octokit/graphql':
|
||||||
specifier: ^8.2.1
|
specifier: ^8.2.1
|
||||||
version: 8.2.1
|
version: 8.2.1
|
||||||
@ -31,7 +37,7 @@ importers:
|
|||||||
version: 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
'@vercel/analytics':
|
'@vercel/analytics':
|
||||||
specifier: ^1.5.0
|
specifier: ^1.5.0
|
||||||
version: 1.5.0(next@15.2.1-canary.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
version: 1.5.0(next@15.2.1-canary.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
@ -57,8 +63,8 @@ importers:
|
|||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
next:
|
next:
|
||||||
specifier: 15.2.1-canary.4
|
specifier: 15.2.1-canary.5
|
||||||
version: 15.2.1-canary.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 15.2.1-canary.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
obj-str:
|
obj-str:
|
||||||
specifier: ^1.1.0
|
specifier: ^1.1.0
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
@ -104,6 +110,9 @@ importers:
|
|||||||
react-tweet:
|
react-tweet:
|
||||||
specifier: ^3.2.2
|
specifier: ^3.2.2
|
||||||
version: 3.2.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 3.2.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
rehype-mdx-import-media:
|
||||||
|
specifier: ^1.2.0
|
||||||
|
version: 1.2.0
|
||||||
rehype-prism-plus:
|
rehype-prism-plus:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
@ -119,6 +128,9 @@ importers:
|
|||||||
rehype-unwrap-images:
|
rehype-unwrap-images:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
remark-frontmatter:
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: 5.0.0
|
||||||
remark-gfm:
|
remark-gfm:
|
||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
@ -150,9 +162,12 @@ importers:
|
|||||||
'@types/comma-number':
|
'@types/comma-number':
|
||||||
specifier: ^2.1.2
|
specifier: ^2.1.2
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
|
'@types/mdx':
|
||||||
|
specifier: ^2.0.13
|
||||||
|
version: 2.0.13
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.13.8
|
specifier: ^22.13.9
|
||||||
version: 22.13.8
|
version: 22.13.9
|
||||||
'@types/prop-types':
|
'@types/prop-types':
|
||||||
specifier: ^15.7.14
|
specifier: ^15.7.14
|
||||||
version: 15.7.14
|
version: 15.7.14
|
||||||
@ -172,8 +187,8 @@ importers:
|
|||||||
specifier: ~9.21.0
|
specifier: ~9.21.0
|
||||||
version: 9.21.0
|
version: 9.21.0
|
||||||
eslint-config-next:
|
eslint-config-next:
|
||||||
specifier: 15.2.1-canary.4
|
specifier: 15.2.1-canary.5
|
||||||
version: 15.2.1-canary.4(eslint@9.21.0)(typescript@5.7.3)
|
version: 15.2.1-canary.5(eslint@9.21.0)(typescript@5.7.3)
|
||||||
eslint-config-prettier:
|
eslint-config-prettier:
|
||||||
specifier: ~10.0.2
|
specifier: ~10.0.2
|
||||||
version: 10.0.2(eslint@9.21.0)
|
version: 10.0.2(eslint@9.21.0)
|
||||||
@ -182,13 +197,13 @@ importers:
|
|||||||
version: 3.1.5(eslint@9.21.0)
|
version: 3.1.5(eslint@9.21.0)
|
||||||
eslint-plugin-prettier:
|
eslint-plugin-prettier:
|
||||||
specifier: ~5.2.3
|
specifier: ~5.2.3
|
||||||
version: 5.2.3(eslint-config-prettier@10.0.2(eslint@9.21.0))(eslint@9.21.0)(prettier@3.5.2)
|
version: 5.2.3(eslint-config-prettier@10.0.2(eslint@9.21.0))(eslint@9.21.0)(prettier@3.5.3)
|
||||||
lint-staged:
|
lint-staged:
|
||||||
specifier: ^15.4.3
|
specifier: ^15.4.3
|
||||||
version: 15.4.3
|
version: 15.4.3
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.5.2
|
specifier: ^3.5.3
|
||||||
version: 3.5.2
|
version: 3.5.3
|
||||||
prisma:
|
prisma:
|
||||||
specifier: ^6.4.1
|
specifier: ^6.4.1
|
||||||
version: 6.4.1(typescript@5.7.3)
|
version: 6.4.1(typescript@5.7.3)
|
||||||
@ -557,62 +572,87 @@ packages:
|
|||||||
'@lit/reactive-element@2.0.4':
|
'@lit/reactive-element@2.0.4':
|
||||||
resolution: {integrity: sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==}
|
resolution: {integrity: sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==}
|
||||||
|
|
||||||
|
'@mdx-js/loader@3.1.0':
|
||||||
|
resolution: {integrity: sha512-xU/lwKdOyfXtQGqn3VnJjlDrmKXEvMi1mgYxVmukEUtVycIz1nh7oQ40bKTd4cA7rLStqu0740pnhGYxGoqsCg==}
|
||||||
|
peerDependencies:
|
||||||
|
webpack: '>=5'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
webpack:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@mdx-js/mdx@3.1.0':
|
'@mdx-js/mdx@3.1.0':
|
||||||
resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==}
|
resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==}
|
||||||
|
|
||||||
'@next/bundle-analyzer@15.2.1-canary.4':
|
'@mdx-js/react@3.1.0':
|
||||||
resolution: {integrity: sha512-dZ2R9qVD42b5hrXumOUy24ZKezpKei5+qgfjZb08NNlBxVWwZGyzz0tgOzR+ohXzuSf4ZqzDxABrtE93zmEi+A==}
|
resolution: {integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=16'
|
||||||
|
react: '>=16'
|
||||||
|
|
||||||
'@next/env@15.2.1-canary.4':
|
'@next/bundle-analyzer@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-JtERr+U5PVJsUhqPne/CgyD4Njod5wyTmU+ky3nMojB2l9gH/3CnmK/mHk4O0/jg8oNqjD3KCQkMKaq7xnISpA==}
|
resolution: {integrity: sha512-gb2W52d+7w4T8R+C2aM/EsdwLQ/YRo+UJn2NWe7dXxxfmoKrAO1BbDm95UmqU9TFynmE25KMb9FA5N3GikncRg==}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.2.1-canary.4':
|
'@next/env@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-Kb2Hwx4hVHpl7J70k+yST61Oc/QSbKUV6To5oN4SOidbNjqWx4KscSKg0DT63r7I4T2ImxU6ANKBkxZKaMU5zw==}
|
resolution: {integrity: sha512-CsxVj4UxrwU0/OVvfNY1aUvd+tcvbAkoIwwecU6w6MrNJzvFxUj9W17+YV/749cdnhxAsXL7jlY2vzdSKORIlQ==}
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@15.2.1-canary.4':
|
'@next/eslint-plugin-next@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-DIxMCTyrcZG8R1ooIspfKP6e/z5viQg+Q7YivyHKSskvlTNY10pMh49qnsTLUsy63rJ+MIhvEPXwoFVDbHB6Pg==}
|
resolution: {integrity: sha512-i5dRFR44ygX8PWs3Iwi26YT+LYFdiaPYUtsOAd6xpryUYrlcCkQBiQYFHOLJzGAe7ylaW4llUQrPAz1jxAdIAQ==}
|
||||||
|
|
||||||
|
'@next/mdx@15.2.1-canary.5':
|
||||||
|
resolution: {integrity: sha512-LIbLeMepvKSOEnmpLFaP+BtHpG90KrpJhnw8ZWznrG/aviT6aXjD4wSLWrw1RFOR14uskTt1GBVo1xMx/NhysA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@mdx-js/loader': '>=0.15.0'
|
||||||
|
'@mdx-js/react': '>=0.15.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@mdx-js/loader':
|
||||||
|
optional: true
|
||||||
|
'@mdx-js/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@next/swc-darwin-arm64@15.2.1-canary.5':
|
||||||
|
resolution: {integrity: sha512-k0bxBSOtGxqyJJQchFfeLP3wwjjlnPf+iGXK4pM2EoChDdyi7fGJgj/fPShx9qE7Wq/cJpHO0TjPXJ2eBiinpw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-darwin-x64@15.2.1-canary.4':
|
'@next/swc-darwin-x64@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-OC3JVKUP8hWsOXOe04WqYa/csyYsaaw1m62V2delOP24h1+CRw3lWXgED6oJ826E9clWOjCNWnksoAeT3JCnuw==}
|
resolution: {integrity: sha512-3QFli23ULFCQUUGDVHZz3PaKVfvT2BZAIBz3RwgayYHX9wBnwSTra/nVEyGMnO9W6v8Gj/c/R+Jc6LYYObDPPA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@15.2.1-canary.4':
|
'@next/swc-linux-arm64-gnu@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-Zc3VpiC3KnxiMiR+8Npy2M3s1Sw0L1YyzvLWcBmAjDqovvXGwosw8biPx817CdzY8fHphA/FMhVMLzTKk68uhQ==}
|
resolution: {integrity: sha512-LOdxWwj5XW7jIDCZeo/mXzmqQlFeYBYL1WOgOL+xdTatJpcplLIpVL1QE8ejj65Z9WTEfDqh29ApraPXUT2wWQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@15.2.1-canary.4':
|
'@next/swc-linux-arm64-musl@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-DS00J2xMLcPUBvkIRGJPLeAuwu9SrL+Sh+vRa3ci4AGL2o3X6qNup+L6M33/KwJMWFOic5XSIRC22lK3k+uAPg==}
|
resolution: {integrity: sha512-SvyYoEWS9RQAoGsSQBYKjRXjgxtjiL2IT+AYFaZUSyOpJeQvanMyb/dWdn3QtnXdo+1RR9+Wo8o3uqFFcsduXw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@15.2.1-canary.4':
|
'@next/swc-linux-x64-gnu@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-TFkq6dzG01T0ozMrA/oIXBJ5vFMjL407HfwIXP1vEwsRnbC06FjV8LPhfZNpkYUHrrIbrK6KJlCq2OKxoAc08Q==}
|
resolution: {integrity: sha512-NRO3kmemLcMXupZITUahHg3OzU7G0KThHtJ6zNcXpTl3g8xT5AcSEgqJ18fxim+9nSZYaLUnv59qNAGS/kLzyA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@15.2.1-canary.4':
|
'@next/swc-linux-x64-musl@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-7pnsx7u1fMlTmZfUCOfrSGPqUDkR3c6PHoCBJJGSnNw1KeFI2jtF9cQyZNUVyRl/ah7uFPV81Fop3rkaLPYsMA==}
|
resolution: {integrity: sha512-VVfvSvGd0tI83Atuuk909NH8HON5+hp/0fihfG8ZcD8SvW0XJD0x25Ov5MO7RECLABS3NTupTXKNoP2KN0EksQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@15.2.1-canary.4':
|
'@next/swc-win32-arm64-msvc@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-hKf3YU+m253RWlzRKzKYFlS2qasvAfiTctN0iMxUSeRAsL8/2GOUE4boY/HLJWMKlnFOvyI2medUl4In894w4g==}
|
resolution: {integrity: sha512-jAUQhmtSkgkPhq0le3G4H417t+xoDdrsy6RztzsWDlTGuzja+BWWorY+IDEDapI3+sy2p32DQwNUjrIyZWMffg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@15.2.1-canary.4':
|
'@next/swc-win32-x64-msvc@15.2.1-canary.5':
|
||||||
resolution: {integrity: sha512-fTZMwPFmNv3xxD9DtB8VPUBDSYAHj01xOr3PKW2ccF+4VzihTtHZFVtbT6j5qJE59kvV7KvrtTg/rKqKJUQaLQ==}
|
resolution: {integrity: sha512-4rCmcJ54QBj00PFyKTVcIeLcBB2GFss4pJ+/0LYAGtMBy5i6SV9YtrJTo6IyDVi4j1MB5qU1WXkAA3ZvAQr/yA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@ -820,8 +860,8 @@ packages:
|
|||||||
'@types/nlcst@2.0.3':
|
'@types/nlcst@2.0.3':
|
||||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||||
|
|
||||||
'@types/node@22.13.8':
|
'@types/node@22.13.9':
|
||||||
resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==}
|
resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==}
|
||||||
|
|
||||||
'@types/prismjs@1.26.5':
|
'@types/prismjs@1.26.5':
|
||||||
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
||||||
@ -1383,8 +1423,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
eslint-config-next@15.2.1-canary.4:
|
eslint-config-next@15.2.1-canary.5:
|
||||||
resolution: {integrity: sha512-pi+vcE8XwbagVr2ISeUXhDcR8nti2m5k3U/ntMMPwNGGSX3Bm76tgrpgVR288RINH+ur/v8oYpOz6CD0bW1HuQ==}
|
resolution: {integrity: sha512-nLiwL5f34WZc/PC8ZDX57xOchwd3u073ihonkbVVqr68ZymHDANYt5VMBJbZEHYaJwLDKuFbg2oRVXmCOjAloA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
|
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
|
||||||
typescript: '>=3.3.1'
|
typescript: '>=3.3.1'
|
||||||
@ -1557,6 +1597,9 @@ packages:
|
|||||||
estree-util-to-js@2.0.0:
|
estree-util-to-js@2.0.0:
|
||||||
resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==}
|
resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==}
|
||||||
|
|
||||||
|
estree-util-value-to-estree@3.3.2:
|
||||||
|
resolution: {integrity: sha512-hYH1aSvQI63Cvq3T3loaem6LW4u72F187zW4FHpTrReJSm6W66vYTFNO1vH/chmcOulp1HlAj1pxn8Ag0oXI5Q==}
|
||||||
|
|
||||||
estree-util-visit@2.0.0:
|
estree-util-visit@2.0.0:
|
||||||
resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==}
|
resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==}
|
||||||
|
|
||||||
@ -1610,6 +1653,9 @@ packages:
|
|||||||
fastq@1.19.0:
|
fastq@1.19.0:
|
||||||
resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
|
resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
|
||||||
|
|
||||||
|
fault@2.0.1:
|
||||||
|
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
||||||
|
|
||||||
fdir@6.4.3:
|
fdir@6.4.3:
|
||||||
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
|
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1649,6 +1695,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
format@0.2.2:
|
||||||
|
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
|
||||||
|
engines: {node: '>=0.4.x'}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@ -1794,17 +1844,20 @@ packages:
|
|||||||
hast-util-parse-selector@4.0.0:
|
hast-util-parse-selector@4.0.0:
|
||||||
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
|
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
|
||||||
|
|
||||||
|
hast-util-properties-to-mdx-jsx-attributes@1.0.1:
|
||||||
|
resolution: {integrity: sha512-ZzxhjHZ+gyxaPIFp/nuRpVL4GIFoqzfH6vNgjaA3CuUAV6XCxYwAQfRczrZRkgL6msi6DdOl+/QEduOdzszvbg==}
|
||||||
|
|
||||||
hast-util-sanitize@5.0.2:
|
hast-util-sanitize@5.0.2:
|
||||||
resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==}
|
resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==}
|
||||||
|
|
||||||
hast-util-to-estree@3.1.1:
|
hast-util-to-estree@3.1.2:
|
||||||
resolution: {integrity: sha512-IWtwwmPskfSmma9RpzCappDUitC8t5jhAynHhc1m2+5trOgsrp7txscUSavc5Ic8PATyAjfrCK1wgtxh2cICVQ==}
|
resolution: {integrity: sha512-94SDoKOfop5gP8RHyw4vV1aj+oChuD42g08BONGAaWFbbO6iaWUqxk7SWfGybgcVzhK16KifZr3zD2dqQgx3jQ==}
|
||||||
|
|
||||||
hast-util-to-html@9.0.4:
|
hast-util-to-html@9.0.4:
|
||||||
resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==}
|
resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==}
|
||||||
|
|
||||||
hast-util-to-jsx-runtime@2.3.2:
|
hast-util-to-jsx-runtime@2.3.5:
|
||||||
resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==}
|
resolution: {integrity: sha512-gHD+HoFxOMmmXLuq9f2dZDMQHVcplCVpMfBNRpJsF03yyLZvJGzsFORe8orVuYDX9k2w0VH0uF8oryFd1whqKQ==}
|
||||||
|
|
||||||
hast-util-to-string@3.0.1:
|
hast-util-to-string@3.0.1:
|
||||||
resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
|
resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
|
||||||
@ -2196,6 +2249,9 @@ packages:
|
|||||||
mdast-util-from-markdown@2.0.2:
|
mdast-util-from-markdown@2.0.2:
|
||||||
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
|
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
|
||||||
|
|
||||||
|
mdast-util-frontmatter@2.0.1:
|
||||||
|
resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==}
|
||||||
|
|
||||||
mdast-util-gfm-autolink-literal@2.0.1:
|
mdast-util-gfm-autolink-literal@2.0.1:
|
||||||
resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
|
resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
|
||||||
|
|
||||||
@ -2251,6 +2307,9 @@ packages:
|
|||||||
micromark-core-commonmark@2.0.2:
|
micromark-core-commonmark@2.0.2:
|
||||||
resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==}
|
resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==}
|
||||||
|
|
||||||
|
micromark-extension-frontmatter@2.0.0:
|
||||||
|
resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==}
|
||||||
|
|
||||||
micromark-extension-gfm-autolink-literal@2.1.0:
|
micromark-extension-gfm-autolink-literal@2.1.0:
|
||||||
resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
|
resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
|
||||||
|
|
||||||
@ -2413,8 +2472,8 @@ packages:
|
|||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
next@15.2.1-canary.4:
|
next@15.2.1-canary.5:
|
||||||
resolution: {integrity: sha512-0Iq5I+s7lPtERoa7DONfkbA//M5/3x++g9lcJXlJe/QguO/jw/Dan4s8v+o7ydOiLrOjVbs49dtAtURA4uXj5Q==}
|
resolution: {integrity: sha512-81GoYGskkuhneyVuEPH23ast8tTE/aVuhp9Edo4W4+fwHgiidO47ZCx7f+h7aZ6FVaffPOFqYlmYOMaEAUeF3A==}
|
||||||
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2566,6 +2625,9 @@ packages:
|
|||||||
parse-numeric-range@1.3.0:
|
parse-numeric-range@1.3.0:
|
||||||
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
|
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
|
||||||
|
|
||||||
|
parse-srcset@1.0.2:
|
||||||
|
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
|
||||||
|
|
||||||
parse5@7.2.1:
|
parse5@7.2.1:
|
||||||
resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==}
|
resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==}
|
||||||
|
|
||||||
@ -2634,8 +2696,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
prettier@3.5.2:
|
prettier@3.5.3:
|
||||||
resolution: {integrity: sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==}
|
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@ -2671,6 +2733,9 @@ packages:
|
|||||||
property-information@6.5.0:
|
property-information@6.5.0:
|
||||||
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||||
|
|
||||||
|
property-information@7.0.0:
|
||||||
|
resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
|
||||||
|
|
||||||
proto-list@1.2.4:
|
proto-list@1.2.4:
|
||||||
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
||||||
|
|
||||||
@ -2774,6 +2839,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
rehype-mdx-import-media@1.2.0:
|
||||||
|
resolution: {integrity: sha512-rf+2qnPv3LTqLtCr8GjhHUja2TEbmwWtD1o4jigrmGWbVDggOMxyNeqJhGpC4E3vtH+sY+a+u9WPSEaskEWPFA==}
|
||||||
|
|
||||||
rehype-parse@9.0.1:
|
rehype-parse@9.0.1:
|
||||||
resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==}
|
resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==}
|
||||||
|
|
||||||
@ -2795,6 +2863,9 @@ packages:
|
|||||||
rehype-unwrap-images@1.0.0:
|
rehype-unwrap-images@1.0.0:
|
||||||
resolution: {integrity: sha512-wzW5Mk9IlVF2UwXC5NtIZsx1aHYbV8+bLWjJnlZaaamz5QU52RppWtq1uEZJqGo8d9Y4RuDqidB6r9RFpKugIg==}
|
resolution: {integrity: sha512-wzW5Mk9IlVF2UwXC5NtIZsx1aHYbV8+bLWjJnlZaaamz5QU52RppWtq1uEZJqGo8d9Y4RuDqidB6r9RFpKugIg==}
|
||||||
|
|
||||||
|
remark-frontmatter@5.0.0:
|
||||||
|
resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==}
|
||||||
|
|
||||||
remark-gfm@4.0.1:
|
remark-gfm@4.0.1:
|
||||||
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||||
|
|
||||||
@ -3077,6 +3148,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
style-to-js@1.1.16:
|
||||||
|
resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==}
|
||||||
|
|
||||||
style-to-object@1.0.8:
|
style-to-object@1.0.8:
|
||||||
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
|
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
|
||||||
|
|
||||||
@ -3632,6 +3706,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@lit-labs/ssr-dom-shim': 1.3.0
|
'@lit-labs/ssr-dom-shim': 1.3.0
|
||||||
|
|
||||||
|
'@mdx-js/loader@3.1.0(acorn@8.14.0)':
|
||||||
|
dependencies:
|
||||||
|
'@mdx-js/mdx': 3.1.0(acorn@8.14.0)
|
||||||
|
source-map: 0.7.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- acorn
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@mdx-js/mdx@3.1.0(acorn@8.14.0)':
|
'@mdx-js/mdx@3.1.0(acorn@8.14.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
@ -3643,7 +3725,7 @@ snapshots:
|
|||||||
estree-util-is-identifier-name: 3.0.0
|
estree-util-is-identifier-name: 3.0.0
|
||||||
estree-util-scope: 1.0.0
|
estree-util-scope: 1.0.0
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
hast-util-to-jsx-runtime: 2.3.2
|
hast-util-to-jsx-runtime: 2.3.5
|
||||||
markdown-extensions: 2.0.0
|
markdown-extensions: 2.0.0
|
||||||
recma-build-jsx: 1.0.0
|
recma-build-jsx: 1.0.0
|
||||||
recma-jsx: 1.0.0(acorn@8.14.0)
|
recma-jsx: 1.0.0(acorn@8.14.0)
|
||||||
@ -3662,41 +3744,54 @@ snapshots:
|
|||||||
- acorn
|
- acorn
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@next/bundle-analyzer@15.2.1-canary.4':
|
'@mdx-js/react@3.1.0(@types/react@19.0.10)(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@types/mdx': 2.0.13
|
||||||
|
'@types/react': 19.0.10
|
||||||
|
react: 19.0.0
|
||||||
|
|
||||||
|
'@next/bundle-analyzer@15.2.1-canary.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
webpack-bundle-analyzer: 4.10.1
|
webpack-bundle-analyzer: 4.10.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
'@next/env@15.2.1-canary.4': {}
|
'@next/env@15.2.1-canary.5': {}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.2.1-canary.4':
|
'@next/eslint-plugin-next@15.2.1-canary.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-glob: 3.3.1
|
fast-glob: 3.3.1
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@15.2.1-canary.4':
|
'@next/mdx@15.2.1-canary.5(@mdx-js/loader@3.1.0(acorn@8.14.0))(@mdx-js/react@3.1.0(@types/react@19.0.10)(react@19.0.0))':
|
||||||
|
dependencies:
|
||||||
|
source-map: 0.7.4
|
||||||
|
optionalDependencies:
|
||||||
|
'@mdx-js/loader': 3.1.0(acorn@8.14.0)
|
||||||
|
'@mdx-js/react': 3.1.0(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
|
||||||
|
'@next/swc-darwin-arm64@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-darwin-x64@15.2.1-canary.4':
|
'@next/swc-darwin-x64@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@15.2.1-canary.4':
|
'@next/swc-linux-arm64-gnu@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@15.2.1-canary.4':
|
'@next/swc-linux-arm64-musl@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@15.2.1-canary.4':
|
'@next/swc-linux-x64-gnu@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@15.2.1-canary.4':
|
'@next/swc-linux-x64-musl@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@15.2.1-canary.4':
|
'@next/swc-win32-arm64-msvc@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@15.2.1-canary.4':
|
'@next/swc-win32-x64-msvc@15.2.1-canary.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
@ -3897,7 +3992,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/concat-stream@2.0.3':
|
'@types/concat-stream@2.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.8
|
'@types/node': 22.13.9
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3939,7 +4034,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
'@types/node@22.13.8':
|
'@types/node@22.13.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.20.0
|
undici-types: 6.20.0
|
||||||
|
|
||||||
@ -4046,9 +4141,9 @@ snapshots:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
'@vercel/analytics@1.5.0(next@15.2.1-canary.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
'@vercel/analytics@1.5.0(next@15.2.1-canary.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
next: 15.2.1-canary.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
next: 15.2.1-canary.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
react: 19.0.0
|
react: 19.0.0
|
||||||
|
|
||||||
abbrev@2.0.0: {}
|
abbrev@2.0.0: {}
|
||||||
@ -4592,9 +4687,9 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@5.0.0: {}
|
escape-string-regexp@5.0.0: {}
|
||||||
|
|
||||||
eslint-config-next@15.2.1-canary.4(eslint@9.21.0)(typescript@5.7.3):
|
eslint-config-next@15.2.1-canary.5(eslint@9.21.0)(typescript@5.7.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 15.2.1-canary.4
|
'@next/eslint-plugin-next': 15.2.1-canary.5
|
||||||
'@rushstack/eslint-patch': 1.10.5
|
'@rushstack/eslint-patch': 1.10.5
|
||||||
'@typescript-eslint/eslint-plugin': 8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.21.0)(typescript@5.7.3))(eslint@9.21.0)(typescript@5.7.3)
|
'@typescript-eslint/eslint-plugin': 8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.21.0)(typescript@5.7.3))(eslint@9.21.0)(typescript@5.7.3)
|
||||||
'@typescript-eslint/parser': 8.25.0(eslint@9.21.0)(typescript@5.7.3)
|
'@typescript-eslint/parser': 8.25.0(eslint@9.21.0)(typescript@5.7.3)
|
||||||
@ -4741,10 +4836,10 @@ snapshots:
|
|||||||
- bluebird
|
- bluebird
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-prettier@5.2.3(eslint-config-prettier@10.0.2(eslint@9.21.0))(eslint@9.21.0)(prettier@3.5.2):
|
eslint-plugin-prettier@5.2.3(eslint-config-prettier@10.0.2(eslint@9.21.0))(eslint@9.21.0)(prettier@3.5.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.21.0
|
eslint: 9.21.0
|
||||||
prettier: 3.5.2
|
prettier: 3.5.3
|
||||||
prettier-linter-helpers: 1.0.0
|
prettier-linter-helpers: 1.0.0
|
||||||
synckit: 0.9.2
|
synckit: 0.9.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@ -4872,6 +4967,10 @@ snapshots:
|
|||||||
astring: 1.9.0
|
astring: 1.9.0
|
||||||
source-map: 0.7.4
|
source-map: 0.7.4
|
||||||
|
|
||||||
|
estree-util-value-to-estree@3.3.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.6
|
||||||
|
|
||||||
estree-util-visit@2.0.0:
|
estree-util-visit@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree-jsx': 1.0.5
|
'@types/estree-jsx': 1.0.5
|
||||||
@ -4935,6 +5034,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.0.4
|
reusify: 1.0.4
|
||||||
|
|
||||||
|
fault@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
format: 0.2.2
|
||||||
|
|
||||||
fdir@6.4.3(picomatch@4.0.2):
|
fdir@6.4.3(picomatch@4.0.2):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.2
|
picomatch: 4.0.2
|
||||||
@ -4972,6 +5075,8 @@ snapshots:
|
|||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
|
format@0.2.2: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -5146,13 +5251,26 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hast-util-properties-to-mdx-jsx-attributes@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.6
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
estree-util-value-to-estree: 3.3.2
|
||||||
|
mdast-util-mdx-jsx: 3.2.0
|
||||||
|
property-information: 7.0.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
style-to-js: 1.1.16
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
hast-util-sanitize@5.0.2:
|
hast-util-sanitize@5.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
'@ungap/structured-clone': 1.3.0
|
'@ungap/structured-clone': 1.3.0
|
||||||
unist-util-position: 5.0.0
|
unist-util-position: 5.0.0
|
||||||
|
|
||||||
hast-util-to-estree@3.1.1:
|
hast-util-to-estree@3.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
'@types/estree-jsx': 1.0.5
|
'@types/estree-jsx': 1.0.5
|
||||||
@ -5165,7 +5283,7 @@ snapshots:
|
|||||||
mdast-util-mdx-expression: 2.0.1
|
mdast-util-mdx-expression: 2.0.1
|
||||||
mdast-util-mdx-jsx: 3.2.0
|
mdast-util-mdx-jsx: 3.2.0
|
||||||
mdast-util-mdxjs-esm: 2.0.1
|
mdast-util-mdxjs-esm: 2.0.1
|
||||||
property-information: 6.5.0
|
property-information: 7.0.0
|
||||||
space-separated-tokens: 2.0.2
|
space-separated-tokens: 2.0.2
|
||||||
style-to-object: 1.0.8
|
style-to-object: 1.0.8
|
||||||
unist-util-position: 5.0.0
|
unist-util-position: 5.0.0
|
||||||
@ -5187,7 +5305,7 @@ snapshots:
|
|||||||
stringify-entities: 4.0.4
|
stringify-entities: 4.0.4
|
||||||
zwitch: 2.0.4
|
zwitch: 2.0.4
|
||||||
|
|
||||||
hast-util-to-jsx-runtime@2.3.2:
|
hast-util-to-jsx-runtime@2.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
@ -5199,7 +5317,7 @@ snapshots:
|
|||||||
mdast-util-mdx-expression: 2.0.1
|
mdast-util-mdx-expression: 2.0.1
|
||||||
mdast-util-mdx-jsx: 3.2.0
|
mdast-util-mdx-jsx: 3.2.0
|
||||||
mdast-util-mdxjs-esm: 2.0.1
|
mdast-util-mdxjs-esm: 2.0.1
|
||||||
property-information: 6.5.0
|
property-information: 7.0.0
|
||||||
space-separated-tokens: 2.0.2
|
space-separated-tokens: 2.0.2
|
||||||
style-to-object: 1.0.8
|
style-to-object: 1.0.8
|
||||||
unist-util-position: 5.0.0
|
unist-util-position: 5.0.0
|
||||||
@ -5638,6 +5756,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
mdast-util-frontmatter@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/mdast': 4.0.4
|
||||||
|
devlop: 1.1.0
|
||||||
|
escape-string-regexp: 5.0.0
|
||||||
|
mdast-util-from-markdown: 2.0.2
|
||||||
|
mdast-util-to-markdown: 2.1.2
|
||||||
|
micromark-extension-frontmatter: 2.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
mdast-util-gfm-autolink-literal@2.0.1:
|
mdast-util-gfm-autolink-literal@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
@ -5802,6 +5931,13 @@ snapshots:
|
|||||||
micromark-util-symbol: 2.0.1
|
micromark-util-symbol: 2.0.1
|
||||||
micromark-util-types: 2.0.1
|
micromark-util-types: 2.0.1
|
||||||
|
|
||||||
|
micromark-extension-frontmatter@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
fault: 2.0.1
|
||||||
|
micromark-util-character: 2.1.1
|
||||||
|
micromark-util-symbol: 2.0.1
|
||||||
|
micromark-util-types: 2.0.1
|
||||||
|
|
||||||
micromark-extension-gfm-autolink-literal@2.1.0:
|
micromark-extension-gfm-autolink-literal@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
micromark-util-character: 2.1.1
|
micromark-util-character: 2.1.1
|
||||||
@ -6097,9 +6233,9 @@ snapshots:
|
|||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
next@15.2.1-canary.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
next@15.2.1-canary.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 15.2.1-canary.4
|
'@next/env': 15.2.1-canary.5
|
||||||
'@swc/counter': 0.1.3
|
'@swc/counter': 0.1.3
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
@ -6109,14 +6245,14 @@ snapshots:
|
|||||||
react-dom: 19.0.0(react@19.0.0)
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
styled-jsx: 5.1.6(react@19.0.0)
|
styled-jsx: 5.1.6(react@19.0.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 15.2.1-canary.4
|
'@next/swc-darwin-arm64': 15.2.1-canary.5
|
||||||
'@next/swc-darwin-x64': 15.2.1-canary.4
|
'@next/swc-darwin-x64': 15.2.1-canary.5
|
||||||
'@next/swc-linux-arm64-gnu': 15.2.1-canary.4
|
'@next/swc-linux-arm64-gnu': 15.2.1-canary.5
|
||||||
'@next/swc-linux-arm64-musl': 15.2.1-canary.4
|
'@next/swc-linux-arm64-musl': 15.2.1-canary.5
|
||||||
'@next/swc-linux-x64-gnu': 15.2.1-canary.4
|
'@next/swc-linux-x64-gnu': 15.2.1-canary.5
|
||||||
'@next/swc-linux-x64-musl': 15.2.1-canary.4
|
'@next/swc-linux-x64-musl': 15.2.1-canary.5
|
||||||
'@next/swc-win32-arm64-msvc': 15.2.1-canary.4
|
'@next/swc-win32-arm64-msvc': 15.2.1-canary.5
|
||||||
'@next/swc-win32-x64-msvc': 15.2.1-canary.4
|
'@next/swc-win32-x64-msvc': 15.2.1-canary.5
|
||||||
sharp: 0.33.5
|
sharp: 0.33.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
@ -6291,6 +6427,8 @@ snapshots:
|
|||||||
|
|
||||||
parse-numeric-range@1.3.0: {}
|
parse-numeric-range@1.3.0: {}
|
||||||
|
|
||||||
|
parse-srcset@1.0.2: {}
|
||||||
|
|
||||||
parse5@7.2.1:
|
parse5@7.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
entities: 4.5.0
|
entities: 4.5.0
|
||||||
@ -6346,7 +6484,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff: 1.3.0
|
fast-diff: 1.3.0
|
||||||
|
|
||||||
prettier@3.5.2: {}
|
prettier@3.5.3: {}
|
||||||
|
|
||||||
prisma@6.4.1(typescript@5.7.3):
|
prisma@6.4.1(typescript@5.7.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6376,6 +6514,8 @@ snapshots:
|
|||||||
|
|
||||||
property-information@6.5.0: {}
|
property-information@6.5.0: {}
|
||||||
|
|
||||||
|
property-information@7.0.0: {}
|
||||||
|
|
||||||
proto-list@1.2.4: {}
|
proto-list@1.2.4: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
@ -6509,6 +6649,16 @@ snapshots:
|
|||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
set-function-name: 2.0.2
|
set-function-name: 2.0.2
|
||||||
|
|
||||||
|
rehype-mdx-import-media@1.2.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
hast-util-properties-to-mdx-jsx-attributes: 1.0.1
|
||||||
|
parse-srcset: 1.0.2
|
||||||
|
unified: 11.0.5
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
rehype-parse@9.0.1:
|
rehype-parse@9.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
@ -6528,7 +6678,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
hast-util-to-estree: 3.1.1
|
hast-util-to-estree: 3.1.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -6558,6 +6708,15 @@ snapshots:
|
|||||||
hast-util-whitespace: 3.0.0
|
hast-util-whitespace: 3.0.0
|
||||||
unist-util-visit: 5.0.0
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
remark-frontmatter@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/mdast': 4.0.4
|
||||||
|
mdast-util-frontmatter: 2.0.1
|
||||||
|
micromark-extension-frontmatter: 2.0.0
|
||||||
|
unified: 11.0.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
remark-gfm@4.0.1:
|
remark-gfm@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
@ -6948,6 +7107,10 @@ snapshots:
|
|||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
|
style-to-js@1.1.16:
|
||||||
|
dependencies:
|
||||||
|
style-to-object: 1.0.8
|
||||||
|
|
||||||
style-to-object@1.0.8:
|
style-to-object@1.0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
inline-style-parser: 0.2.4
|
inline-style-parser: 0.2.4
|
||||||
@ -7065,7 +7228,7 @@ snapshots:
|
|||||||
'@types/concat-stream': 2.0.3
|
'@types/concat-stream': 2.0.3
|
||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
'@types/is-empty': 1.2.3
|
'@types/is-empty': 1.2.3
|
||||||
'@types/node': 22.13.8
|
'@types/node': 22.13.9
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
concat-stream: 2.0.0
|
concat-stream: 2.0.0
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user