29 Commits

Author SHA1 Message Date
8edad84796 Clean up errors for URL import 2023-07-22 03:32:15 +00:00
b74cf41713 Remove unnecessary backup flames 2023-07-22 03:28:52 +00:00
342e4ee6cf Implement offline rendering, default to pre-render 2023-07-22 03:26:54 +00:00
04d5099338 Implement everything in terms of an incremental render 2023-07-20 23:55:26 +00:00
7f44243cd0 Convert baseline renderer to animation 2023-07-18 02:21:01 +00:00
782b00320b Minor cleanup 2023-07-17 02:47:53 +00:00
01af290363 Start on animations so I don't kill the browser 2023-07-17 02:27:45 +00:00
458920b1ae Sierpinski Gasket in flam3 2023-07-16 16:45:54 -04:00
90ac3559ae Implement color 2023-07-15 22:48:06 +00:00
ea946b2ae9 Fix transparency for black-and-white rendering 2023-07-07 20:46:28 +00:00
ce4fdd154a Add solo transforms 2023-07-04 18:25:31 +00:00
f3c463360e Show all progress so far on the same page 2023-07-03 00:01:51 +00:00
de903e1617 Minor comment update 2023-07-02 23:55:55 +00:00
671b97b7a0 Refactor, fix julia bug, implement binary/linear/logarithmic membership 2023-07-02 19:35:42 -04:00
e8ec0e0521 More refactoring 2023-07-02 19:49:36 +00:00
79394519c7 Refactor to use a step function 2023-07-02 19:30:56 +00:00
4bcc321c4b Get full post/final xform working 2023-07-01 04:38:15 +00:00
68c0b26015 Thu Jun 29 19:37:01 EDT 2023 2023-06-29 19:37:01 -04:00
0a7323a8c9 Baseline render running 2023-06-26 01:56:33 +00:00
8e1c7725c1 feat: switch to frontmatter
The intention is to build an RSS generator eventually
2023-05-12 11:04:55 +00:00
65d58c7f7d remove unused imports 2023-04-23 21:49:32 +00:00
7894cf8c81 remove custom rehype highlight plugin 2023-04-23 21:44:49 +00:00
dff82d018b math and more icons 2023-04-23 21:15:48 +00:00
11b70a6da8 fix font sizing 2023-04-22 03:26:57 +00:00
c32052e9e1 fix some CSS problems
The current site doesn't handle highlighting code links properly
2023-04-22 03:13:35 +00:00
b2ff9535c9 switch back to fontawesome to fix SSR 2023-04-22 03:03:34 +00:00
b7a6971174 different icons package 2023-04-22 02:23:19 +00:00
2d3b9c0c3a add a calendar icon 2023-04-22 01:50:43 +00:00
3c80fbab21 tweak styling 2023-04-22 01:40:15 +00:00
41 changed files with 2633 additions and 814 deletions

1277
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,21 +12,31 @@
"dependencies": {
"@fontsource/jetbrains-mono": "^4.5.12",
"@fontsource/lato": "^4.5.10",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@mdx-js/react": "^2.3.0",
"highlight.js": "^11.7.0",
"katex": "^0.16.6",
"normalize.css": "^8.0.1",
"prism-themes": "^1.9.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"remark-frontmatter": "^4.0.1"
},
"devDependencies": {
"@bspeice/vite-plugin-blog": "^1.1.0",
"@mdx-js/rollup": "^2.3.0",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/remark-prism": "^1.3.4",
"@vitejs/plugin-react-swc": "^3.0.0",
"husky": "^8.0.0",
"pretty-quick": "^3.1.3",
"remark-prism": "^1.3.6",
"rehype-highlight": "^6.0.0",
"rehype-katex": "^6.0.3",
"remark-math": "^5.1.1",
"remark-mdx-frontmatter": "^3.0.0",
"typescript": "^4.9.3",
"vite": "^4.2.0"
}

View File

@ -1,20 +1,16 @@
import { PropsWithChildren } from "react";
import { PropsWithChildren, StrictMode } from "react";
import "./style.css";
const Sidebar: React.FC = () => (
<span className={"navbar"}>
<a href="/">Home</a>/<a href="/about">About</a>
</span>
);
import Navbar from "./Navbar";
const Layout: React.FC<PropsWithChildren> = ({ children }) => (
<StrictMode>
<div className="gridOffset">
<div className="gridOffsetSide">
<Sidebar />
</div>
<Navbar />
<hr style={{ marginTop: "0" }} />
{children}
</div>
</StrictMode>
);
export default Layout;

18
pages/Navbar.tsx Normal file
View File

@ -0,0 +1,18 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHome, faUser } from "@fortawesome/free-solid-svg-icons";
const Navbar: React.FC = () => (
<span className="navbar">
<a href="/">
<FontAwesomeIcon icon={faHome} className="icon" />
Home
</a>
<span>/</span>
<a href="/about">
<FontAwesomeIcon icon={faUser} className="icon" />
About
</a>
</span>
);
export default Navbar;

View File

