+64
-6
components/Button.tsx
+64
-6
components/Button.tsx
···
1
1
import { JSX } from "preact";
2
-
import { IS_BROWSER } from "fresh/runtime";
2
+
3
+
type ButtonBaseProps = {
4
+
color?: "blue" | "amber";
5
+
icon?: string;
6
+
iconAlt?: string;
7
+
label?: string;
8
+
className?: string;
9
+
condensed?: boolean;
10
+
};
11
+
12
+
type ButtonProps = ButtonBaseProps & Omit<JSX.HTMLAttributes<HTMLButtonElement>, keyof ButtonBaseProps>;
13
+
type AnchorProps = ButtonBaseProps & Omit<JSX.HTMLAttributes<HTMLAnchorElement>, keyof ButtonBaseProps> & { href: string };
14
+
15
+
type Props = ButtonProps | AnchorProps;
16
+
17
+
export function Button(props: Props) {
18
+
const { color = "blue", icon, iconAlt, label, className = "", condensed = false, ...rest } = props;
19
+
const isAnchor = 'href' in props;
20
+
21
+
const baseStyles = "airport-sign flex items-center [transition:none]";
22
+
const paddingStyles = condensed ? 'px-2 py-1.5' : 'px-3 py-2 sm:px-6 sm:py-3';
23
+
const transformStyles = "translate-y-0 hover:translate-y-1 hover:transition-transform hover:duration-200 hover:ease-in-out";
24
+
const colorStyles = {
25
+
blue: "bg-gradient-to-r from-blue-400 to-blue-500 text-white hover:from-blue-500 hover:to-blue-600",
26
+
amber: "bg-gradient-to-r from-amber-400 to-amber-500 text-slate-900 hover:from-amber-500 hover:to-amber-600",
27
+
};
3
28
4
-
export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
29
+
const buttonContent = (
30
+
<>
31
+
{icon && (
32
+
<img
33
+
src={icon}
34
+
alt={iconAlt || ""}
35
+
className={`${condensed ? 'w-4 h-4' : 'w-6 h-6'} mr-2`}
36
+
style={{ filter: color === 'blue' ? "brightness(0) invert(1)" : "brightness(0)" }}
37
+
/>
38
+
)}
39
+
{label && (
40
+
<span className="font-mono font-bold tracking-wider">
41
+
{label}
42
+
</span>
43
+
)}
44
+
</>
45
+
);
46
+
47
+
const buttonStyles = `${baseStyles} ${paddingStyles} ${transformStyles} ${colorStyles[color]} ${className}`;
48
+
49
+
if (isAnchor) {
50
+
return (
51
+
<a
52
+
href={props.href}
53
+
className={buttonStyles}
54
+
{...rest as JSX.HTMLAttributes<HTMLAnchorElement>}
55
+
>
56
+
{buttonContent}
57
+
</a>
58
+
);
59
+
}
60
+
61
+
const buttonProps = rest as JSX.HTMLAttributes<HTMLButtonElement>;
5
62
return (
6
63
<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
-
/>
64
+
{...buttonProps}
65
+
className={buttonStyles}
66
+
>
67
+
{buttonContent}
68
+
</button>
11
69
);
12
70
}
+37
-4
deno.lock
+37
-4
deno.lock
···
25
25
"jsr:@std/path@^1.0.9": "1.0.9",
26
26
"jsr:@std/semver@1": "1.0.5",
27
27
"npm:@atproto-labs/handle-resolver-node@~0.1.14": "0.1.15",
28
+
"npm:@atproto-labs/simple-store@~0.1.2": "0.1.2",
28
29
"npm:@atproto/api@*": "0.15.6",
29
30
"npm:@atproto/api@~0.15.6": "0.15.6",
30
31
"npm:@atproto/crypto@*": "0.4.4",
31
32
"npm:@atproto/identity@*": "0.4.8",
32
33
"npm:@atproto/jwk@0.1.4": "0.1.4",
33
34
"npm:@atproto/oauth-client@~0.3.13": "0.3.16",
35
+
"npm:@atproto/oauth-types@~0.2.4": "0.2.7",
34
36
"npm:@atproto/syntax@*": "0.4.0",
35
37
"npm:@atproto/xrpc@*": "0.7.0",
38
+
"npm:@lucide/lab@*": "0.1.2",
36
39
"npm:@opentelemetry/api@^1.9.0": "1.9.0",
37
40
"npm:@preact/signals@^1.2.3": "1.3.2_preact@10.26.6",
38
41
"npm:@preact/signals@^2.0.4": "2.0.4_preact@10.26.6",
42
+
"npm:@types/node@*": "22.15.15",
39
43
"npm:autoprefixer@10.4.17": "10.4.17_postcss@8.4.35",
40
44
"npm:cssnano@6.0.3": "6.0.3_postcss@8.4.35",
41
45
"npm:esbuild-wasm@0.23.1": "0.23.1",
42
46
"npm:esbuild@0.23.1": "0.23.1",
43
47
"npm:iron-session@*": "8.0.4",
44
48
"npm:jose@5.9.6": "5.9.6",
49
+
"npm:lucide-preact@*": "0.511.0_preact@10.26.6",
45
50
"npm:postcss@8.4.35": "8.4.35",
51
+
"npm:preact-feather@*": "4.2.1_preact@10.26.6",
46
52
"npm:preact-render-to-string@^6.5.11": "6.5.13_preact@10.26.6",
47
53
"npm:preact@^10.25.1": "10.26.6",
48
54
"npm:preact@^10.26.6": "10.26.6",
···
166
172
"dependencies": [
167
173
"@atproto-labs/fetch",
168
174
"@atproto-labs/pipe",
169
-
"@atproto-labs/simple-store",
175
+
"@atproto-labs/simple-store@0.2.0",
170
176
"@atproto-labs/simple-store-memory",
171
177
"@atproto/did",
172
178
"zod"
···
199
205
"@atproto-labs/handle-resolver@0.1.8": {
200
206
"integrity": "sha512-Y0ckccoCGDo/3g4thPkgp9QcORmc+qqEaCBCYCZYtfLIQp4775u22wd+4fyEyJP4DqoReKacninkICgRGfs3dQ==",
201
207
"dependencies": [
202
-
"@atproto-labs/simple-store",
208
+
"@atproto-labs/simple-store@0.2.0",
203
209
"@atproto-labs/simple-store-memory",
204
210
"@atproto/did",
205
211
"zod"
···
219
225
"@atproto-labs/simple-store-memory@0.1.3": {
220
226
"integrity": "sha512-jkitT9+AtU+0b28DoN92iURLaCt/q/q4yX8q6V+9LSwYlUTqKoj/5NFKvF7x6EBuG+gpUdlcycbH7e60gjOhRQ==",
221
227
"dependencies": [
222
-
"@atproto-labs/simple-store",
228
+
"@atproto-labs/simple-store@0.2.0",
223
229
"lru-cache"
224
230
]
231
+
},
232
+
"@atproto-labs/simple-store@0.1.2": {
233
+
"integrity": "sha512-9vTNvyPPBs44tKVFht16wGlilW8u4wpEtKwLkWbuNEh3h9TTQ8zjVhEoGZh/v73G4Otr9JUOSIq+/5+8OZD2mQ=="
225
234
},
226
235
"@atproto-labs/simple-store@0.2.0": {
227
236
"integrity": "sha512-0bRbAlI8Ayh03wRwncAMEAyUKtZ+AuTS1jgPrfym1WVOAOiottI/ZmgccqLl6w5MbxVcClNQF7WYGKvGwGoIhA=="
···
300
309
"@atproto-labs/fetch",
301
310
"@atproto-labs/handle-resolver",
302
311
"@atproto-labs/identity-resolver",
303
-
"@atproto-labs/simple-store",
312
+
"@atproto-labs/simple-store@0.2.0",
304
313
"@atproto-labs/simple-store-memory",
305
314
"@atproto/did",
306
315
"@atproto/jwk@0.1.5",
···
482
491
"@jridgewell/sourcemap-codec"
483
492
]
484
493
},
494
+
"@lucide/lab@0.1.2": {
495
+
"integrity": "sha512-VprF2BJa7ZuTGOhUd5cf8tHJXyL63wdxcGieAiVVoR9hO0YmPsnZO0AGqDiX2/br+/MC6n8BoJcmPilltOXIJA=="
496
+
},
485
497
"@noble/curves@1.9.1": {
486
498
"integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==",
487
499
"dependencies": [
···
534
546
"@trysound/sax@0.2.0": {
535
547
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
536
548
},
549
+
"@types/node@22.15.15": {
550
+
"integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
551
+
"dependencies": [
552
+
"undici-types"
553
+
]
554
+
},
537
555
"ansi-regex@5.0.1": {
538
556
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
539
557
},
···
996
1014
"lru-cache@10.4.3": {
997
1015
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
998
1016
},
1017
+
"lucide-preact@0.511.0_preact@10.26.6": {
1018
+
"integrity": "sha512-7MhxCepYkNOfXZTWahbDVODh/BkhLUeCTY5mh6WmIvWcCWssul7TeIM/SkNARifRWZ9KUwYcl9oeV6VTIlqJog==",
1019
+
"dependencies": [
1020
+
"preact"
1021
+
]
1022
+
},
999
1023
"mdn-data@2.0.28": {
1000
1024
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="
1001
1025
},
···
1349
1373
"source-map-js"
1350
1374
]
1351
1375
},
1376
+
"preact-feather@4.2.1_preact@10.26.6": {
1377
+
"integrity": "sha512-yK5kYW64AoOkm+xTtUjwcFx0zNrqVTbwmtww8G2AmAB6f8wyQgwZgc6oRXllSYeg7q1I8VbkUpErJuKJ6Vq2eA==",
1378
+
"dependencies": [
1379
+
"preact"
1380
+
]
1381
+
},
1352
1382
"preact-render-to-string@6.5.13_preact@10.26.6": {
1353
1383
"integrity": "sha512-iGPd+hKPMFKsfpR2vL4kJ6ZPcFIoWZEcBf0Dpm3zOpdVvj77aY8RlLiQji5OMrngEyaxGogeakTb54uS2FvA6w==",
1354
1384
"dependencies": [
···
1547
1577
},
1548
1578
"uncrypto@0.1.3": {
1549
1579
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
1580
+
},
1581
+
"undici-types@6.21.0": {
1582
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
1550
1583
},
1551
1584
"undici@6.21.2": {
1552
1585
"integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g=="
+37
-60
islands/Header.tsx
+37
-60
islands/Header.tsx
···
1
1
import { useEffect, useState } from "preact/hooks";
2
2
import { IS_BROWSER } from "fresh/runtime";
3
+
import { Button } from "../components/Button.tsx";
3
4
4
5
interface User {
5
6
did: string;
···
18
19
19
20
export default function Header() {
20
21
const [user, setUser] = useState<User | null>(null);
22
+
const [showDropdown, setShowDropdown] = useState(false);
21
23
22
24
useEffect(() => {
23
25
if (!IS_BROWSER) return;
···
71
73
<div className="max-w-7xl mx-auto px-4">
72
74
<div className="flex items-center justify-between py-4">
73
75
{/* Home Link */}
74
-
<a
76
+
<Button
75
77
href="/"
76
-
className="airport-sign bg-gradient-to-r from-blue-500 to-blue-600 text-white flex items-center px-3 sm:px-6 py-2 sm:py-3 transform translate-y-0 transition-transform duration-200 ease-in-out hover:translate-y-1 hover:from-blue-600 hover:to-blue-700"
77
-
>
78
-
<img
79
-
src="/icons/plane_bold.svg"
80
-
alt="Plane"
81
-
className="w-6 h-6 mr-2"
82
-
style={{ filter: "brightness(0) invert(1)" }}
83
-
/>
84
-
<span className="font-mono font-bold tracking-wider">AIRPORT</span>
85
-
</a>
78
+
color="blue"
79
+
icon="/icons/plane_bold.svg"
80
+
iconAlt="Plane"
81
+
label="AIRPORT"
82
+
/>
86
83
87
84
<div className="flex items-center gap-3">
88
85
{/* Departures (Migration) */}
89
-
<div className="relative group">
90
-
<a
91
-
href="/migrate"
92
-
className="airport-sign bg-gradient-to-r from-amber-400 to-amber-500 text-slate-900 flex items-center px-3 sm:px-6 py-2 sm:py-3 transform translate-y-0 transition-transform duration-200 ease-in-out hover:translate-y-1 hover:from-amber-500 hover:to-amber-600"
93
-
>
94
-
<img
95
-
src="/icons/plane-departure_bold.svg"
96
-
alt="Departures"
97
-
className="w-6 h-6 mr-2"
98
-
style={{ filter: "brightness(0)" }}
99
-
/>
100
-
<span className="font-mono font-bold tracking-wider">
101
-
DEPARTURES
102
-
</span>
103
-
</a>
104
-
</div>
86
+
<Button
87
+
href="/migrate"
88
+
color="amber"
89
+
icon="/icons/plane-departure_bold.svg"
90
+
iconAlt="Departures"
91
+
label="DEPARTURES"
92
+
/>
105
93
106
94
{/* Check-in (Login/Profile) */}
107
95
<div className="relative">
108
-
{user?.did
109
-
? (
110
-
<div className="relative group">
111
-
<div className="airport-sign bg-gradient-to-r from-amber-400 to-amber-500 text-slate-900 flex items-center px-3 sm:px-6 py-2 sm:py-3 transform translate-y-0 transition-transform duration-200 ease-in-out hover:translate-y-1 hover:from-amber-500 hover:to-amber-600 cursor-pointer">
112
-
<img
113
-
src="/icons/ticket_bold.svg"
114
-
alt="Check-in"
115
-
className="w-6 h-6 mr-2"
116
-
style={{ filter: "brightness(0)" }}
117
-
/>
118
-
<span className="font-mono font-bold tracking-wider">
119
-
CHECKED IN
120
-
</span>
121
-
</div>
122
-
<div className="absolute opacity-0 translate-y-[-8px] pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto top-full right-0 w-56 bg-gradient-to-r from-amber-400 to-amber-500 text-slate-900 py-3 px-4 rounded-md transition-all duration-200">
96
+
{user?.did ? (
97
+
<div className="relative">
98
+
<Button
99
+
color="amber"
100
+
icon="/icons/ticket_bold.svg"
101
+
iconAlt="Check-in"
102
+
label="CHECKED IN"
103
+
onClick={() => setShowDropdown(!showDropdown)}
104
+
/>
105
+
{showDropdown && (
106
+
<div className="absolute right-0 mt-2 w-64 bg-white dark:bg-slate-800 rounded-lg shadow-lg p-4 border border-slate-200 dark:border-slate-700">
123
107
<div className="text-sm font-mono mb-2 pb-2 border-b border-slate-900/10">
124
108
<div title={user.handle || "Anonymous"}>
125
109
{truncateText(user.handle || "Anonymous", 20)}
···
136
120
Sign Out
137
121
</button>
138
122
</div>
139
-
</div>
140
-
)
141
-
: (
142
-
<a
143
-
href="/login"
144
-
className="airport-sign bg-gradient-to-r from-amber-400 to-amber-500 text-slate-900 flex items-center px-3 sm:px-6 py-2 sm:py-3 transform translate-y-0 transition-transform duration-200 ease-in-out hover:translate-y-1 hover:from-amber-500 hover:to-amber-600"
145
-
>
146
-
<img
147
-
src="/icons/ticket_bold.svg"
148
-
alt="Check-in"
149
-
className="w-6 h-6 mr-2"
150
-
style={{ filter: "brightness(0)" }}
151
-
/>
152
-
<span className="font-mono font-bold tracking-wider">
153
-
CHECK-IN
154
-
</span>
155
-
</a>
156
-
)}
123
+
)}
124
+
</div>
125
+
) : (
126
+
<Button
127
+
href="/login"
128
+
color="amber"
129
+
icon="/icons/ticket_bold.svg"
130
+
iconAlt="Check-in"
131
+
label="CHECK-IN"
132
+
/>
133
+
)}
157
134
</div>
158
135
</div>
159
136
</div>
+74
islands/SocialLinks.tsx
+74
islands/SocialLinks.tsx
···
1
+
import { useEffect, useState } from "preact/hooks";
2
+
import * as Icon from 'npm:preact-feather';
3
+
4
+
interface GitHubRepo {
5
+
stargazers_count: number;
6
+
}
7
+
8
+
export default function SocialLinks() {
9
+
const [starCount, setStarCount] = useState<number | null>(null);
10
+
11
+
useEffect(() => {
12
+
const fetchRepoInfo = async () => {
13
+
try {
14
+
const response = await fetch("https://api.github.com/repos/knotbin/airport");
15
+
const data: GitHubRepo = await response.json();
16
+
setStarCount(data.stargazers_count);
17
+
} catch (error) {
18
+
console.error("Failed to fetch GitHub repo info:", error);
19
+
}
20
+
};
21
+
22
+
fetchRepoInfo();
23
+
}, []);
24
+
25
+
const formatStarCount = (count: number | null) => {
26
+
if (count === null) return "...";
27
+
if (count >= 1000) {
28
+
return `${(count / 1000).toFixed(1)}k`;
29
+
}
30
+
return count.toString();
31
+
};
32
+
33
+
return (
34
+
<div class="mt-8 flex justify-center items-center gap-6">
35
+
<a
36
+
href="https://bsky.app/profile/knotbin.com"
37
+
class="text-gray-600 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-400 transition-colors"
38
+
target="_blank"
39
+
rel="noopener noreferrer"
40
+
>
41
+
<svg
42
+
class="w-6 h-6"
43
+
viewBox="-20 -20 296 266"
44
+
fill="none"
45
+
stroke="currentColor"
46
+
stroke-width="25"
47
+
stroke-linejoin="round"
48
+
xmlns="http://www.w3.org/2000/svg"
49
+
>
50
+
<path
51
+
d="M55.491 15.172c29.35 22.035 60.917 66.712 72.509 90.686 11.592-23.974 43.159-68.651 72.509-90.686C221.686-.727 256-13.028 256 26.116c0 7.818-4.482 65.674-7.111 75.068-9.138 32.654-42.436 40.983-72.057 35.942 51.775 8.812 64.946 38 36.501 67.187-54.021 55.433-77.644-13.908-83.696-31.676-1.11-3.257-1.63-4.78-1.637-3.485-.008-1.296-.527.228-1.637 3.485-6.052 17.768-29.675 87.11-83.696 31.676-28.445-29.187-15.274-58.375 36.5-67.187-29.62 5.041-62.918-3.288-72.056-35.942C4.482 91.79 0 33.934 0 26.116 0-13.028 34.314-.727 55.491 15.172Z"
52
+
/>
53
+
</svg>
54
+
</a>
55
+
<a
56
+
href="https://ko-fi.com/knotbin"
57
+
class="text-gray-600 hover:text-red-500 dark:text-gray-400 dark:hover:text-red-400 transition-colors"
58
+
target="_blank"
59
+
rel="noopener noreferrer"
60
+
>
61
+
<Icon.Coffee class="w-6 h-6" />
62
+
</a>
63
+
<a
64
+
href="https://github.com/knotbin/airport"
65
+
class="text-gray-600 hover:text-purple-500 dark:text-gray-400 dark:hover:text-purple-400 transition-colors flex items-center gap-1"
66
+
target="_blank"
67
+
rel="noopener noreferrer"
68
+
>
69
+
<Icon.Github class="w-6 h-6" />
70
+
<span class="text-sm font-mono">{formatStarCount(starCount)}</span>
71
+
</a>
72
+
</div>
73
+
);
74
+
}
+2
-2
islands/Ticket.tsx
+2
-2
islands/Ticket.tsx
···
40
40
41
41
return (
42
42
<div class="max-w-4xl mx-auto">
43
-
<div class="ticket mb-8 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 p-6 relative before:absolute before:inset-[1px] before:bg-white dark:before:bg-slate-800 before:-z-10 after:absolute after:inset-0 after:bg-slate-200 dark:after:bg-slate-700 after:-z-20 [clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)] after:[clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)]">
43
+
<div class="ticket mb-8 bg-white dark:bg-slate-800 p-6 relative before:absolute before:bg-white dark:before:bg-slate-800 before:-z-10 after:absolute after:inset-0 after:bg-slate-200 dark:after:bg-slate-700 after:-z-20 [clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)] after:[clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)]">
44
44
<div class="boarding-label text-amber-500 dark:text-amber-400 font-mono font-bold tracking-wider text-sm mb-4">
45
45
BOARDING PASS
46
46
</div>
···
71
71
</p>
72
72
</div>
73
73
74
-
<div class="ticket bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 p-6 relative before:absolute before:inset-[1px] before:bg-white dark:before:bg-slate-800 before:-z-10 after:absolute after:inset-0 after:bg-slate-200 dark:after:bg-slate-700 after:-z-20 [clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)] after:[clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)]">
74
+
<div class="ticket mb-8 bg-white dark:bg-slate-800 p-6 relative before:absolute before:bg-white dark:before:bg-slate-800 before:-z-10 after:absolute after:inset-0 after:bg-slate-200 dark:after:bg-slate-700 after:-z-20 [clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)] after:[clip-path:polygon(0_0,20px_0,100%_0,100%_calc(100%-20px),calc(100%-20px)_100%,0_100%,0_calc(100%-20px),20px_100%)]">
75
75
<div class="boarding-label text-amber-500 dark:text-amber-400 font-mono font-bold tracking-wider text-sm mb-4">
76
76
FLIGHT DETAILS
77
77
</div>
+9
-6
routes/index.tsx
+9
-6
routes/index.tsx
···
1
1
import Ticket from "../islands/Ticket.tsx";
2
2
import AirportSign from "../islands/AirportSign.tsx";
3
+
import SocialLinks from "../islands/SocialLinks.tsx";
4
+
import { Button } from "../components/Button.tsx";
3
5
4
6
export default function Home() {
5
7
return (
···
15
17
16
18
<Ticket />
17
19
18
-
<div class="mt-6 sm:mt-8 text-center">
19
-
<a
20
+
<div class="mt-6 sm:mt-8 text-center w-fit mx-auto">
21
+
<Button
20
22
href="/login"
21
-
class="inline-flex items-center px-4 sm:px-6 py-2 sm:py-3 border border-transparent text-base sm:text-lg font-mono rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
22
-
>
23
-
Begin Your Journey
24
-
</a>
23
+
color="blue"
24
+
label="BEGIN YOUR JOURNEY"
25
+
/>
25
26
</div>
27
+
28
+
<SocialLinks />
26
29
</div>
27
30
</div>
28
31
</div>
+1
-1
static/styles.css
+1
-1
static/styles.css