Skip to content

Commit

Permalink
Landing page in HTML (#1)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip

* wip

* add blur to tube

* add first bubble

* add box shadow

* simplex noise bubble paths

* example

* add standard http file server

* stretchy bubbles

* add delay to stagger bubbles

* update tube.svg

* add perspective divs

* add rising lands effect

* wip

* wip

* wip

todo: cubes show rise from the bottom

* wip

* wip

* wip

* Squashed commit of the following:

commit 1c366c4361564b7dba59df55ca73a61ecd6c6c04
Author: EthanThatOneKid <[email protected]>
Date:   Mon Apr 22 10:28:31 2024 -0700

    fix rising cubes animation

commit a7a8ae1a39d857b65ea9f95d481b3ef254de839d
Author: EthanThatOneKid <[email protected]>
Date:   Mon Apr 22 09:25:13 2024 -0700

    Update index.css

commit a52e75db411e289bfc69dd73e240b4e3d9c742b6
Author: EthanThatOneKid <[email protected]>
Date:   Mon Apr 22 09:20:28 2024 -0700

    change text color of rising cubes

* stagger rising rotation

* add scroll timeline polyfill

https://github.com/flackr/scroll-timeline

* add favicon

* add hljs syntax highlighting

copy hljs output from <https://github.com/FartLabs/jsonx_docs/pull/25>'s Lab example.

* add page headings

* add grid to hero element

* add grid to hero element

with mobile responsiveness

* seo: add description

* Update index.html

* Update index.html

* add fl-icon class

* add molecule blob behind hero logo animation

* add baby goop character asset

i love baby goop

* add projects section

* delete badges

* add remaining recent projects

choose more tube colors (with mom ♥)

* Update index.html

* fix grammar

* fix grammar

* remove unneeded punctuation
  • Loading branch information
EthanThatOneKid authored Apr 23, 2024
1 parent dbda9fb commit 77745b6
Show file tree
Hide file tree
Showing 28 changed files with 1,449 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"deno.enable": true,
"deno.unstable": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[markdown]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[jsonc]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[typescriptreact]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"files.eol": "\n"
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# fart.tools

🧪 Official website of FartLabs.
Binary file added baby-goop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions background-blob.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.background-blob {
background-image: url("molecule-blob.svg");
background-size: cover;
background-size: 100%;
background-repeat: no-repeat;
background-position: 50% 50%;
}
11 changes: 11 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"lock": false,
"imports": {
"@std/http": "jsr:@std/http@^0.223.0",
"simplex-noise": "npm:simplex-noise@^4.0.1"
},
"tasks": {
"start": "deno run --allow-net --allow-read main.ts",
"generate": "deno run --allow-write gen.ts"
}
}
Binary file added fl-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
303 changes: 303 additions & 0 deletions gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import { createNoise2D } from "simplex-noise";

const noise = createNoise2D();

const green = "#C3EF3C";
const purple = "#9D00FF";
const yellow = "#FDBD01";
const turquoise = "#16E2F5";
const magenta = "#FF00FF";
const orange = "#FF6700";
const blue = "#1589FF";