@ -1,8 +1,23 @@
import Layout from "./Page";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGithub, faLinkedin } from "@fortawesome/free-brands-svg-icons";
import { faEnvelope } from "@fortawesome/free-solid-svg-icons";
import Layout from "./LayoutPage";
export default Layout;
Developer currently living in New York City
Developer living in New York City
Email: [bradlee@speice.io](mailto:bradlee@speice.io)
LinkedIn: [bradleespeice](https://www.linkedin.com/in/bradleespeice/)
> <FontAwesomeIcon
> icon={faEnvelope}
> style={{ color: "var(--color-secondary)" }}
> /> [bradlee@speice.io](mailto:bradlee@speice.io)
>
> <FontAwesomeIcon
> icon={faGithub}
> style={{ color: "var(--color-secondary)" }}
> /> [bspeice](https://github.com/bspeice)
>
> <FontAwesomeIcon
> icon={faLinkedin}
> style={{ color: "var(--color-secondary)" }}
> /> [bradleespeice](https://www.linkedin.com/in/bradleespeice/)

View File

@ -1,9 +1,18 @@
import Layout from "./LayoutPage";
export const Page = () => (
<>
<p>Is this thing on?</p>
<p>
<a href="/2023/06/flam3">Code</a>
</p>
</>
);
export default function () {
return (
<Layout>
<p>Is this thing on?</p>
<Page />
</Layout>
);
}

View File

@ -2,12 +2,23 @@
@import "@fontsource/lato";
@import "@fontsource/jetbrains-mono";
@import "prism-themes/themes/prism-material-dark";
@import "highlight.js/styles/atom-one-dark.css";
@import "katex/dist/katex.min.css";
:root {
--color-primary: #000;
--color-secondary: #777;
--color-tertiary: #999;
--color-highlight: #f4f4f4;
--color-primary-highlight: #fff;
--color-secondary-highlight: #999;
}
body {
font-family: "Lato", sans-serif;
font-size: 14pt;
line-height: 1.4;
font-size: 15pt;
line-height: 1.5;
}
h1,
@ -20,6 +31,20 @@ h6 {
margin-bottom: 0;
}
h3 {
color: var(--color-secondary);
}
h4,
h5,
h6 {
color: var(--color-tertiary);
}
a {
color: var(--color-secondary);
}
p,
ul {
margin-top: 0.5em;
@ -30,43 +55,76 @@ pre {
padding: 1em 0;
}
code,
code[class*="language-"] {
code {
font-family: "JetBrains Mono", monospace;
font-size: 13pt;
background-color: var(--color-highlight);
}
a,
a > code {
text-decoration: dotted;
border-color: var(--color-secondary);
border-bottom-style: dotted;
border-bottom-width: 1px;
}
a,
a > code {
transition: all ease 0.5s;
}
a:hover,
a:hover > code,
a > code:hover {
color: var(--color-primary-highlight);
background-color: var(--color-secondary-highlight);
border-bottom: none;
}
.gridOffset {
display: grid;
grid-template-columns:
[full-start] minmax(1em, 2fr)
[main-start] minmax(0, 45em) [main-end]
minmax(0, 1fr)
[side-start] minmax(0, 3fr) [side-end]
minmax(1em, 2fr) [full-end];
[main-start] minmax(0, 42em) [main-end]
minmax(0, 5fr) [full-end];
}
.gridOffset > :not(.gridOffsetSide) {
.gridOffset > * {
grid-column: main;
}
.gridOffset > div.remark-highlight,
.gridOffset > div.remark-highlight > pre {
.gridOffset hr {
display: inherit;
grid-column: main;
grid-template-columns: inherit;
border-style: solid;
border-color: var(--color-secondary);
}
.gridOffset > pre {
display: inherit;
grid-column: full;
grid-template-columns: inherit;
}
.gridOffset > div.remark-highlight > pre > code {
grid-column: main;
.gridOffset > pre > code {
grid-column: main / full;
}
.gridOffsetSide {
grid-column: side;
margin-top: 1em;
margin-bottom: 1em;
.navbar {
text-align: right;
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.navbar > * {
margin-left: 0.5em;
margin-right: 0.5em;
.navbar :not(.icon) {
padding-left: 0.25em;
padding-right: 0.25em;
text-decoration: none;
border-bottom-style: none;
}
.icon {
margin-right: 0.3em;
}

View File

@ -1,9 +1,13 @@
---
title: "Global Memory Usage: The Whole World"
description: Static considered slightly less harmful.
published: 2019-02-05
---
import Blog from "../../LayoutBlog";
export default Blog({
title: "Global Memory Usage: The Whole World",
description: "Static considered slightly less harmful.",
published: "2019-02-05",
});
export default Blog(frontmatter);
$m_at^h$
The first memory type we'll look at is pretty special: when Rust can prove that a _value_ is fixed
for the life of a program (`const`), and when a _reference_ is unique for the life of a program

View File

@ -0,0 +1,117 @@
import React, { useEffect, useRef, useState } from "react";
import { DEFAULT_SIZE, RenderParams, Renderer } from "./0-utility.js";
import { paramsGasket } from "./1-gasket.js";
import { paramsBaseline } from "./2a-baseline.js";
import { paramsPost } from "./2b-post.js";
import { paramsFinal } from "./2c-final.js";
import { paramsBinary } from "./3a-binary.js";
import { paramsLinear } from "./3b-linear.js";
import { paramsLogarithmic } from "./3c-logarithmic.js";
import { paramsColor } from "./4-color.js";
import { paramsGasketFlame } from "./5a-gasket.js";
import { paramsSolo1, paramsSolo2, paramsSolo3 } from "./5b-solo.js";
// @ts-expect-error: Vite URL import
import urlGasket from "./images/1-gasket.png";
// @ts-expect-error: Vite URL import
import urlBaseline from "./images/2a-baseline.png";
// @ts-expect-error: Vite URL import
import urlPost from "./images/2b-post.png";
// @ts-expect-error: Vite URL import
import urlFinal from "./images/2c-final.png";
// @ts-expect-error: Vite URL import
import urlBinary from "./images/3a-binary.png";
// @ts-expect-error: Vite URL import
import urlLinear from "./images/3b-linear.png";
// @ts-expect-error: Vite URL import
import urlLogarithmic from "./images/3c-logarithmic.png";
// @ts-expect-error: Vite URL import
import urlColor from "./images/4-color.png";
// @ts-expect-error: Vite URL import
import urlGasketFlame from "./images/5a-gasket.png";
// @ts-expect-error: Vite URL import
import urlSolo1 from "./images/5b-solo1.png";
// @ts-expect-error: Vite URL import
import urlSolo2 from "./images/5b-solo2.png";
// @ts-expect-error: Vite URL import
import urlSolo3 from "./images/5b-solo3.png";
export const DEFAULT_STEP: number = 0.1;
export type CanvasParams = RenderParams & { url: string };
export const CanvasRenderer: React.FC<CanvasParams> = (params) => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [useUrl, setUseUrl] = useState(true);
var qualityCurrent: number = 0;
var rendererCurrent: Renderer = params.renderer(DEFAULT_SIZE);
const animate = () => {
const ctx = canvasRef.current?.getContext("2d");
if (!ctx) {
console.log("Ref not ready");
requestAnimationFrame(animate);
return;
}
const image = ctx.createImageData(DEFAULT_SIZE, DEFAULT_SIZE);
rendererCurrent.run(DEFAULT_STEP);
rendererCurrent.render(image);
ctx.putImageData(image, 0, 0);
if (qualityCurrent < params.quality) {
qualityCurrent += DEFAULT_STEP;
requestAnimationFrame(animate);
}
};
const useCanvas = () => {
setUseUrl(false);
requestAnimationFrame(animate);
};
const reset = () => {
qualityCurrent = 0;
rendererCurrent = params.renderer(DEFAULT_SIZE);
requestAnimationFrame(animate);
};
return (
<>
<img src={params.url} onClick={useCanvas} hidden={!useUrl} />
<canvas
ref={canvasRef}
width={DEFAULT_SIZE}
height={DEFAULT_SIZE}
onClick={reset}
hidden={useUrl}
/>
</>
);
};
export const CanvasGasket: React.FC<{}> = () =>
CanvasRenderer({ url: urlGasket, ...paramsGasket });
export const CanvasBaseline: React.FC<{}> = () =>
CanvasRenderer({ url: urlBaseline, ...paramsBaseline });
export const CanvasPost: React.FC<{}> = () =>
CanvasRenderer({ url: urlPost, ...paramsPost });
export const CanvasFinal: React.FC<{}> = () =>
CanvasRenderer({ url: urlFinal, ...paramsFinal });
export const CanvasBinary: React.FC<{}> = () =>
CanvasRenderer({ url: urlBinary, ...paramsBinary });
export const CanvasLinear: React.FC<{}> = () =>
CanvasRenderer({ url: urlLinear, ...paramsLinear });
export const CanvasLogarithmic: React.FC<{}> = () =>
CanvasRenderer({ url: urlLogarithmic, ...paramsLogarithmic });
export const CanvasColor: React.FC<{}> = () =>
CanvasRenderer({ url: urlColor, ...paramsColor });
export const CanvasGasketFlame: React.FC<{}> = () =>
CanvasRenderer({ url: urlGasketFlame, ...paramsGasketFlame });
export const CanvasSolo1: React.FC<{}> = () =>
CanvasRenderer({ url: urlSolo1, ...paramsSolo1 });
export const CanvasSolo2: React.FC<{}> = () =>
CanvasRenderer({ url: urlSolo2, ...paramsSolo2 });
export const CanvasSolo3: React.FC<{}> = () =>
CanvasRenderer({ url: urlSolo3, ...paramsSolo3 });

View File

@ -0,0 +1,96 @@
export const DEFAULT_SIZE: number = 400;
/**
* Image render manager
*
* This class tracks the chaos game state so we can periodically
* get an image.
*/
export abstract class Renderer {
/**
* Build a render manager. For simplicity, this class assumes
* we're working with a square image.
*
* @param size Image width and height
*/
constructor(public readonly size: number) {}
/**
* Run the chaos game
*
* @param quality iteration count
*/
abstract run(quality: number): void;
/**
* Output the current chaos game state to image
*
* @param image output pixel buffer
*/
abstract render(image: ImageData): void;
}
export type RenderParams = {
quality: number;
renderer: (size: number) => Renderer;
};
/**
* @returns random number in the bi-unit square (-1, 1)
*/
export function randomBiUnit() {
// Math.random() produces a number in the range [0, 1),
// scale to (-1, 1)
return Math.random() * 2 - 1;
}
/**
* @returns random integer (with equal weight) in the range [min, max)
*/
export function randomInteger(min: number, max: number) {
return Math.floor(Math.random() * (max - min)) + min;
}
/**
* @param choices array of [weight, value] pairs
* @returns pair of [index, value]
*/
export function weightedChoice<T>(choices: [number, T][]): [number, T] {
const weightSum = choices.reduce(
(current, [weight, _t]) => current + weight,
0
);
var choice = Math.random() * weightSum;
for (var i = 0; i < choices.length; i++) {
const [weight, t] = choices[i];
if (choice < weight) {
return [i, t];
}
choice -= weight;
}
throw "unreachable";
}
/**
* @param x pixel coordinate
* @param y pixel coordinate
* @param width image width
* @returns index into ImageData buffer for a specific pixel
*/
export function imageIndex(x: number, y: number, width: number) {
// Taken from: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
return y * (width * 4) + x * 4;
}
/**
* @param x pixel coordinate
* @param y pixel coordinate
* @param width image width
* @returns index into a histogram for a specific pixel
*/
export function histIndex(x: number, y: number, width: number) {
return y * width + x;
}

View File

@ -0,0 +1,95 @@
import {
randomBiUnit,
randomInteger,
imageIndex,
Renderer,
histIndex,
RenderParams,
} from "./0-utility.js";
type Transform = (x: number, y: number) => [number, number];
export class RendererGasket extends Renderer {
private values = new Uint8Array(this.size * this.size);
private x = randomBiUnit();
private y = randomBiUnit();
/**
* Translate values in the flame coordinate system to pixel coordinates
*
* A trivial implementation would take the range [-1, 1], shift it to [0, 2],
* then scale by the image size:
* pixelX = Math.floor((x + 1) * this.size / 2)
* pixelY = Math.floor((y + 1) * this.size / 2)
*
* However, because the gasket solution set only has values in the range [0, 1],
* that would lead to wasting 3/4 of the pixels. We'll instead plot just the range
* we care about:
* pixelX = Math.floor(x * this.size)
* pixelY = Math.floor(x * this.size)
*
* @param x point in the range [-1, 1]
* @param y point in the range [-1, 1]
*/
plot(x: number, y: number): void {
var pixelX = Math.floor(x * this.size);
var pixelY = Math.floor(y * this.size);
if (
pixelX < 0 ||
pixelX >= this.size ||
pixelY < 0 ||
pixelY >= this.size
) {
return;
}
const index = histIndex(pixelX, pixelY, this.size);
this.values[index] = 1;
}
run(quality: number): void {
const transforms: Transform[] = [
(x, y) => [x / 2, y / 2],
(x, y) => [(x + 1) / 2, y / 2],
(x, y) => [x / 2, (y + 1) / 2],
];
// NOTE: `x` and `y` are set as fields on this class
// (rather than here in the main chaos game method) because
// we render in chunks (to avoid bogging down the browser)
const iterations = quality * this.size * this.size;
for (var i = 0; i < iterations; i++) {
const transformIndex = randomInteger(0, transforms.length);
[this.x, this.y] = transforms[transformIndex](this.x, this.y);
if (i >= 20) {
this.plot(this.x, this.y);
}
}
}
render(image: ImageData): void {
for (var pixelX = 0; pixelX < image.width; pixelX++) {
for (var pixelY = 0; pixelY < image.height; pixelY++) {
const hIndex = histIndex(pixelX, pixelY, this.size);
if (!this.values[hIndex]) {
continue;
}
// Set the pixel black
const iIndex = imageIndex(pixelX, pixelY, this.size);
image.data[iIndex + 0] = 0;
image.data[iIndex + 1] = 0;
image.data[iIndex + 2] = 0;
image.data[iIndex + 3] = 0xff;
}
}
}
}
export const paramsGasket: RenderParams = {
quality: 1,
renderer: (size) => new RendererGasket(size),
};

View File

@ -0,0 +1,223 @@
import {
RenderParams,
Renderer,
histIndex,
imageIndex,
randomBiUnit,
weightedChoice,
} from "./0-utility.js";
export type Variation = (
x: number,
y: number,
transformCoefs: Coefs
) => [number, number];
export type Coefs = {
a: number;
b: number;
c: number;
d: number;
e: number;
f: number;
};
export const identityCoefs = {
a: 1,
b: 0,
c: 0,
d: 0,
e: 1,
f: 0,
};
function r(x: number, y: number) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
}
function theta(x: number, y: number) {
return Math.atan2(x, y);
}
function omega(): number {
return Math.random() > 0.5 ? Math.PI : 0;
}
export const linear: Variation = (x, y) => [x, y];
export const julia: Variation = (x, y) => {
const sqrtR = Math.sqrt(r(x, y));
const thetaVal = theta(x, y) / 2 + omega();
return [sqrtR * Math.cos(thetaVal), sqrtR * Math.sin(thetaVal)];
};
export const popcorn: Variation = (x, y, transformCoefs) => {
return [
x + transformCoefs.c * Math.sin(Math.tan(3 * y)),
y + transformCoefs.f * Math.sin(Math.tan(3 * x)),
];
};
export const pdj: (
pdjA: number,
pdjB: number,
pdjC: number,
pdjD: number
) => Variation = (pdjA, pdjB, pdjC, pdjD) => {
return (x, y) => [
Math.sin(pdjA * y) - Math.cos(pdjB * x),
Math.sin(pdjC * x) - Math.cos(pdjD * y),
];
};
export class Transform {
constructor(
public readonly coefs: Coefs,
public readonly variations: [number, Variation][]
) {}
apply(x: number, y: number): [number, number] {
const xformX = this.coefs.a * x + this.coefs.b * y + this.coefs.c;
const xformY = this.coefs.d * x + this.coefs.e * y + this.coefs.f;
var [curX, curY] = [0, 0];
this.variations.forEach(([blend, variation]) => {
const [varX, varY] = variation(xformX, xformY, this.coefs);
curX += blend * varX;
curY += blend * varY;
});
return [curX, curY];
}
}
/**
* Translate values in the flame coordinate system to pixel coordinates
*
* The way `flam3` actually calculates the "camera" for mapping a point
* to its pixel coordinate is fairly involved - it also needs to calculate
* zoom and rotation. We'll make some simplifying assumptions:
* - The final image is square
* - We want to plot the range [-2, 2]
*
* The reference parameters were designed in Apophysis, which uses the
* range [-2, 2] by default (the `scale` parameter in XML defines the
* "pixels per unit", and with the default zoom, is chosen to give a
* range of [-2, 2]).
*
* @param x point in the range [-2, 2]
* @param y point in the range [-2, 2]
* @param size image size
* @returns pair of pixel coordinates
*/
export function camera(x: number, y: number, size: number): [number, number] {
return [Math.floor(((x + 2) * size) / 4), Math.floor(((y + 2) * size) / 4)];
}
export class RendererFlame extends Renderer {
private values = new Uint8Array(this.size * this.size);
protected x = randomBiUnit();
protected y = randomBiUnit();
constructor(size: number, public readonly transforms: [number, Transform][]) {
super(size);
}
plot(x: number, y: number) {
const [pixelX, pixelY] = camera(x, y, this.size);
if (
pixelX < 0 ||
pixelX >= this.size ||
pixelY < 0 ||
pixelY >= this.size
) {
return;
}
const hIndex = histIndex(pixelX, pixelY, this.size);
this.values[hIndex] = 1;
}
run(quality: number): void {
const iterations = quality * this.size * this.size;
for (var i = 0; i < iterations; i++) {
const [_, transform] = weightedChoice(this.transforms);
[this.x, this.y] = transform.apply(this.x, this.y);
if (i > 20) {
this.plot(this.x, this.y);
}
}
}
render(image: ImageData): void {
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
const hIndex = histIndex(x, y, this.size);
if (!this.values[hIndex]) {
continue;
}
const iIndex = imageIndex(x, y, this.size);
image.data[iIndex + 0] = 0;
image.data[iIndex + 1] = 0;
image.data[iIndex + 2] = 0;
image.data[iIndex + 3] = 0xff;
}
}
}
}
export const transform1Weight = 0.56453495;
export const transform1 = new Transform(
{
a: -1.381068,
b: -1.381068,
c: 0,
d: 1.381068,
e: -1.381068,
f: 0,
},
[[1, julia]]
);
export const transform2Weight = 0.013135;
export const transform2 = new Transform(
{
a: 0.031393,
b: 0.031367,
c: 0,
d: -0.031367,
e: 0.031393,
f: 0,
},
[
[1, linear],
[1, popcorn],
]
);
export const transform3Weight = 0.42233;
export const transform3 = new Transform(
{
a: 1.51523,
b: -3.048677,
c: 0.724135,
d: 0.740356,
e: -1.455964,
f: -0.362059,
},
[[1, pdj(1.09358, 2.13048, 2.54127, 2.37267)]]
);
export const transformAll: [number, Transform][] = [
[transform1Weight, transform1],
[transform2Weight, transform2],
[transform3Weight, transform3],
];
export const paramsBaseline: RenderParams = {
quality: 1,
renderer: (size) => new RendererFlame(size, transformAll),
};

View File

@ -0,0 +1,68 @@
import { RenderParams } from "./0-utility.js";
import {
Coefs,
Variation,
Transform,
transform1Weight,
transform1,
transform2Weight,
transform2,
transform3Weight,
transform3,
identityCoefs,
RendererFlame,
} from "./2a-baseline.js";
export class TransformPost extends Transform {
constructor(
coefs: Coefs,
variations: [number, Variation][],
public readonly post: Coefs
) {
super(coefs, variations);
}
apply(x: number, y: number): [number, number] {
const [transformX, transformY] = super.apply(x, y);
return [
transformX * this.post.a + transformY * this.post.b + this.post.c,
transformX * this.post.d + transformY * this.post.e + this.post.f,
];
}
}
export const transform1Post = new TransformPost(
transform1.coefs,
transform1.variations,
identityCoefs
);
export const transform2Post = new TransformPost(
transform2.coefs,
transform2.variations,
{
a: 1,
b: 0,
c: 0.241352,
d: 0,
e: 1,
f: 0.271521,
}
);
export const transform3Post = new TransformPost(
transform3.coefs,
transform3.variations,
identityCoefs
);
export const transformAllPost: [number, TransformPost][] = [
[transform1Weight, transform1Post],
[transform2Weight, transform2Post],
[transform3Weight, transform3Post],
];
export const paramsPost: RenderParams = {
quality: 1,
renderer: (size) => new RendererFlame(size, transformAllPost),
};

View File

@ -0,0 +1,35 @@
import { RenderParams } from "./0-utility.js";
import { julia, identityCoefs, RendererFlame } from "./2a-baseline.js";
import { TransformPost, transformAllPost } from "./2b-post.js";
export class RendererFinal extends RendererFlame {
constructor(
size: number,
transforms: [number, TransformPost][],
public readonly final: TransformPost
) {
super(size, transforms);
}
plot(x: number, y: number): void {
super.plot(...this.final.apply(x, y));
}
}
export const transformFinal = new TransformPost(
{
a: 2,
b: 0,
c: 0,
d: 0,
e: 2,
f: 0,
},
[[1, julia]],
identityCoefs
);
export const paramsFinal: RenderParams = {
quality: 1,
renderer: (size) => new RendererFinal(size, transformAllPost, transformFinal),
};

View File

@ -0,0 +1,67 @@
import { RenderParams, histIndex, imageIndex } from "./0-utility.js";
import {
camera,
transform1Weight,
transform2Weight,
transform3Weight,
} from "./2a-baseline.js";
import {
TransformPost,
transform1Post,
transform2Post,
transform3Post,
transformAllPost,
} from "./2b-post.js";
import { RendererFinal, transformFinal } from "./2c-final.js";
export class RendererHistogram extends RendererFinal {
protected histogram: number[] = [];
constructor(
size: number,
transforms: [number, TransformPost][],
final: TransformPost
) {
super(size, transforms, final);
for (var i = 0; i < this.size * this.size; i++) {
this.histogram.push(0);
}
}
plot(x: number, y: number): void {
[x, y] = this.final.apply(x, y);
const [pixelX, pixelY] = camera(x, y, this.size);
if (
pixelX < 0 ||
pixelY >= this.size ||
pixelY < 0 ||
pixelY >= this.size
) {
return;
}
const hIndex = histIndex(pixelX, pixelY, this.size);
this.histogram[hIndex] += 1;
}
render(image: ImageData): void {
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
const hIndex = histIndex(x, y, this.size);
const iIndex = imageIndex(x, y, this.size);
image.data[iIndex + 0] = 0;
image.data[iIndex + 1] = 0;
image.data[iIndex + 2] = 0;
image.data[iIndex + 3] = (this.histogram[hIndex] > 0 ? 1 : 0) * 0xff;
}
}
}
}
export const paramsBinary: RenderParams = {
quality: 1,
renderer: (size) =>
new RendererHistogram(size, transformAllPost, transformFinal),
};

View File

@ -0,0 +1,30 @@
import { RenderParams, histIndex, imageIndex } from "./0-utility.js";
import { transformAllPost } from "./2b-post.js";
import { transformFinal } from "./2c-final.js";
import { RendererHistogram } from "./3a-binary.js";
class RendererLinear extends RendererHistogram {
render(image: ImageData): void {
const maxHistogram = this.histogram.reduce(
(max, v) => Math.max(max, v),
-Infinity
);
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
const hIndex = histIndex(x, y, this.size);
const iIndex = imageIndex(x, y, this.size);
image.data[iIndex + 0] = 0;
image.data[iIndex + 1] = 0;
image.data[iIndex + 2] = 0;
image.data[iIndex + 3] = (this.histogram[hIndex] / maxHistogram) * 0xff;
}
}
}
}
export const paramsLinear: RenderParams = {
quality: 10,
renderer: (size) =>
new RendererLinear(size, transformAllPost, transformFinal),
};

View File

@ -0,0 +1,33 @@
import { RenderParams, histIndex, imageIndex } from "./0-utility.js";
import { transformAllPost } from "./2b-post.js";
import { transformFinal } from "./2c-final.js";
import { RendererHistogram } from "./3a-binary.js";
export class RendererLogarithmic extends RendererHistogram {
render(image: ImageData): void {
// Because log(0) is -Infinity, all the math actually works out.
const histogramLog = this.histogram.map(Math.log);
const histogramLogMax = histogramLog.reduce(
(max, v) => Math.max(max, v),
-Infinity
);
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
const hIndex = histIndex(x, y, this.size);
const iIndex = imageIndex(x, y, this.size);
image.data[iIndex + 0] = 0;
image.data[iIndex + 1] = 0;
image.data[iIndex + 2] = 0;
image.data[iIndex + 3] =
(histogramLog[hIndex] / histogramLogMax) * 0xff;
}
}
}
}
export const paramsLogarithmic: RenderParams = {
quality: 10,
renderer: (size) =>
new RendererLogarithmic(size, transformAllPost, transformFinal),
};

View File

@ -0,0 +1,192 @@
import {
RenderParams,
histIndex,
imageIndex,
weightedChoice,
} from "./0-utility.js";
import {
Coefs,
Variation,
camera,
transform1Weight,
transform2Weight,
transform3Weight,
} from "./2a-baseline.js";
import {
TransformPost,
transform1Post,
transform2Post,
transform3Post,
} from "./2b-post.js";
import { RendererFinal, transformFinal } from "./2c-final.js";
export class TransformColor extends TransformPost {
constructor(
coefs: Coefs,
variations: [number, Variation][],
post: Coefs,
public readonly color: number
) {
super(coefs, variations, post);
}
}
export class RendererColor extends RendererFinal {
protected color: number = Math.random();
protected red: number[] = [];
protected green: number[] = [];
protected blue: number[] = [];
protected alpha: number[] = [];
constructor(
size: number,
transforms: [number, TransformColor][],
final: TransformColor,
private readonly palette: number[]
) {
super(size, transforms, final);
for (var i = 0; i < this.size * this.size; i++) {
this.red.push(0);
this.green.push(0);
this.blue.push(0);
this.alpha.push(0);
}
}
colorFromIndex(c: number): [number, number, number] {
// A smarter coloring implementation would interpolate between points in the palette,
// but we'll use a step function here to keep things simple
const colorIndex = Math.floor(c * (this.palette.length / 3)) * 3;
return [
paletteNumber[colorIndex + 0],
paletteNumber[colorIndex + 1],
paletteNumber[colorIndex + 2],
];
}
plotColor(x: number, y: number, color: number): void {
const [finalX, finalY] = this.final.apply(x, y);
const [pixelX, pixelY] = camera(finalX, finalY, this.size);
if (pixelX < 0 || pixelX > this.size || pixelY < 0 || pixelY > this.size) {
return;
}
// NOTE: The reference parameters use a final `symmetry` of 1,
// which effectively disables the final transform's contribution
// to color mixing (see the `color_speed` in flam3).
// While we'd normally want to apply the same color transformation
// like we do in the `run` method, it is skipped here so the output
// image matches the reference image
//
// const finalColor = (color + (final as TransformColor).color) / 2
const finalColor = color;
const [r, g, b] = this.colorFromIndex(finalColor);
const hIndex = histIndex(pixelX, pixelY, this.size);
this.red[hIndex] += r;
this.green[hIndex] += g;
this.blue[hIndex] += b;
this.alpha[hIndex] += 1;
}
run(quality: number): void {
const iterations = quality * this.size * this.size;
for (var i = 0; i < iterations; i++) {
const [_, transform] = weightedChoice(this.transforms);
[this.x, this.y] = transform.apply(this.x, this.y);
this.color = (this.color + (transform as TransformColor).color) / 2;
if (i > 20) {
this.plotColor(this.x, this.y, this.color);
}
}
}
render(image: ImageData): void {
for (var x = 0; x < image.width; x++) {
for (var y = 0; y < image.height; y++) {
const hIndex = histIndex(x, y, image.width);
// NOTE: Calculating the scaling factor for accumulated color value to final
// pixel coloring is very involved (gamma, vibrancy, etc.). This scaling implementation
// is only intended to approximate the reference parameters.
const aScale =
Math.log10(this.alpha[hIndex]) / (this.alpha[hIndex] * 1.5);
const iIdx = imageIndex(x, y, this.size);
image.data[iIdx + 0] = this.red[hIndex] * aScale * 0xff;
image.data[iIdx + 1] = this.green[hIndex] * aScale * 0xff;
image.data[iIdx + 2] = this.blue[hIndex] * aScale * 0xff;
image.data[iIdx + 3] = this.alpha[hIndex] * aScale * 0xff;
}
}
}
}
export const transform1ColorValue = 0;
export const transform1Color = new TransformColor(
transform1Post.coefs,
transform1Post.variations,
transform1Post.post,
transform1ColorValue
);
export const transform2ColorValue = 0.844;
export const transform2Color = new TransformColor(
transform2Post.coefs,
transform2Post.variations,
transform2Post.post,
transform2ColorValue
);
export const transform3ColorValue = 0.349;
export const transform3Color = new TransformColor(
transform3Post.coefs,
transform3Post.variations,
transform3Post.post,
transform3ColorValue
);
export const transformFinalColorValue = 0;
export const transformFinalColor = new TransformColor(
transformFinal.coefs,
transformFinal.variations,
transformFinal.post,
transformFinalColorValue
);
// Copied from the reference parameters
export const paletteHex =
"7E3037762C45722B496E2A4E6A29506728536527546326565C265C5724595322574D2155482153462050451F4E441E4D431E4C3F1E473F1E453F1E433F1E3F3F1E3B3E1E393E1E37421D36431C38451C3A471B3B491B3C4A1A3C4B1A3D4D1A3E4F19405318435517445817465A16475D15495E154960154A65134E6812506B12526E1153711055720F55740F55770E577A0E59810C58840B58880A588B09588F08589107569307559A05539D0451A1034FA5024BA90147AA0046AC0045B00242B4043DBB0634BE082EC20A29C30B27C50C26C90F1DCC1116D32110D6280EDA300CDC380ADF4109E04508E24A08E45106E75704EA6402EC6B01EE7300EE7600EF7A00F07E00F18300F29000F29300F39600F39900F39C00F3A000F3A100F3A201F2A502F1A805F0A906EFAA08EEA909EEA80AEDA60CEBA50FE5A313E1A113DD9F13DB9E13D99D14D49C15D09815CC9518C79318BE8B1ABB891BB9871DB4811FB07D1FAB7621A671239C6227975C289256299053298E502A89482C853F2D803A2E7E3037762C45742B47722B496E2A4E6A29516728536326565C265C5724595322575022564E2255482153452050451F4E431E4C3F1E473E1D463D1D453F1E43411E413F1E3B3E1E37421D36421D38431D3B451C3A471B3A491B3C4B1A3D4D1A3E4F19405318435418445518455817465A16475D154960154A65134E66124F6812506B12526E1153711055740F55770E577A0E597E0D57810C58840B58880A588B09588F08589307559A05539C04529E0452A1034FA5024BA90147AC0045B00242B4043DB7053ABB0634BE0831C20A29C50C26C90F1DCC1116D01711D32110D72A0EDA300CDD390ADF4109E24A08E45106E75704E95F03EA6402EC6C01EE7300EF7A00F07E00F18300F28900F29000F39300F39600F39C00F3A000F3A100F3A201F2A502F2A503F1A805F0A807EFAA08EEA80AEDA60CEBA50FE9A411E5A313E1A113DD9F13D99D14D49C15D09815CC9518C79318C38F1ABE8B1AB9871DB4811FB07D1FAB7621A67123A16A249C6227975E289256298E502A89482C853F2D803A2E";
// https://stackoverflow.com/a/34356351
export function hexToBytes(hex: string) {
var bytes = [];
for (var i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substring(i, i + 2), 16));
}
return bytes;
}
export const paletteBytes = hexToBytes(paletteHex);
/**
* Re-scale pixel color values to the range [0, 1], done to match
* 'flam3_get_palette'
*/
export const paletteNumber = paletteBytes.map((b) => b / 0xff);
export const paramsColor: RenderParams = {
quality: 30,
renderer: (size) =>
new RendererColor(
size,
[
[transform1Weight, transform1Color],
[transform2Weight, transform2Color],
[transform3Weight, transform3Color],
],
transformFinalColor,
paletteNumber
),
};

