website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
d7dbb78
— pyrossh
2023-12-01T23:53:13+05:30
initial commit
- .github/workflows/deploy.yml +33 -0
- .gitignore +10 -0
- .vscode/extensions.json +6 -0
- .vscode/settings.json +17 -0
- README.md +16 -0
- components/Button.tsx +12 -0
- components/Footer.tsx +22 -0
- components/Header.tsx +44 -0
- components/SocialLinks.tsx +19 -0
- components/icons/EmailIcon.tsx +15 -0
- components/icons/GithubIcon.tsx +15 -0
- components/icons/LinkedInIcon.tsx +11 -0
- deno.json +26 -0
- dev.ts +8 -0
- fresh.config.ts +6 -0
- fresh.gen.ts +29 -0
- islands/Counter.tsx +16 -0
- islands/LemonDrop.tsx +177 -0
- main.ts +13 -0
- routes/_404.tsx +27 -0
- routes/_app.tsx +39 -0
- routes/api/joke.ts +21 -0
- routes/greet/[name].tsx +5 -0
- routes/index.tsx +39 -0
- static/favicon.ico +0 -0
- static/logo.svg +6 -0
- static/styles.css +3 -0
- tailwind.config.ts +25 -0
.github/workflows/deploy.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Deploy
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
deploy:
|
|
10
|
+
name: Deploy
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # Needed for auth with Deno Deploy
|
|
15
|
+
contents: read # Needed to clone the repository
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Clone repository
|
|
19
|
+
uses: actions/checkout@v3
|
|
20
|
+
|
|
21
|
+
- name: Install Deno
|
|
22
|
+
uses: denoland/setup-deno@v1
|
|
23
|
+
with:
|
|
24
|
+
deno-version: v1.x
|
|
25
|
+
|
|
26
|
+
- name: Build step
|
|
27
|
+
run: "deno task build"
|
|
28
|
+
|
|
29
|
+
- name: Upload to Deno Deploy
|
|
30
|
+
uses: denoland/deployctl@v1
|
|
31
|
+
with:
|
|
32
|
+
project: "example-project"
|
|
33
|
+
entrypoint: "./main.ts"
|
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# dotenv environment variable files
|
|
2
|
+
.env
|
|
3
|
+
.env.development.local
|
|
4
|
+
.env.test.local
|
|
5
|
+
.env.production.local
|
|
6
|
+
.env.local
|
|
7
|
+
|
|
8
|
+
# Fresh build directory
|
|
9
|
+
_fresh/
|
|
10
|
+
node_modules/
|
.vscode/extensions.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"recommendations": [
|
|
3
|
+
"denoland.vscode-deno",
|
|
4
|
+
"sastan.twind-intellisense"
|
|
5
|
+
]
|
|
6
|
+
}
|
.vscode/settings.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"deno.enable": true,
|
|
3
|
+
"deno.lint": true,
|
|
4
|
+
"editor.defaultFormatter": "denoland.vscode-deno",
|
|
5
|
+
"[typescriptreact]": {
|
|
6
|
+
"editor.defaultFormatter": "denoland.vscode-deno"
|
|
7
|
+
},
|
|
8
|
+
"[typescript]": {
|
|
9
|
+
"editor.defaultFormatter": "denoland.vscode-deno"
|
|
10
|
+
},
|
|
11
|
+
"[javascriptreact]": {
|
|
12
|
+
"editor.defaultFormatter": "denoland.vscode-deno"
|
|
13
|
+
},
|
|
14
|
+
"[javascript]": {
|
|
15
|
+
"editor.defaultFormatter": "denoland.vscode-deno"
|
|
16
|
+
}
|
|
17
|
+
}
|
README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Fresh project
|
|
2
|
+
|
|
3
|
+
Your new Fresh project is ready to go. You can follow the Fresh "Getting
|
|
4
|
+
Started" guide here: https://fresh.deno.dev/docs/getting-started
|
|
5
|
+
|
|
6
|
+
### Usage
|
|
7
|
+
|
|
8
|
+
Make sure to install Deno: https://deno.land/manual/getting_started/installation
|
|
9
|
+
|
|
10
|
+
Then start the project:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
deno task start
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This will watch the project directory and restart as necessary.
|
components/Button.tsx
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { JSX } from "preact";
|
|
2
|
+
import { IS_BROWSER } from "$fresh/runtime.ts";
|
|
3
|
+
|
|
4
|
+
export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
|
|
5
|
+
return (
|
|
6
|
+
<button
|
|
7
|
+
{...props}
|
|
8
|
+
disabled={!IS_BROWSER || props.disabled}
|
|
9
|
+
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
|
|
10
|
+
/>
|
|
11
|
+
);
|
|
12
|
+
}
|
components/Footer.tsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import SocialLinks from "./SocialLinks.tsx";
|
|
2
|
+
import LemonDrop from "../islands/LemonDrop.tsx";
|
|
3
|
+
|
|
4
|
+
export default function Footer() {
|
|
5
|
+
return (
|
|
6
|
+
<footer class="flex w-full flex-1 flex-row justify-center text-xl leading-6">
|
|
7
|
+
<div class="flex flex-row flex-1 p-4 max-w-5xl text-yellow">
|
|
8
|
+
<div class="flex-1">
|
|
9
|
+
<SocialLinks />
|
|
10
|
+
</div>
|
|
11
|
+
<div class="flex">
|
|
12
|
+
<span class="text-base text-yellow hover:no-underline">
|
|
13
|
+
powered by
|
|
14
|
+
</span>
|
|
15
|
+
<a class="ml-2 -mt-3" href="https://fresh.deno.dev">
|
|
16
|
+
<LemonDrop />
|
|
17
|
+
</a>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</footer>
|
|
21
|
+
);
|
|
22
|
+
}
|
components/Header.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import SocialLinks from "./SocialLinks.tsx";
|
|
2
|
+
|
|
3
|
+
export default function Header() {
|
|
4
|
+
return (
|
|
5
|
+
<header>
|
|
6
|
+
<nav class="sm:py-2 md:py-0 flex w-full flex-1 flex-row justify-center bg-black text-xl font-mono">
|
|
7
|
+
<div class="flex flex-row flex-1 items-center p-3.5 max-w-5xl">
|
|
8
|
+
<a
|
|
9
|
+
class="flex text-yellow pr-4 hover:no-underline text-xl ml-0 pl-0 pr-4"
|
|
10
|
+
href="/"
|
|
11
|
+
>
|
|
12
|
+
<span class="mr-1 font-logo font-bold">木</span> pyros.sh
|
|
13
|
+
</a>
|
|
14
|
+
<div class="flex flex-row flex-1 items-center">
|
|
15
|
+
<a
|
|
16
|
+
class="text-white mx-1 px-2 hover:bg-[#444] hover:no-underline"
|
|
17
|
+
href="/dev"
|
|
18
|
+
rel="prefetch"
|
|
19
|
+
>
|
|
20
|
+
dev
|
|
21
|
+
</a>
|
|
22
|
+
<div class="text-white">|</div>
|
|
23
|
+
<a
|
|
24
|
+
class="text-white mx-1 px-2 hover:bg-[#444] hover:no-underline"
|
|
25
|
+
href="/ref"
|
|
26
|
+
rel="prefetch"
|
|
27
|
+
>
|
|
28
|
+
ref
|
|
29
|
+
</a>
|
|
30
|
+
<div class="text-white">|</div>
|
|
31
|
+
<a
|
|
32
|
+
class="text-white mx-1 px-2 hover:bg-[#444] hover:no-underline"
|
|
33
|
+
href="/blog"
|
|
34
|
+
rel="prefetch"
|
|
35
|
+
>
|
|
36
|
+
blog
|
|
37
|
+
</a>
|
|
38
|
+
</div>
|
|
39
|
+
<SocialLinks />
|
|
40
|
+
</div>
|
|
41
|
+
</nav>
|
|
42
|
+
</header>
|
|
43
|
+
);
|
|
44
|
+
}
|
components/SocialLinks.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import EmailIcon from "./icons/EmailIcon.tsx";
|
|
2
|
+
import GithubIcon from "./icons/GithubIcon.tsx";
|
|
3
|
+
import LinkedInIcon from "./icons/LinkedInIcon.tsx";
|
|
4
|
+
|
|
5
|
+
export default function SocialLinks() {
|
|
6
|
+
return (
|
|
7
|
+
<div class="flex hover:no-underline text-white [&>a]:ml-5">
|
|
8
|
+
<a href="https://linkedin.com/in/pyrossh" title="My LinkedIn profile">
|
|
9
|
+
<LinkedInIcon />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/pyrossh" title="My GitHub profile">
|
|
12
|
+
<GithubIcon />
|
|
13
|
+
</a>
|
|
14
|
+
<a href="mailto:[email protected]" title="My email">
|
|
15
|
+
<EmailIcon />
|
|
16
|
+
</a>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
components/icons/EmailIcon.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default function EmailIcon() {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
viewBox="0 0 512 512"
|
|
5
|
+
width="32"
|
|
6
|
+
height="32"
|
|
7
|
+
>
|
|
8
|
+
<path
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
d="M424 80H88a56.06 56.06 0 0 0-56 56v240a56.06 56.06 0 0 0 56 56h336a56.06 56.06 0 0 0 56-56V136a56.06 56.06 0 0 0-56-56zm-14.18 92.63-144 112a16 16 0 0 1-19.64 0l-144-112a16 16 0 1 1 19.64-25.26L256 251.73l134.18-104.36a16 16 0 0 1 19.64 25.26z"
|
|
11
|
+
>
|
|
12
|
+
</path>
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
components/icons/GithubIcon.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default function GithubIcon() {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
viewBox="0 0 24 24"
|
|
5
|
+
width="32"
|
|
6
|
+
height="32"
|
|
7
|
+
>
|
|
8
|
+
<path
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33.85 0 1.71.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2z"
|
|
11
|
+
>
|
|
12
|
+
</path>
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
components/icons/LinkedInIcon.tsx
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default function LinkedInIcon() {
|
|
2
|
+
return (
|
|
3
|
+
<svg viewBox="0 0 24 24" width="32" height="32">
|
|
4
|
+
<path
|
|
5
|
+
fill="currentColor"
|
|
6
|
+
d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.32 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.79M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z"
|
|
7
|
+
>
|
|
8
|
+
</path>
|
|
9
|
+
</svg>
|
|
10
|
+
);
|
|
11
|
+
}
|
deno.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lock": false,
|
|
3
|
+
"tasks": {
|
|
4
|
+
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
|
|
5
|
+
"start": "deno run -A --watch=static/,routes/ dev.ts",
|
|
6
|
+
"build": "deno run -A dev.ts build",
|
|
7
|
+
"preview": "deno run -A main.ts",
|
|
8
|
+
"update": "deno run -A -r https://fresh.deno.dev/update ."
|
|
9
|
+
},
|
|
10
|
+
"lint": { "rules": { "tags": ["fresh", "recommended"] } },
|
|
11
|
+
"exclude": ["**/_fresh/*"],
|
|
12
|
+
"nodeModulesDir": true,
|
|
13
|
+
"imports": {
|
|
14
|
+
"$fresh/": "https://deno.land/x/[email protected]/",
|
|
15
|
+
"preact": "https://esm.sh/[email protected]",
|
|
16
|
+
"preact/": "https://esm.sh/[email protected]/",
|
|
17
|
+
"preact-render-to-string": "https://esm.sh/*[email protected]",
|
|
18
|
+
"@preact/signals": "https://esm.sh/*@preact/[email protected]",
|
|
19
|
+
"@preact/signals-core": "https://esm.sh/*@preact/[email protected]",
|
|
20
|
+
"tailwindcss": "npm:[email protected]",
|
|
21
|
+
"tailwindcss/": "npm:/[email protected]/",
|
|
22
|
+
"tailwindcss/plugin": "npm:/[email protected]/plugin.js",
|
|
23
|
+
"$std/": "https://deno.land/[email protected]/"
|
|
24
|
+
},
|
|
25
|
+
"compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" }
|
|
26
|
+
}
|
dev.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env -S deno run -A --watch=static/,routes/
|
|
2
|
+
|
|
3
|
+
import dev from "$fresh/dev.ts";
|
|
4
|
+
import config from "./fresh.config.ts";
|
|
5
|
+
|
|
6
|
+
import "$std/dotenv/load.ts";
|
|
7
|
+
|
|
8
|
+
await dev(import.meta.url, "./main.ts", config);
|
fresh.config.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { defineConfig } from "$fresh/server.ts";
|
|
2
|
+
import tailwind from "$fresh/plugins/tailwind.ts";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [tailwind()],
|
|
6
|
+
});
|
fresh.gen.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// DO NOT EDIT. This file is generated by Fresh.
|
|
2
|
+
// This file SHOULD be checked into source version control.
|
|
3
|
+
// This file is automatically updated during development when running `dev.ts`.
|
|
4
|
+
|
|
5
|
+
import * as $_404 from "./routes/_404.tsx";
|
|
6
|
+
import * as $_app from "./routes/_app.tsx";
|
|
7
|
+
import * as $api_joke from "./routes/api/joke.ts";
|
|
8
|
+
import * as $greet_name_ from "./routes/greet/[name].tsx";
|
|
9
|
+
import * as $index from "./routes/index.tsx";
|
|
10
|
+
import * as $Counter from "./islands/Counter.tsx";
|
|
11
|
+
import * as $LemonDrop from "./islands/LemonDrop.tsx";
|
|
12
|
+
import { type Manifest } from "$fresh/server.ts";
|
|
13
|
+
|
|
14
|
+
const manifest = {
|
|
15
|
+
routes: {
|
|
16
|
+
"./routes/_404.tsx": $_404,
|
|
17
|
+
"./routes/_app.tsx": $_app,
|
|
18
|
+
"./routes/api/joke.ts": $api_joke,
|
|
19
|
+
"./routes/greet/[name].tsx": $greet_name_,
|
|
20
|
+
"./routes/index.tsx": $index,
|
|
21
|
+
},
|
|
22
|
+
islands: {
|
|
23
|
+
"./islands/Counter.tsx": $Counter,
|
|
24
|
+
"./islands/LemonDrop.tsx": $LemonDrop,
|
|
25
|
+
},
|
|
26
|
+
baseUrl: import.meta.url,
|
|
27
|
+
} satisfies Manifest;
|
|
28
|
+
|
|
29
|
+
export default manifest;
|
islands/Counter.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Signal } from "@preact/signals";
|
|
2
|
+
import { Button } from "../components/Button.tsx";
|
|
3
|
+
|
|
4
|
+
interface CounterProps {
|
|
5
|
+
count: Signal<number>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function Counter(props: CounterProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div class="flex gap-8 py-6">
|
|
11
|
+
<Button onClick={() => props.count.value -= 1}>-1</Button>
|
|
12
|
+
<p class="text-3xl">{props.count}</p>
|
|
13
|
+
<Button onClick={() => props.count.value += 1}>+1</Button>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
islands/LemonDrop.tsx
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { useEffect, useRef } from "preact/hooks";
|
|
2
|
+
import { useSignal } from "@preact/signals";
|
|
3
|
+
|
|
4
|
+
export interface Spring {
|
|
5
|
+
p: number;
|
|
6
|
+
v: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class WaveTank {
|
|
10
|
+
springs = [] as Spring[];
|
|
11
|
+
waveLength = 100;
|
|
12
|
+
k = 0.02;
|
|
13
|
+
damping = 0.02;
|
|
14
|
+
spread = 0.02;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
for (let i = 0; i < this.waveLength; i++) {
|
|
18
|
+
this.springs[i] = {
|
|
19
|
+
p: 0,
|
|
20
|
+
v: 0,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
update(springs: Spring[]) {
|
|
26
|
+
for (const i of springs) {
|
|
27
|
+
const a = -this.k * i.p - this.damping * i.v;
|
|
28
|
+
i.p += i.v;
|
|
29
|
+
i.v += a;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const leftDeltas = [];
|
|
33
|
+
const rightDeltas = [];
|
|
34
|
+
|
|
35
|
+
for (let t = 0; t < 8; t++) {
|
|
36
|
+
for (let i = 0; i < springs.length; i++) {
|
|
37
|
+
const prev = springs[(i - 1 + springs.length) % springs.length];
|
|
38
|
+
const next = springs[(i + 1) % springs.length];
|
|
39
|
+
|
|
40
|
+
leftDeltas[i] = this.spread * (springs[i].p - prev.p);
|
|
41
|
+
rightDeltas[i] = this.spread * (springs[i].p - next.p);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < springs.length; i++) {
|
|
45
|
+
const prev = springs[(i - 1 + springs.length) % springs.length];
|
|
46
|
+
const next = springs[(i + 1) % springs.length];
|
|
47
|
+
prev.v += leftDeltas[i];
|
|
48
|
+
next.v += rightDeltas[i];
|
|
49
|
+
prev.p += leftDeltas[i];
|
|
50
|
+
next.p += rightDeltas[i];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function easeInCirc(x: number) {
|
|
57
|
+
return 1 - Math.sqrt(1 - Math.pow(x, 2));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const waveTank = new WaveTank();
|
|
61
|
+
|
|
62
|
+
function LemonDrop() {
|
|
63
|
+
const SVG_WIDTH = 100;
|
|
64
|
+
const counter = useSignal(0);
|
|
65
|
+
const dropy = useSignal(60);
|
|
66
|
+
const width = useSignal(SVG_WIDTH);
|
|
67
|
+
const widthRef = useRef(width.value);
|
|
68
|
+
const springs = useSignal(waveTank.springs);
|
|
69
|
+
const requestIdRef = useRef<number>();
|
|
70
|
+
const grid = SVG_WIDTH / waveTank.waveLength;
|
|
71
|
+
const points = [
|
|
72
|
+
[0, 100],
|
|
73
|
+
[0, 0],
|
|
74
|
+
...springs.value.map((x, i) => [i * grid, x.p]),
|
|
75
|
+
[width.value, 0],
|
|
76
|
+
[width.value, 100],
|
|
77
|
+
];
|
|
78
|
+
const springsPath = `${points.map((x) => x.join(",")).join(" ")}`;
|
|
79
|
+
const juice = `M18 ${63 + counter.value} C15 ${63 + counter.value} 16 ${
|
|
80
|
+
63 + counter.value
|
|
81
|
+
} 12 61L9 56C2 33 62 -3 80 12C103 27 44 56 29 58C27 58 25 59 24 61C20 ${
|
|
82
|
+
63 + counter.value
|
|
83
|
+
} 21 ${63 + counter.value} 18 ${63 + counter.value}Z`;
|
|
84
|
+
|
|
85
|
+
function updateJuice(timestamp: number) {
|
|
86
|
+
const amp = 40;
|
|
87
|
+
const x = timestamp / 2000;
|
|
88
|
+
const saw = x - Math.floor(x);
|
|
89
|
+
if (saw < 0.6) {
|
|
90
|
+
counter.value = easeInCirc(saw) * amp;
|
|
91
|
+
dropy.value = -100;
|
|
92
|
+
} else {
|
|
93
|
+
counter.value = easeInCirc(1 - saw) * amp * 0.1;
|
|
94
|
+
dropy.value = 70 + Math.pow(saw - 0.6, 2) * 10000;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function update(timestamp: number) {
|
|
99
|
+
updateJuice(timestamp);
|
|
100
|
+
waveTank.update(waveTank.springs);
|
|
101
|
+
springs.value = [...waveTank.springs];
|
|
102
|
+
|
|
103
|
+
const offset = 500;
|
|
104
|
+
const saw = (timestamp + offset) / 2000 -
|
|
105
|
+
Math.floor((timestamp + offset) / 2000);
|
|
106
|
+
if (saw < 0.01) {
|
|
107
|
+
drop();
|
|
108
|
+
}
|
|
109
|
+
requestIdRef.current = globalThis.requestAnimationFrame(update);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resize() {
|
|
113
|
+
width.value = document.body.clientWidth;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function drop() {
|
|
117
|
+
const dropPosition = Math.round(
|
|
118
|
+
((widthRef.current / 2 - 30) / widthRef.current) * 100,
|
|
119
|
+
);
|
|
120
|
+
waveTank.springs[dropPosition].p = -60;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
widthRef.current = width.value;
|
|
125
|
+
}, [width.value]);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
129
|
+
if (mediaQuery.matches) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
requestIdRef.current = requestAnimationFrame(update);
|
|
134
|
+
globalThis.addEventListener("resize", resize);
|
|
135
|
+
resize();
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
globalThis.removeEventListener("resize", resize);
|
|
139
|
+
if (requestIdRef.current !== undefined) {
|
|
140
|
+
cancelAnimationFrame(requestIdRef.current);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}, []);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<svg
|
|
147
|
+
width="32"
|
|
148
|
+
height="48"
|
|
149
|
+
viewBox="0 0 100 120"
|
|
150
|
+
fill="none"
|
|
151
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
152
|
+
role="img"
|
|
153
|
+
aria-label="Fresh logo"
|
|
154
|
+
>
|
|
155
|
+
<circle cx="18" cy={dropy.value} r="4" fill="white"></circle>
|
|
156
|
+
<path
|
|
157
|
+
d="M11.9 96.3v24H7v-24h4.9Zm7.5 10.2v4h-8.8v-4h8.8Zm1-10.2v4h-9.8v-4h9.8ZM23.2 96.3H31c1.6 0 3 .3 4.1.9 1.1.5 2 1.3 2.6 2.4a8 8 0 0 1 1 4c0 1.3-.2 2.4-.6 3.3-.3 1-.8 1.7-1.5 2.3-.7.6-1.4 1-2.3 1.5l-1.5.8h-6.3v-4h4.3a3 3 0 0 0 1.7-.4c.4-.3.7-.7 1-1.2a5 5 0 0 0 .3-2c0-.7-.1-1.3-.3-1.9-.2-.5-.5-1-1-1.2-.3-.3-.9-.5-1.5-.5h-3v20h-4.8v-24Zm11 24-4.4-10.7h5l4.6 10.5v.2h-5.2ZM55.9 116.3v4H45.4v-4h10.5Zm-9-20v24H42v-24H47Zm7.6 9.8v3.8h-9.1v-3.8h9Zm1.4-9.8v4H45.4v-4h10.5ZM69 114c0-.4 0-.8-.2-1.1 0-.4-.2-.7-.5-1l-1-1-1.9-.8-2.6-1.2-2.2-1.5a6.5 6.5 0 0 1-1.7-2 6 6 0 0 1-.5-2.7c0-1 .1-2 .5-2.7a6 6 0 0 1 1.6-2.2c.7-.5 1.5-1 2.4-1.3a9.5 9.5 0 0 1 7.1.5c1.2.6 2 1.5 2.7 2.6.6 1 1 2.4 1 3.8h-5a5 5 0 0 0-.2-1.8c-.2-.5-.5-1-1-1.2-.4-.3-1-.5-1.6-.5-.6 0-1.1.2-1.5.4-.4.2-.7.6-1 1l-.2 1.4c0 .4.1.8.3 1.1l.8.8a21.3 21.3 0 0 0 2.8 1.4l2.9 1.4a9 9 0 0 1 2 1.8c.7.6 1 1.3 1.4 2a7.9 7.9 0 0 1-.1 5.5c-.4.8-.9 1.5-1.5 2.1a7 7 0 0 1-2.5 1.4c-2 .5-1 1.8-3 1.8s-1.3-1.5-3.2-1.8c-1-.3-2-.8-2.7-1.4a6.7 6.7 0 0 1-1.8-2.5c-.4-1-.6-2.2-.6-3.5h4.9c0 .7 0 1.3.2 1.8.1.5.3 1 .6 1.3.3.2.7.5 1.1.6.5.2 1 .2 1.5.2.7 0 1.2 0 1.6-.3.4-.3.6-.6.8-1 .2-.4.3-.9.3-1.4ZM90.5 106v4h-10v-4h10Zm-8.6-9.7v24h-4.8v-24h4.8Zm12.1 0v24h-4.8v-24H94Z"
|
|
158
|
+
fill="#FFFFFF"
|
|
159
|
+
/>
|
|
160
|
+
<path
|
|
161
|
+
d="M84 16c13 27 1 52-7 59 0 4-9 9-12 7-12 5-38-2-53-21-6-7 1-21 21-36 13-10 33-17 51-9Z"
|
|
162
|
+
fill="#FFD80B"
|
|
163
|
+
/>
|
|
164
|
+
<path d={juice} fill="white" />
|
|
165
|
+
<path
|
|
166
|
+
d="M69 15c15-1 9 10-6 19L44 44c-2 1-4-2-6 0l-3 6c-3 1-13 3-16 2-5-2-5-9 5-18l7-4c-1-2-1-2 2-5 3-2 19-10 29-11l1 2 6-1Z"
|
|
167
|
+
fill="#FFED4E"
|
|
168
|
+
/>
|
|
169
|
+
<path
|
|
170
|
+
d="M38 35c1-1 3-2 3-4l8-3c0 1-1 3 1 4-2 1-7 1-8 5-1-2-1-2-4-2Z"
|
|
171
|
+
fill="#fff"
|
|
172
|
+
/>
|
|
173
|
+
</svg>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default LemonDrop;
|
main.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference no-default-lib="true" />
|
|
2
|
+
/// <reference lib="dom" />
|
|
3
|
+
/// <reference lib="dom.iterable" />
|
|
4
|
+
/// <reference lib="dom.asynciterable" />
|
|
5
|
+
/// <reference lib="deno.ns" />
|
|
6
|
+
|
|
7
|
+
import "$std/dotenv/load.ts";
|
|
8
|
+
|
|
9
|
+
import { start } from "$fresh/server.ts";
|
|
10
|
+
import manifest from "./fresh.gen.ts";
|
|
11
|
+
import config from "./fresh.config.ts";
|
|
12
|
+
|
|
13
|
+
await start(manifest, config);
|
routes/_404.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Head } from "$fresh/runtime.ts";
|
|
2
|
+
|
|
3
|
+
export default function Error404() {
|
|
4
|
+
return (
|
|
5
|
+
<>
|
|
6
|
+
<Head>
|
|
7
|
+
<title>404 - Page not found</title>
|
|
8
|
+
</Head>
|
|
9
|
+
<div class="px-4 py-8 mx-auto bg-[#86efac]">
|
|
10
|
+
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
|
|
11
|
+
<img
|
|
12
|
+
class="my-6"
|
|
13
|
+
src="/logo.svg"
|
|
14
|
+
width="128"
|
|
15
|
+
height="128"
|
|
16
|
+
alt="the Fresh logo: a sliced lemon dripping with juice"
|
|
17
|
+
/>
|
|
18
|
+
<h1 class="text-4xl font-bold">404 - Page not found</h1>
|
|
19
|
+
<p class="my-4">
|
|
20
|
+
The page you were looking for doesn't exist.
|
|
21
|
+
</p>
|
|
22
|
+
<a href="/" class="underline">Go back home</a>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
routes/_app.tsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AppProps } from "$fresh/server.ts";
|
|
2
|
+
import Footer from "../components/Footer.tsx";
|
|
3
|
+
import Header from "../components/Header.tsx";
|
|
4
|
+
|
|
5
|
+
// const SEO = () => (
|
|
6
|
+
// <title>fres</title>
|
|
7
|
+
// <link rel="canonical" href={Astro.url} />
|
|
8
|
+
// <meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
9
|
+
// <link rel="icon" type="image/png" href="/favicon.png" />
|
|
10
|
+
// <meta name="author" content="pyrossh" />
|
|
11
|
+
// <meta name="keywords" content="pyros.sh,pyrossh,astro,website" />
|
|
12
|
+
// <meta property="og:site_name" content="pyros.sh" />
|
|
13
|
+
// <meta property="og:type" content="website" />
|
|
14
|
+
// <meta property="og:url" content={Astro.url} />
|
|
15
|
+
// <meta property="og:site_name" content="pyros.sh" />
|
|
16
|
+
// )
|
|
17
|
+
|
|
18
|
+
export default function App({ Component }: AppProps) {
|
|
19
|
+
return (
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="utf-8" />
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
24
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
25
|
+
</head>
|
|
26
|
+
<body class="bg-black-lighter">
|
|
27
|
+
<Header />
|
|
28
|
+
<main class="w-full h-full block bg-white">
|
|
29
|
+
<div class="flex w-full flex-1 flex-row justify-center">
|
|
30
|
+
<div class="flex w-full flex-1 flex-row items-center max-w-5xl mt-4 mb-20 p-2">
|
|
31
|
+
<Component />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</main>
|
|
35
|
+
<Footer />
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
|
38
|
+
);
|
|
39
|
+
}
|
routes/api/joke.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HandlerContext } from "$fresh/server.ts";
|
|
2
|
+
|
|
3
|
+
// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/
|
|
4
|
+
const JOKES = [
|
|
5
|
+
"Why do Java developers often wear glasses? They can't C#.",
|
|
6
|
+
"A SQL query walks into a bar, goes up to two tables and says “can I join you?”",
|
|
7
|
+
"Wasn't hard to crack Forrest Gump's password. 1forrest1.",
|
|
8
|
+
"I love pressing the F5 key. It's refreshing.",
|
|
9
|
+
"Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”",
|
|
10
|
+
"There are 10 types of people in the world. Those who understand binary and those who don't.",
|
|
11
|
+
"Why are assembly programmers often wet? They work below C level.",
|
|
12
|
+
"My favourite computer based band is the Black IPs.",
|
|
13
|
+
"What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.",
|
|
14
|
+
"An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const handler = (_req: Request, _ctx: HandlerContext): Response => {
|
|
18
|
+
const randomIndex = Math.floor(Math.random() * JOKES.length);
|
|
19
|
+
const body = JOKES[randomIndex];
|
|
20
|
+
return new Response(body);
|
|
21
|
+
};
|
routes/greet/[name].tsx
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { PageProps } from "$fresh/server.ts";
|
|
2
|
+
|
|
3
|
+
export default function Greet(props: PageProps) {
|
|
4
|
+
return <div>Hello {props.params.name}</div>;
|
|
5
|
+
}
|
routes/index.tsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useSignal } from "@preact/signals";
|
|
2
|
+
import { HandlerContext, Handlers, PageProps } from "$fresh/server.ts";
|
|
3
|
+
import Counter from "../islands/Counter.tsx";
|
|
4
|
+
|
|
5
|
+
export const handler: Handlers = {
|
|
6
|
+
async GET(req, ctx) {
|
|
7
|
+
return await ctx.render();
|
|
8
|
+
},
|
|
9
|
+
async POST(req, ctx) {
|
|
10
|
+
const form = await req.formData();
|
|
11
|
+
return new Response(null, {
|
|
12
|
+
status: 303,
|
|
13
|
+
headers: { location: "/" },
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function Home() {
|
|
19
|
+
const count = useSignal(3);
|
|
20
|
+
return (
|
|
21
|
+
<div class="px-4 py-8 mx-auto bg-[#86efac]">
|
|
22
|
+
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
|
|
23
|
+
<img
|
|
24
|
+
class="my-6"
|
|
25
|
+
src="/logo.svg"
|
|
26
|
+
width="128"
|
|
27
|
+
height="128"
|
|
28
|
+
alt="the Fresh logo: a sliced lemon dripping with juice"
|
|
29
|
+
/>
|
|
30
|
+
<h1 class="text-4xl font-bold">Welcome to Fresh</h1>
|
|
31
|
+
<p class="my-4">
|
|
32
|
+
Try updating this message in the
|
|
33
|
+
<code class="mx-2">./routes/index.tsx</code> file, and refresh.
|
|
34
|
+
</p>
|
|
35
|
+
<Counter count={count} />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
static/favicon.ico
ADDED
|
Binary file
|
static/logo.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M34.092 8.845C38.929 20.652 34.092 27 30 30.5c1 3.5-2.986 4.222-4.5 2.5-4.457 1.537-13.512 1.487-20-5C2 24.5 4.73 16.714 14 11.5c8-4.5 16-7 20.092-2.655Z" fill="#FFDB1E"/>
|
|
3
|
+
<path d="M14 11.5c6.848-4.497 15.025-6.38 18.368-3.47C37.5 12.5 21.5 22.612 15.5 25c-6.5 2.587-3 8.5-6.5 8.5-3 0-2.5-4-5.183-7.75C2.232 23.535 6.16 16.648 14 11.5Z" fill="#fff" stroke="#FFDB1E"/>
|
|
4
|
+
<path d="M28.535 8.772c4.645 1.25-.365 5.695-4.303 8.536-3.732 2.692-6.606 4.21-7.923 4.83-.366.173-1.617-2.252-1.617-1 0 .417-.7 2.238-.934 2.326-1.365.512-4.223 1.29-5.835 1.29-3.491 0-1.923-4.754 3.014-9.122.892-.789 1.478-.645 2.283-.645-.537-.773-.534-.917.403-1.546C17.79 10.64 23 8.77 25.212 8.42c.366.014.82.35.82.629.41-.14 2.095-.388 2.503-.278Z" fill="#FFE600"/>
|
|
5
|
+
<path d="M14.297 16.49c.985-.747 1.644-1.01 2.099-2.526.566.121.841-.08 1.29-.701.324.466 1.657.608 2.453.701-.715.451-1.057.852-1.452 2.106-1.464-.611-3.167-.302-4.39.42Z" fill="#fff"/>
|
|
6
|
+
</svg>
|
static/styles.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
tailwind.config.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Config } from "tailwindcss";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
content: ["{routes,islands,components}/**/*.{ts,tsx}"],
|
|
5
|
+
theme: {
|
|
6
|
+
fontFamily: {
|
|
7
|
+
sans: "system-ui",
|
|
8
|
+
serif: "system-ui",
|
|
9
|
+
mono: "monospace",
|
|
10
|
+
logo: "Kaiti SC",
|
|
11
|
+
},
|
|
12
|
+
extend: {
|
|
13
|
+
colors: {
|
|
14
|
+
yellow: "#f1fa8c",
|
|
15
|
+
black: "#1a1a1a",
|
|
16
|
+
"black-lighter": "#1f1f1f",
|
|
17
|
+
"black-light": "#313a3d",
|
|
18
|
+
"yellow-dark": "#858579",
|
|
19
|
+
blue: "#0645ad",
|
|
20
|
+
"blue-light": "#0e81c7",
|
|
21
|
+
"slider-bg": "#f0ede2",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
} as Config;
|