// deno run gen.ts
//
if (import.meta.main) {
const path = [
{ x: 20, y: 20 },
{ x: 84, y: 20 },
{ x: 84, y: 84 },
{ x: 20, y: 84 },
];
const verticesAmount = Deno.args[0] ? parseInt(Deno.args[0]) : 100;

const emptyTubeSVG = renderEmptyTube();
Deno.writeTextFileSync("tube-empty.svg", emptyTubeSVG);

const emptyTubeCSS = renderTubeCSS("empty", "#ffffffaa");
Deno.writeTextFileSync("tube-empty.css", emptyTubeCSS);

const greenTubeSVG = renderGreenTube(
renderBubbles(path, verticesAmount, 30, green),
);
Deno.writeTextFileSync("tube-green.svg", greenTubeSVG);

const greenTubeCSS = renderTubeCSS("green", green);
Deno.writeTextFileSync("tube-green.css", greenTubeCSS);

const purpleTubeSVG = renderPurpleTube(
renderBubbles(path, verticesAmount, 30, purple),
);
Deno.writeTextFileSync("tube-purple.svg", purpleTubeSVG);

const purpleTubeCSS = renderTubeCSS("purple", purple);
Deno.writeTextFileSync("tube-purple.css", purpleTubeCSS);

const yellowTubeSVG = renderYellowTube(
renderBubbles(path, verticesAmount, 30, yellow),
);
Deno.writeTextFileSync("tube-yellow.svg", yellowTubeSVG);

const yellowTubeCSS = renderTubeCSS("yellow", yellow);
Deno.writeTextFileSync("tube-yellow.css", yellowTubeCSS);

const turquoiseTubeSVG = renderTurquoiseTube(
renderBubbles(path, verticesAmount, 30, turquoise),
);
Deno.writeTextFileSync("tube-turquoise.svg", turquoiseTubeSVG);

const turquoiseTubeCSS = renderTubeCSS("turquoise", turquoise);
Deno.writeTextFileSync("tube-turquoise.css", turquoiseTubeCSS);

const magentaTubeSVG = renderMagentaTube(
renderBubbles(path, verticesAmount, 30, magenta),
);
Deno.writeTextFileSync("tube-magenta.svg", magentaTubeSVG);

const magentaTubeCSS = renderTubeCSS("magenta", magenta);
Deno.writeTextFileSync("tube-magenta.css", magentaTubeCSS);

const orangeTubeSVG = renderOrangeTube(
renderBubbles(path, verticesAmount, 30, orange),
);
Deno.writeTextFileSync("tube-orange.svg", orangeTubeSVG);

const orangeTubeCSS = renderTubeCSS("orange", orange);
Deno.writeTextFileSync("tube-orange.css", orangeTubeCSS);

const blueTubeSVG = renderBlueTube(
renderBubbles(path, verticesAmount, 30, blue),
);
Deno.writeTextFileSync("tube-blue.svg", blueTubeSVG);

const blueTubeCSS = renderTubeCSS("blue", blue);
Deno.writeTextFileSync("tube-blue.css", blueTubeCSS);
}

function renderTubeCSS(name: string, color: string) {
return `.border-tube-${name} {
border-image-slice: 31;
border-image-width: 28px;
border-image-outset: 4px;
border-image-repeat: stretch stretch;
border-image-source: url("tube-${name}.svg");
border-style: solid;
padding: 20px;
border-radius: 2em;
}
.border-tube-${name}[class*=" glow"],
.border-tube-${name}[class*="glow "] {
box-shadow: 0 0 42px 8px ${color};
}\n`;
}

function renderGreenTube(...children: string[]) {
return renderEmptyTube(
`<rect filter="blur(1px)" stroke="${green}aa" stroke-width="28" x="16" y="16" width="68" height="68" rx="16" ry="16" />`,
...children,
);
}

function renderPurpleTube(...children: string[]) {
return renderEmptyTube(
`<rect filter="blur(1px)" stroke="${purple}aa" stroke-width="28" x="16" y="16" width="68" height="68" rx="16" ry="16" />`,
...children,
);
}

function renderYellowTube(...children: string[]) {
return renderEmptyTube(
`<rect filter="blur(1px)" stroke="${yellow}aa" stroke-width="28" x="16" y="16" width="68" height="68" rx="16" ry="16" />`,
...children,
);
}

function renderTurquoiseTube(...children: string[]) {
return renderEmptyTube(
`<rect filter="blur(1px)" stroke="${turquoise}aa" stroke-width="28" x="16" y="16" width="68" height="68" rx="16" ry="16" />`,
...children,
);
}

function renderMagentaTube(...children: string[]) {
return renderEmptyTube(
`<rect filter="blur(1px)" stroke="${magenta}aa" stroke-width="28" x="16" y="16" width="68" height="68" rx="16" ry="16" />`,
...children,
);
}

function renderOrangeTube(...children: string[]) {
return renderEmptyTube(
`<rect filter="blur(1px)" stroke="${orange}aa" stroke-width="28" x="16" y="16" width="68" height="68" rx="16" ry="16" />`,
...children,
);
}

function renderBlueTube(...children: string[]) {
return renderEmptyTube(
`<rect filter="blur(1px)" stroke="${blue}aa" stroke-width="28" x="16" y="16" width="68" height="68" rx="16" ry="16" />`,
...children,
);
}