View File

@ -0,0 +1,49 @@
import { RenderParams } from "./0-utility.js";
import { RendererFlame, Transform, linear } from "./2a-baseline.js";
export const transformGasket1 = new Transform(
{
a: 0.5,
b: 0,
c: 0,
d: 0,
e: 0.5,
f: 0,
},
[[1, linear]]
);
export const transformGasket2 = new Transform(
{
a: 0.5,
b: 0,
c: 0.5,
d: 0,
e: 0.5,
f: 0,
},
[[1, linear]]
);
export const transformGasket3 = new Transform(
{
a: 0.5,
b: 0,
c: 0,
d: 0,
e: 0.5,
f: 0.5,
},
[[1, linear]]
);
export const transformGasket: [number, Transform][] = [
[1 / 3, transformGasket1],
[1 / 3, transformGasket2],
[1 / 3, transformGasket3],
];
export const paramsGasketFlame: RenderParams = {
quality: 1,
renderer: (size) => new RendererFlame(size, transformGasket),
};

View File

@ -0,0 +1,46 @@
import { RenderParams, weightedChoice } from "./0-utility.js";
import { TransformPost, transformAllPost } from "./2b-post.js";
import { transformFinal } from "./2c-final.js";
import { RendererLogarithmic } from "./3c-logarithmic.js";
export class RendererSolo extends RendererLogarithmic {
constructor(
size: number,
transforms: [number, TransformPost][],
final: TransformPost,
private readonly transformSolo: number
) {
super(size, transforms, final);
}
run(quality: number): void {
const iterations = quality * this.size * this.size;
for (var i = 0; i < iterations; i++) {
const [transformIndex, transform] = weightedChoice(this.transforms);
[this.x, this.y] = transform.apply(this.x, this.y);
// NOTE: Only plot if the current point is from the solo transform
if (i > 20 && transformIndex == this.transformSolo) {
this.plot(this.x, this.y);
}
}
}
}
export const paramsSolo1: RenderParams = {
quality: 10,
renderer: (size) =>
new RendererSolo(size, transformAllPost, transformFinal, 0),
};
export const paramsSolo2: RenderParams = {
quality: 10,
renderer: (size) =>
new RendererSolo(size, transformAllPost, transformFinal, 1),
};
export const paramsSolo3: RenderParams = {
quality: 10,
renderer: (size) =>
new RendererSolo(size, transformAllPost, transformFinal, 2),
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,15 @@
import Blog from "../../../LayoutBlog";
import { CanvasColor } from "./0-canvas.js";
export default function () {
const Layout = Blog({
title: "The fractal flame algorithm",
description: "Explaining the paper",
published: "2023-06-25",
});
return (
<Layout>
<CanvasColor />
</Layout>
);
}

View File

@ -0,0 +1,45 @@
import * as fs from "fs/promises";
import { createCanvas } from "canvas";
import { DEFAULT_SIZE, RenderParams } from "./0-utility.js";
import { paramsGasket } from "./1-gasket.js";
import { paramsBaseline } from "./2a-baseline.js";
import { paramsPost } from "./2b-post.js";
import { paramsFinal } from "./2c-final.js";
import { paramsBinary } from "./3a-binary.js";
import { paramsLinear } from "./3b-linear.js";
import { paramsLogarithmic } from "./3c-logarithmic.js";
import { paramsColor } from "./4-color.js";
import { paramsGasketFlame } from "./5a-gasket.js";
import { paramsSolo1, paramsSolo2, paramsSolo3 } from "./5b-solo.js";
export type OfflineParams = RenderParams & { filename: string };
const paramsAll: OfflineParams[] = [
{ filename: "images/1-gasket.png", ...paramsGasket },
{ filename: "images/2a-baseline.png", ...paramsBaseline },
{ filename: "images/2b-post.png", ...paramsPost },
{ filename: "images/2c-final.png", ...paramsFinal },
{ filename: "images/3a-binary.png", ...paramsBinary },
{ filename: "images/3b-linear.png", ...paramsLinear },
{ filename: "images/3c-logarithmic.png", ...paramsLogarithmic },
{ filename: "images/4-color.png", ...paramsColor },
{ filename: "images/5a-gasket.png", ...paramsGasketFlame },
{ filename: "images/5b-solo1.png", ...paramsSolo1 },
{ filename: "images/5b-solo2.png", ...paramsSolo2 },
{ filename: "images/5b-solo3.png", ...paramsSolo3 },
];
for (const param of paramsAll) {
const render = param.renderer(DEFAULT_SIZE);
render.run(param.quality);
const canvas = createCanvas(DEFAULT_SIZE, DEFAULT_SIZE);
const ctx = canvas.getContext("2d");
const data = ctx.createImageData(DEFAULT_SIZE, DEFAULT_SIZE);
render.render(data as any);
ctx.putImageData(data, 0, 0);
const buffer = canvas.toBuffer();
await fs.writeFile(param.filename, buffer);
}

659
posts/2023/06/flam3/package-lock.json generated Normal file
View File

@ -0,0 +1,659 @@
{
"name": "blog-flam3",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "blog-flam3",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"canvas": "^2.11.2"
},
"devDependencies": {
"typescript": "^5.1.6"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/canvas": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
"integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
"hasInstallScript": true,
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.0",
"nan": "^2.17.0",
"simple-get": "^3.0.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"dependencies": {
"mimic-response": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
},
"node_modules/detect-libc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
"engines": {
"node": ">=8"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nan": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
"integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ=="
},
"node_modules/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/simple-get": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"dependencies": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tar": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
"integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/typescript": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

View File

@ -0,0 +1,17 @@
{
"name": "blog-flam3",
"version": "1.0.0",
"description": "",
"type": "module",
"scripts": {
"render": "tsc && node ./dist/offline.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"canvas": "^2.11.2"
},
"devDependencies": {
"typescript": "^5.1.6"
}
}

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["DOM", "ES2021"],
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"exclude": ["*.tsx"]
}

