diff --git a/.gitignore b/.gitignore
index c3e08f3..9df2c16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,4 +49,7 @@ pnpm-lock.yaml
# auto-generated icon files
/icons/types
-/public/icons/sprites
\ No newline at end of file
+/public/icons/sprites
+
+# Service worker
+/public/sw.js
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 67e6533..ff53906 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "thebatproject",
- "version": "0.1.1",
+ "version": "0.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "thebatproject",
- "version": "0.1.1",
+ "version": "0.1.2",
"dependencies": {
"@tsparticles/all": "^3.7.1",
"@tsparticles/engine": "^3.7.1",
diff --git a/package.json b/package.json
index 72578c4..ce0cf30 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,20 @@
{
"name": "thebatproject",
- "version": "0.1.1",
+ "version": "0.1.2",
"private": true,
"type": "module",
"scripts": {
- "dev": "npm run pretty:check && npm run lint && npm run minify:images && npm run build:icons && next dev --turbopack",
- "build": "npm run pretty:check && npm run lint && npm run minify:images && npm run build:icons && next build",
+ "dev": "npm run prebuild && next dev --turbopack",
+ "build": "npm run prebuild && next build",
"start": "next start",
"lint": "next lint --max-warnings 0",
"pretty": "prettier --write \"./**/*.{json,tsx,ts,css,js,html,scss,yml}\"",
"pretty:check": "prettier \"./**/*.{json,tsx,ts,css,js,html,scss,yml}\" --check",
"minify:images": "npx tsx ./scripts/minify-images.ts",
"build:ci": "npx @cloudflare/next-on-pages@1",
- "build:icons": "npx tsx ./scripts/build-icons.ts"
+ "build:icons": "npx tsx ./scripts/build-icons.ts",
+ "generate:sw": "npx tsx ./scripts/generate-sw.ts",
+ "prebuild": "npm run pretty:check && npm run lint && npm run minify:images && npm run build:icons && npm run generate:sw"
},
"dependencies": {
"@tsparticles/all": "^3.7.1",
diff --git a/scripts/generate-sw.ts b/scripts/generate-sw.ts
new file mode 100644
index 0000000..403b2da
--- /dev/null
+++ b/scripts/generate-sw.ts
@@ -0,0 +1,77 @@
+import pkg from "../package.json";
+import * as path from "node:path";
+import fsExtra from "fs-extra";
+
+console.log(`Generating service worker for ${pkg.version}`);
+
+const scriptContent = `
+const cacheName = "${pkg.version}";
+const cacheLimit = 100;
+
+const ignoreEndPoints = [];
+
+const shouldIgnoreCaching = (request) => {
+ if (request.method !== "GET") {
+ return true;
+ }
+ return ignoreEndPoints.some((endPoint) => request.url.startsWith(endPoint));
+};
+
+const cacheClone = async (request) => {
+ const res = await fetch(request);
+ const cache = await caches.open(cacheName);
+ await cache.put(request, res.clone());
+ limitCacheSize(cacheName, cacheLimit);
+ return res;
+};
+
+const getCache = async (request) => {
+ const cache = await caches.match(request);
+ if (cache) {
+ return cache;
+ }
+ return cacheClone(request);
+};
+
+const clearOldCache = async () => {
+ const existingCaches = await caches.keys();
+ return Promise.all(
+ existingCaches
+ .filter((cache) => cache !== cacheName)
+ .map((cache) => caches.delete(cache)),
+ );
+};
+
+const limitCacheSize = async (cacheName, size) => {
+ const cache = await caches.open(cacheName);
+ const cacheKeys = await cache.keys();
+ if (cacheKeys.length > size) {
+ await cache.delete(cacheKeys[0]);
+ limitCacheSize(cacheName, size);
+ }
+};
+
+self.addEventListener("install", (e) => {
+ self.skipWaiting();
+ e.waitUntil(clearOldCache());
+});
+
+self.addEventListener("activate", (e) => {
+ e.waitUntil(clearOldCache());
+});
+
+self.addEventListener("fetch", (e) => {
+ if (shouldIgnoreCaching(e.request)) {
+ return;
+ }
+ e.respondWith(getCache(e.request));
+});
+`;
+
+const cwd = process.cwd();
+const outputDir = path.join(cwd, "public");
+const outputPath = path.join(outputDir, "sw.js");
+await fsExtra.ensureDir(outputDir);
+await fsExtra.writeFile(outputPath, scriptContent, "utf8");
+
+console.log(`Service worker generated at ${path.relative(cwd, outputPath)}`);
diff --git a/src/app/_components/HeroCopy.tsx b/src/app/_components/HeroCopy.tsx
index dae85e1..99a9c92 100644
--- a/src/app/_components/HeroCopy.tsx
+++ b/src/app/_components/HeroCopy.tsx
@@ -5,10 +5,10 @@ import { ExternalLinks } from "@/utils/constants";
export const HeroCopy = () => {
return (
-
+
Developer by day,
Vigilante by night
-
+
“If I can't change things here,{" "}
if I can't have an effect,
I
@@ -18,23 +18,23 @@ export const HeroCopy = () => {
SUMMON THE KNIGHT
-
+
(Schedule a call)
VIGILANTE PROFILE
-
+
(Download resume)
diff --git a/src/app/_components/HeroSection.tsx b/src/app/_components/HeroSection.tsx
index 215ee92..8af4850 100644
--- a/src/app/_components/HeroSection.tsx
+++ b/src/app/_components/HeroSection.tsx
@@ -17,7 +17,9 @@ export const HeroSection = () => {
className="w-full h-svh grid grid-cols-1 overflow-x-clip pt-32 xs:pt-32 sm:pt-36 lg:pt-16 lg:grid-cols-[1fr_1.2fr] items-center gap-10 xl:gap-20"
>
-
+
+
+
);
};
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index fc182ec..2d1433c 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
import { Bebas_Neue, Inter } from "next/font/google";
import "@/styles/globals.css";
import { getMetaData, getViewPort } from "@/utils/helpers";
+import { ServiceWorker } from "@/components/ServiceWorker";
const rethinkSans = Inter({
subsets: ["latin"],
@@ -25,9 +26,10 @@ export default function RootLayout({
return (
{children}
+
);
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
index fa24d8b..aee1562 100644
--- a/src/app/not-found.tsx
+++ b/src/app/not-found.tsx
@@ -7,15 +7,11 @@ const NotFound = () => {
Holy Broken Links, Batman!
-
+
Even Batman can't find this page... It might have vanished into the
Batcave or been taken down by the Joker. Either way, it's not here.{" "}
-
+
Back to Home
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index f897c79..494d32c 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -5,7 +5,7 @@ export type ButtonVariant = "primary";
export const variantClasses: Record = {
primary:
- "border border-gray-500 rounded-md border-solid text-white/80 bg-black/70 backdrop-blur-sm py-3 px-5 focus-visible:[&:not(:disabled)]:text-white hover:[&:not(:disabled)]:border-white/50 hover:[&:not(:disabled)]:text-white disabled:opacity-30",
+ "border border-stroke rounded-md border-solid text-secondary bg-surface/70 backdrop-blur-sm py-3 px-5 focus-visible:[&:not(:disabled)]:text-primary hover:[&:not(:disabled)]:border-tertiary hover:[&:not(:disabled)]:text-primary disabled:opacity-30",
};
export interface ButtonProps
diff --git a/src/components/CustomLink.tsx b/src/components/CustomLink.tsx
index fd7ca78..cad5f9c 100644
--- a/src/components/CustomLink.tsx
+++ b/src/components/CustomLink.tsx
@@ -16,7 +16,7 @@ export const CustomLink: React.ForwardRefExoticComponent<
return (
{
return (