function renderEmptyTube(...children: string[]) {
return [
`<svg width='100' height='100' viewBox='0 0 100 100' fill='none' xmlns='http://www.w3.org/2000/svg'>`,
...children,
`<rect stroke="#fffffff0" stroke-width="2" x="3" y="3" width="94" height="94" rx="24" ry="24" />`,
`<rect stroke="#fffffff0" stroke-width="2" x="29" y="29" width="42" height="42" rx="6" ry="6" />`,
`<rect filter="blur(2px)" stroke="#ffffffaa" stroke-width="7.5" x="17.5" y="17.5" width="66" height="66" rx="12" ry="12" />`,
`</svg>\n`,
].join("\n");
}

function renderBubbles(
path: Vertex[],
amount: number,
totalBubbles: number,
color: string,
): string {
const duration = 8;
return Array.from(
{ length: totalBubbles },
(_, i) =>
renderBubble(
path,
amount,
Math.random(),
color,
duration,
-i * (duration / totalBubbles),
),
).join("");
}

function renderBubble(
path: Vertex[],
amount: number,
seed: number,
color: string,
duration: number,
delay = 0,
): string {
const vertices = generateVertices(path, amount, seed);
return [
`<circle r="4" fill="${color}" fill-opacity="60%">`,
`<animateMotion dur="${duration}s" repeatCount="indefinite" begin="${delay}s" `,
`path="${renderVertices(vertices)}"`,
"/>",
"</circle>",
].join("");
}

function renderVertices(vertices: Vertex[]): string {
let path = "M ";
for (const vertex of vertices) {
path += `${vertex.x},${vertex.y} `;
}

return path.trim() + " Z";
}

interface Vertex {
x: number;
y: number;
}

/**
* iteratePath iterates over a path of vertices divided into `amount` segments.
*/
function* iteratePath(
path: Vertex[],
amount: number,
): Iterable<Vertex> {
const pathLength = path.length;
for (let i = 0; i < amount; i++) {
const t = i / (amount - 1);
const index = t * (pathLength - 1);
const indexFloor = Math.floor(index);
const indexCeil = Math.ceil(index);
const vertexA = path[indexFloor];
const vertexB = path[indexCeil];
const vertex = lerpVertex(vertexA, vertexB, index - indexFloor);
yield vertex;
}
}

/**
* lerpVertex returns a vertex that is `t` percent between `a` and `b`.
*/
function lerpVertex(a: Vertex, b: Vertex, t: number): Vertex {
return {
x: lerp(a.x, b.x, t),
y: lerp(a.y, b.y, t),
};
}

/**
* lerp returns a value that is `t` percent between `a` and `b`.
*/
function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}

/**
* getOffset returns a random offset based on the noise at the given position.
*/
function getOffset(
maxOffset: number,
theta: number,
offsetX = 0,
offsetY = offsetX,
offsetR = 0.5,
) {
const x = Math.cos(theta) * offsetR + offsetX;
const y = Math.sin(theta) * offsetR + offsetY;
return (noise(x, y) - 0.5) * maxOffset;
}

/**
* generateVertices generates a set of vertices based on a path and a seed
* used for Simplex noise using the polar noise technique.
*
* @see
* [Coding Challenge #136.1: Polar Perlin Noise Loops](https://youtu.be/ZI1dmHv3MeM)
*/
function generateVertices(
path: Vertex[],
amount: number,
seed: number,
variance = 5,
fractionDigits = 2,
) {
if (
path[0].x !== path[path.length - 1].x ||
path[0].y !== path[path.length - 1].y
) {
path.push({ x: path[0].x, y: path[0].y });
}

const seedX = seed * 1e3;
const seedY = seedX * -1;
return Array.from(iteratePath(path, amount))
.map((v, i, { length }) => {
const theta = (i / length) * Math.PI * 2;
const offsetX = getOffset(variance, theta, seedX);
const offsetY = getOffset(variance, theta, seedY);
return {
x: Number((v.x + offsetX).toFixed(fractionDigits)),
y: Number((v.y + offsetY).toFixed(fractionDigits)),
};
});
}
Loading

0 comments on commit 77745b6

Please sign in to comment.