View File

@ -0,0 +1,120 @@
<Flames name="variations">
<flame name="post xform" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
<xform weight="0.0131350067581356" color="0" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" post="1 0 0 1 0.241352 0.271521" />
<palette count="256" format="RGB">
3A78875998AA5E9DAC78B1C2599BAB36798A2252601B3438
1823270D1215080705010101000000000002080A090A0809
0C070D0B090A030406010101000000000000000000000000
0A00000B0A080E1213101B1F21202830243A6737357A3C31
864424643A22452F1838251427190E1C12080E0F110E1213
1014152110183720105D320FA0531F9144180409080A1312
0C13140E13160E15160E17160F16171015180B161C0A1225
0A0F2F101E37172E40294C5A3B6B7549798758879975A9BE
79A7BF7EA6C0949FA2AA9985B7A27BC4AB72AC965A867654
61574E4C4D48374343474141573F3F7C5C36B0914EC1DFF9
C4E4FAC8E9FCBEE1F4B5DAEDB2D8EDB0D6ED5398A7386D78
1D424A1B3B4219343B1B383E1D3C411D3B462155623D7C8B
46747F4F6C74636454785C3584663E917047BEA467CEA86A
DEAC6DC5975EAC834F916E41765A335F3D21431F21241625
1F202B1A2B321A2D321B30331B323A1628360E1D220E1D21
0F1D20101C1F111C1E111D1E121E1E2B21153B2B1B725432
85542C9854279B63369F7346AD7C3AB2763AB18F4FB39453
B69957B99B56BC9E56C19651CB9346AB6A2A9851254E341D
2F261B10181A0E15160C12120D11120A10100D0D0D0C0E0E
0B0F100B10120C11140F191A101F221829331A373B1E3D52
1A40551744591D556420424C1E3B431D3C41112C33102328
101B1D10191E111820101D2311242A1B33371B3A3F276476
3E637D556284545F7D7759355C41261B30290E16180B0F0F
0908060405030002010A0E0F12171A1C1B2B17343C3C7481
467F8F508A9E528FA23E81923769722E69772248512B545E
35616C688589807F85939FB5ABD6E6B3D6EA89B7CE5891A4
467E92356C81194A6B1A373F132C310E1C1F050409020205
0000020000000101010800000B0000170A000D0D0D0D1110
0F0E14100F141F11082619082F1904210F05111717101919
0F1B1B101F22182C2B252E2B282D311B2E321A2E2F162E30
1325270E191B0F1314190D0F2E1211461A27552227612723
6C303A56213D3033381C343619343B15383E193A431A4E5C
</palette>
</flame>
<flame name="baseline" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
<xform weight="0.564534951145298" color="0.13" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
<xform weight="0.0131350067581356" color="0.844" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" />
<palette count="256" format="RGB">
FF0000D31616BD2121A72C2C9137377C4242714747664D4D
3A63631D7171008080008B8B00969600A1A100ACAC00B1B1
00B7B700CCCC00D7D700E2E200EDED00F8F800FBFB00FFFF
2CF0FF42E8FF58E0FF6DD8FF83D1FF8ECDFF99C9FFAFC2FF
C5BAFFFFA6FFE9A2FFD39FFFBD9CFFA799FF9C97FF9196FF
668FFF508CFF3A89FF2485FF0E82FF0781FF0080FF0B80FF
1680FF2C80FF3780FF4280FF4D80FF5880FF5D80FF6380FF
7980FF7785F4758BE96A96D35FA1BD59A6B254ACA749B791
3EC17C28D7501DE23A12ED2409F61200FF0016E9002CD300
58A7006D9100837C00996600AF5000BA4500C53A00DB2400
F10E00E90B00D31600BD2100B22600A72C009137007C4200
5058003A6300246E001973000E79000080000A7500146A00
1E5F003249003C3E004633004B2D005028005A1D00651200
8100008C00009800009E0000A40000AF0000BB0000C70000
D20000EA0000F00000F60000FD0000F2160BE82C16DD4221
C76E37BC8342B2994DACA452A7AF589CC56392DB6E87F179
80FF8080E99680E39B80DEA180D3AC80C8B780BEC180B3CC
809DE2808EF08080FF7A80F47580E96A80D35F80BD5480A7
4980913380662D805B2880501D803A12802407800E008000
2C841A3784204285265887336E8940838B4D998D5AAF8E66
C59073FF9595FF9393FF9292FF9090FF8D8DFF8B8BFF8888
FF8383FF8181FF8080FF7E7EFF7B7BFF7979FF7777FF7783
FF768EFF769AFF75A6FF75B1FF74BDFF74C9FF73D4FF73E0
FF72F8FF71FBFF71FFFF6BEDFF65DBFF5FC9FF5AB7FF54A5
FF4E93FF4881FF426FFF3C5DFF374BFF2D2DFA293AF62548
F12155ED1E63E81A70E4167EDF128BDB0E99D60AA6D106B4
CD02C1CA00CACC00B9CE00A7CF0096D10085D30073D50062
D70050D8003FDA002EDC001CDE000BDF0000D90C06D4180C
CE2413C82F19C33B1FBD4725B7532BB25F32AC6B38A6773E
9D8A489D7E429E723C9E66359F5B2FA04F29A04323A1371D
A12B16A21F10A3130AA30804A40000A000009C0000980000
9400009000008C00008800008400008000007C0000750000
</palette>
</flame>
<flame name="final xform" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="1 1 1" brightness="4" gamma="4" >
<xform weight="0.422330042096567" color="0.349" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
<xform weight="0.0131350067581356" color="0.844" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" post="1 0 0 1 0.241352 0.271521" />
<finalxform color="0" symmetry="1" julia="1" coefs="2 0 0 2 0 0" />
<palette count="256" format="RGB">
7E3037762C45722B496E2A4E6A2950672853652754632656
5C265C5724595322574D2155482153462050451F4E441E4D
431E4C3F1E473F1E453F1E433F1E3F3F1E3B3E1E393E1E37
421D36431C38451C3A471B3B491B3C4A1A3C4B1A3D4D1A3E
4F19405318435517445817465A16475D15495E154960154A
65134E6812506B12526E1153711055720F55740F55770E57
7A0E59810C58840B58880A588B09588F0858910756930755
9A05539D0451A1034FA5024BA90147AA0046AC0045B00242
B4043DBB0634BE082EC20A29C30B27C50C26C90F1DCC1116
D32110D6280EDA300CDC380ADF4109E04508E24A08E45106
E75704EA6402EC6B01EE7300EE7600EF7A00F07E00F18300
F29000F29300F39600F39900F39C00F3A000F3A100F3A201
F2A502F1A805F0A906EFAA08EEA909EEA80AEDA60CEBA50F
E5A313E1A113DD9F13DB9E13D99D14D49C15D09815CC9518
C79318BE8B1ABB891BB9871DB4811FB07D1FAB7621A67123
9C6227975C289256299053298E502A89482C853F2D803A2E
7E3037762C45742B47722B496E2A4E6A2951672853632656
5C265C5724595322575022564E2255482153452050451F4E
431E4C3F1E473E1D463D1D453F1E43411E413F1E3B3E1E37
421D36421D38431D3B451C3A471B3A491B3C4B1A3D4D1A3E
4F19405318435418445518455817465A16475D154960154A
65134E66124F6812506B12526E1153711055740F55770E57
7A0E597E0D57810C58840B58880A588B09588F0858930755
9A05539C04529E0452A1034FA5024BA90147AC0045B00242
B4043DB7053ABB0634BE0831C20A29C50C26C90F1DCC1116
D01711D32110D72A0EDA300CDD390ADF4109E24A08E45106
E75704E95F03EA6402EC6C01EE7300EF7A00F07E00F18300
F28900F29000F39300F39600F39C00F3A000F3A100F3A201
F2A502F2A503F1A805F0A807EFAA08EEA80AEDA60CEBA50F
E9A411E5A313E1A113DD9F13D99D14D49C15D09815CC9518
C79318C38F1ABE8B1AB9871DB4811FB07D1FAB7621A67123
A16A249C6227975E289256298E502A89482C853F2D803A2E
</palette>
</flame>
</Flames>

