website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


d7dbb78pyrossh 2023-12-01T23:53:13+05:30
initial commit
.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;