View File

@ -1,4 +1,7 @@
import { PropsWithChildren } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCalendar } from "@fortawesome/free-solid-svg-icons";
import { MDXProvider } from "@mdx-js/react";
import React, { PropsWithChildren } from "react";
import Base from "../pages/LayoutBase";
@ -9,6 +12,10 @@ interface BlogProps {
updated?: string;
}
const components = {
pre: (props: any) => <pre className="hljs" {...props} />,
};
export default function Layout({
title,
description,
@ -19,7 +26,14 @@ export default function Layout({
<div className="header">
<h1>{title}</h1>
<h3>{description}</h3>
<p>Published: {published}</p>
<h4>
<FontAwesomeIcon
icon={faCalendar}
scale={1.2}
className="icon icon-post"
/>
{published}
</h4>
{updated && <p>Last updated: {updated}</p>}
</div>
);
@ -27,8 +41,8 @@ export default function Layout({
const withChildren: React.FC<PropsWithChildren> = ({ children }) => (
<Base>
{header}
<hr />
{children}
<div style={{ paddingTop: "2em" }} />
<MDXProvider components={components}>{children}</MDXProvider>
</Base>
);
return withChildren;

View File

@ -3,7 +3,7 @@
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,

View File

@ -2,17 +2,30 @@ import { defineConfig } from "vite";
import blog from "@bspeice/vite-plugin-blog";
import mdx from "@mdx-js/rollup";
import react from "@vitejs/plugin-react-swc";
import remarkPrism from "remark-prism";
import rehypeHighlight from "rehype-highlight";
import rehypeKatex from "rehype-katex";
import remarkFrontmatter from "remark-frontmatter";
import remarkMath from "remark-math";
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
export default defineConfig({
build: {
rollupOptions: {
external: ["react-icons"],
},
},
plugins: [
blog({
"/": "/pages/index.tsx",
"/about": "/pages/about.mdx",
"/2019/02/the-whole-world": "/posts/2019/02/the-whole-world.mdx",
"/2023/06/flam3": "/posts/2023/06/flam3/index.tsx",
}),
mdx({
remarkPlugins: [remarkFrontmatter, remarkMath, remarkMdxFrontmatter],
rehypePlugins: [rehypeHighlight, rehypeKatex],
providerImportSource: "@mdx-js/react",
}),
mdx({ remarkPlugins: [remarkPrism] }),
react(),
],
});