meta files

This commit is contained in:
Michael Freno
2025-12-17 15:32:20 -05:00
parent e02476b207
commit 5bf6385e2c
8 changed files with 859 additions and 46 deletions

78
AGENTS.md Normal file
View File

@@ -0,0 +1,78 @@
# Agent Guidelines for freno-dev
## Build/Lint/Test Commands
- **Dev**: `bun dev` (starts Vinxi dev server)
- **Build**: `bun build` (production build)
- **Start**: `bun start` (production server)
- **No tests configured** - No test runner or test scripts exist yet
## Tech Stack
- **Framework**: SolidJS with SolidStart (Vinxi)
- **Routing**: @solidjs/router
- **API**: tRPC v10 with Zod validation
- **Database**: libSQL/Turso with SQL queries
- **Styling**: TailwindCSS v4
- **Runtime**: Bun (Node >=22)
- **Deployment**: Vercel preset
## Code Style
### Naming Conventions
- **Files/Components**: PascalCase (e.g., `Button.tsx`, `UserProfile.tsx`)
- **Variables/Functions**: camelCase (e.g., `getUserID`, `displayName`)
- **Types/Interfaces**: PascalCase (e.g., `User`, `ButtonProps`)
- **Constants**: camelCase or UPPER_SNAKE_CASE for true constants
### Imports
- Prefer named imports from solid-js: `import { createSignal, Show, For } from "solid-js"`
- Use `~/*` path alias for src imports: `import { api } from "~/lib/api"`
- Group imports: external deps → solid-js → local (~/)
### SolidJS Patterns (NOT React!)
- **State**: Use `createSignal()` not `useState`. Always call signals: `count()` to read
- **Effects**: Use `createEffect()` not `useEffect`. Auto-tracks dependencies (no array)
- **Conditionals**: Prefer `<Show when={condition()}>` over `&&` or ternary
- **Lists**: Prefer `<For each={items()}>` over `.map()`
- **Forms**: Use `onInput` (not `onChange`), access `e.currentTarget.value`
- **Refs**: Use `let ref` binding or `createSignal()` for reactive refs
### TypeScript
- **Strict mode enabled** - always type function params and returns
- Use interfaces for props: `export interface ButtonProps extends JSX.HTMLAttributes<T>`
- Use `splitProps()` for component prop destructuring
- Prefer explicit types over `any` - use `unknown` if type truly unknown
- Database types: Cast with `as unknown as User` for SQL results
### API/Server Patterns
- **tRPC routers**: Export from `src/server/api/routers/*.ts`
- **Procedures**: Use `.query()` for reads, `.mutation()` for writes
- **Validation**: Use Zod schemas in `.input()` - validate all user input
- **Auth**: Extract userId with `await getUserID(ctx.event.nativeEvent)`
- **Errors**: Throw `TRPCError` with proper codes (UNAUTHORIZED, NOT_FOUND, BAD_REQUEST)
- **Database**: Use `ConnectionFactory()` singleton, parameterized queries only
### Error Handling
- Use TRPCError with semantic codes on server
- Validate inputs with Zod schemas before processing
- Check auth state before mutations: throw UNAUTHORIZED if missing userId
- Return structured responses: `{ success: boolean, message?: string }`
### Comments
- **Minimal comments** - prefer self-documenting code
- JSDoc for exported functions/components only
- Inline comments for non-obvious logic only
### File Organization
- Routes in `src/routes/` (file-based routing)
- Components in `src/components/` (reusable) or co-located with routes
- API routers in `src/server/api/routers/`
- Types in `src/types/` (shared types) or co-located
- Utils in `src/lib/` or `src/server/utils.ts`
## Key Differences from React
See `src/lib/SOLID-PATTERNS.md` for comprehensive React→Solid conversion guide. Key gotchas:
- Signals must be called with `()` to read value
- `onChange``onInput` for real-time input updates
- `useEffect``createEffect` (auto-tracking, no deps array)
- `Link``A` component from @solidjs/router
- Server actions → tRPC procedures

9
public/robots.txt Normal file
View File

@@ -0,0 +1,9 @@
User-agent: *
Allow: /
Allow: /blog
Allow: /projects
Disallow: /login
Disallow: /debug/
Disallow: /databaseMGMT
Sitemap: https://www.freno.me/sitemap.xml

View File

@@ -1,32 +1,66 @@
import { Router } from "@solidjs/router"; import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router"; import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js"; import { createEffect, createSignal, ErrorBoundary, Suspense } from "solid-js";
import "./app.css"; import "./app.css";
import { LeftBar, RightBar } from "./components/Bars"; import { LeftBar, RightBar } from "./components/Bars";
import { TerminalSplash } from "./components/TerminalSplash"; import { TerminalSplash } from "./components/TerminalSplash";
import { SplashProvider } from "./context/splash"; import { SplashProvider } from "./context/splash";
import { MetaProvider } from "@solidjs/meta"; import { MetaProvider } from "@solidjs/meta";
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback";
export default function App() { export default function App() {
let leftBarRef: HTMLDivElement | undefined;
let rightBarRef: HTMLDivElement | undefined;
const [contentWidth, setContentWidth] = createSignal(0);
const [contentWidthOffset, setContentWidthOffset] = createSignal(0);
createEffect(() => {
const handleResize = () => {
if (leftBarRef && rightBarRef) {
setContentWidth(
window.innerWidth - leftBarRef.clientWidth - rightBarRef.clientWidth
);
setContentWidthOffset(leftBarRef.clientWidth);
}
};
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
});
return ( return (
<MetaProvider> <MetaProvider>
<SplashProvider> <SplashProvider>
<div> <ErrorBoundary
<TerminalSplash /> fallback={(error, reset) => (
<Router <ErrorBoundaryFallback error={error} reset={reset} />
root={(props) => ( )}
<div class="flex flex-row max-w-screen"> >
<LeftBar /> <div>
<div class="flex-1"> <TerminalSplash />
<Suspense>{props.children}</Suspense> <Router
root={(props) => (
<div class="flex max-w-screen flex-row">
<LeftBar ref={leftBarRef} />
<div
style={{
width: `${contentWidth()}px`,
"margin-left": `${contentWidthOffset()}px`
}}
>
<Suspense>{props.children}</Suspense>
</div>
<RightBar ref={rightBarRef} />
</div> </div>
<RightBar /> )}
</div> >
)} <FileRoutes />
> </Router>
<FileRoutes /> </div>
</Router> </ErrorBoundary>
</div>
</SplashProvider> </SplashProvider>
</MetaProvider> </MetaProvider>
); );

View File

@@ -1,15 +1,19 @@
import { Typewriter } from "./Typewriter"; import { Typewriter } from "./Typewriter";
export function LeftBar() { export function LeftBar(props: { ref: HTMLDivElement | undefined }) {
let ref = props.ref;
return ( return (
<nav class="border-r-overlay2 fixed h-full min-h-screen w-fit max-w-[25%] border-r-2"> <nav
<Typewriter speed={10} class="z-50 pr-8 pl-4"> ref={ref}
class="border-r-overlay2 fixed h-full min-h-screen w-fit max-w-[25%] border-r-2"
>
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
<h3 class="hover:text-subtext0 w-fit text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105"> <h3 class="hover:text-subtext0 w-fit text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
<a href="/">Freno.dev</a> <a href="/">Freno.dev</a>
</h3> </h3>
</Typewriter> </Typewriter>
<Typewriter keepAlive={false} class="z-50"> <Typewriter keepAlive={false} class="z-50 h-full">
<div class="text-text flex h-screen flex-col justify-between px-4 py-10 text-xl font-bold"> <div class="text-text flex h-full flex-col justify-between px-4 text-xl font-bold">
<ul class="gap-4"> <ul class="gap-4">
{/*TODO:Grab and render 5 most recent blog posts here */} {/*TODO:Grab and render 5 most recent blog posts here */}
<li></li> <li></li>
@@ -31,22 +35,30 @@ export function LeftBar() {
); );
} }
export function RightBar() { export function RightBar(props: { ref: HTMLDivElement | undefined }) {
let ref = props.ref;
return ( return (
<nav class="border-l-overlay2 text-text flex h-full min-h-screen w-fit max-w-[25%] flex-col gap-4 border-l-2 px-4 py-10 text-xl font-bold"> <nav
ref={ref}
class="border-l-overlay2 fixed right-0 h-full min-h-screen w-fit max-w-[25%] border-l-2"
>
<Typewriter keepAlive={false} class="z-50"> <Typewriter keepAlive={false} class="z-50">
<h3 class="text-center text-2xl">Right Navigation</h3> <h3 class="hover:text-subtext0 w-fit text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
<ul> Right Navigation
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold"> </h3>
<a href="#home">Home</a> <div class="text-text flex h-screen flex-col justify-between px-4 py-10 text-xl font-bold">
</li> <ul class="gap-4">
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold"> <li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#about">About</a> <a href="#home">Home</a>
</li> </li>
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold"> <li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#services">Services</a> <a href="#about">About</a>
</li> </li>
</ul> <li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#services">Services</a>
</li>
</ul>
</div>
</Typewriter> </Typewriter>
</nav> </nav>
); );

View File

@@ -0,0 +1,220 @@
import { useNavigate } from "@solidjs/router";
import { createEffect, createSignal, For } from "solid-js";
export interface ErrorBoundaryFallbackProps {
error: Error;
reset: () => void;
}
export default function ErrorBoundaryFallback(
props: ErrorBoundaryFallbackProps
) {
const navigate = useNavigate();
const [glitchText, setGlitchText] = createSignal("ERROR");
createEffect(() => {
console.error(props.error);
const glitchChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`";
const originalText = "ERROR";
const glitchInterval = setInterval(() => {
if (Math.random() > 0.8) {
let glitched = "";
for (let i = 0; i < originalText.length; i++) {
if (Math.random() > 0.6) {
glitched +=
glitchChars[Math.floor(Math.random() * glitchChars.length)];
} else {
glitched += originalText[i];
}
}
setGlitchText(glitched);
setTimeout(() => setGlitchText(originalText), 150);
}
}, 400);
return () => clearInterval(glitchInterval);
});
const createParticles = () => {
return Array.from({ length: 40 }, (_, i) => ({
id: i,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
animationDuration: `${2 + Math.random() * 3}s`
}));
};
return (
<div class="relative min-h-screen w-full overflow-hidden bg-gradient-to-bl from-slate-900 via-red-950/20 to-slate-900 dark:from-black dark:via-red-950/30 dark:to-black">
<div class="absolute inset-0 overflow-hidden">
<For each={createParticles()}>
{(particle) => (
<div
class="absolute animate-pulse"
style={{
left: particle.left,
top: particle.top,
"animation-delay": particle.animationDelay,
"animation-duration": particle.animationDuration
}}
>
<div class="h-1 w-1 rounded-full bg-red-400 opacity-40 dark:bg-red-300" />
</div>
)}
</For>
</div>
{/* Animated grid background */}
<div class="absolute inset-0 opacity-10">
<div
class="h-full w-full"
style={{
"background-image": `
linear-gradient(rgba(239, 68, 68, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(239, 68, 68, 0.3) 1px, transparent 1px)
`,
"background-size": "60px 60px",
animation: "grid-move 25s linear infinite"
}}
/>
</div>
{/* Main content */}
<div class="relative z-10 flex min-h-screen flex-col items-center justify-center px-4 text-center">
{/* Glitchy ERROR text */}
<div class="mb-8">
<h1
class="bg-gradient-to-r from-red-400 via-orange-500 to-red-600 bg-clip-text text-7xl font-bold text-transparent select-none md:text-8xl"
style={{
"text-shadow": "0 0 30px rgba(239, 68, 68, 0.5)",
filter: "drop-shadow(0 0 10px rgba(239, 68, 68, 0.3))"
}}
>
{glitchText()}
</h1>
<div class="mx-auto mt-2 h-1 w-40 animate-pulse bg-gradient-to-r from-transparent via-red-500 to-transparent" />
</div>
{/* Error message */}
<div class="max-w-2xl space-y-4">
<h2 class="animate-fade-in text-2xl font-light text-slate-700 md:text-3xl dark:text-slate-400">
Huh.
</h2>
<p class="animate-fade-in-delay text-lg text-slate-600 dark:text-slate-500">
An unexpected error has disrupted the flow of ... something.
<br />
But don't worry, you can try again or navigate back to safety.
</p>
{props.error.message && (
<p class="animate-fade-in-delay-2 font-mono text-sm text-slate-600 dark:text-slate-600">
Error: {props.error.message}
</p>
)}
</div>
<div class="mt-12 flex flex-col gap-4 sm:flex-row">
<button
onClick={() => props.reset()}
class="group relative overflow-hidden rounded-lg bg-gradient-to-r from-red-600 to-orange-600 px-8 py-4 text-lg font-medium text-white shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-red-500/25 active:scale-95"
>
<div class="absolute inset-0 bg-gradient-to-r from-red-700 to-orange-700 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<span class="relative flex items-center gap-2">🔄 Try Again</span>
</button>
<button
onClick={() => navigate("/")}
class="group relative overflow-hidden rounded-lg bg-gradient-to-r from-blue-600 to-purple-600 px-8 py-4 text-lg font-medium text-white shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-blue-500/25 active:scale-95"
>
<div class="absolute inset-0 bg-gradient-to-r from-blue-700 to-purple-700 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<span class="relative flex items-center gap-2">🏠 cd ~</span>
</button>
<button
onClick={() => window.history.back()}
class="group relative overflow-hidden rounded-lg border-2 border-slate-600 bg-transparent px-8 py-4 text-lg font-medium text-slate-600 transition-all duration-300 hover:border-red-500 hover:bg-red-500/10 hover:text-red-400 active:scale-95"
>
<span class="relative flex items-center gap-2"> Go Back</span>
</button>
</div>
{/* Floating elements */}
<div class="animate-bounce-slow absolute top-20 left-10">
<div class="h-6 w-6 rotate-45 bg-gradient-to-br from-red-400 to-orange-500 opacity-60" />
</div>
<div class="animate-bounce-slow-delay absolute top-32 right-16">
<div class="h-4 w-4 rounded-full bg-gradient-to-br from-orange-400 to-red-500 opacity-60" />
</div>
<div class="animate-bounce-slow absolute bottom-20 left-20">
<div
class="h-5 w-5 bg-gradient-to-br from-red-500 to-orange-400 opacity-60"
style={{ "clip-path": "polygon(50% 0%, 0% 100%, 100% 100%)" }}
/>
</div>
{/* Footer */}
<div class="absolute bottom-8 left-1/2 -translate-x-1/2">
<p class="text-sm text-slate-500 dark:text-slate-600">
System Error Something went wrong
</p>
</div>
</div>
{/* Custom styles */}
<style>{`
@keyframes grid-move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(60px, 60px);
}
}
.animate-fade-in {
animation: fadeIn 1s ease-out 0.5s both;
}
.animate-fade-in-delay {
animation: fadeIn 1s ease-out 1s both;
}
.animate-fade-in-delay-2 {
animation: fadeIn 1s ease-out 1.5s both;
}
.animate-bounce-slow {
animation: bounce-slow 3s ease-in-out infinite;
}
.animate-bounce-slow-delay {
animation: bounce-slow 3s ease-in-out infinite 1.5s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce-slow {
0%,
100% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
`}</style>
</div>
);
}

231
src/routes/401.tsx Normal file
View File

@@ -0,0 +1,231 @@
import { Title } from "@solidjs/meta";
import { HttpStatusCode } from "@solidjs/start";
import { useNavigate } from "@solidjs/router";
import { createEffect, createSignal, For } from "solid-js";
export default function Page_401() {
const navigate = useNavigate();
const [glitchText, setGlitchText] = createSignal("401");
createEffect(() => {
const glitchChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`";
const originalText = "401";
const glitchInterval = setInterval(() => {
if (Math.random() > 0.85) {
let glitched = "";
for (let i = 0; i < originalText.length; i++) {
if (Math.random() > 0.7) {
glitched +=
glitchChars[Math.floor(Math.random() * glitchChars.length)];
} else {
glitched += originalText[i];
}
}
setGlitchText(glitched);
setTimeout(() => setGlitchText(originalText), 100);
}
}, 300);
return () => clearInterval(glitchInterval);
});
const createParticles = () => {
return Array.from({ length: 45 }, (_, i) => ({
id: i,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
animationDuration: `${2 + Math.random() * 3}s`
}));
};
function doubleBack() {
window.history.go(-2);
}
return (
<>
<Title>401 - Unauthorized</Title>
<HttpStatusCode code={401} />
<div class="relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-slate-900 via-amber-950/20 to-slate-900 dark:from-black dark:via-amber-950/30 dark:to-black">
{/* Animated particle background */}
<div class="absolute inset-0 overflow-hidden">
<For each={createParticles()}>
{(particle) => (
<div
class="absolute animate-pulse"
style={{
left: particle.left,
top: particle.top,
"animation-delay": particle.animationDelay,
"animation-duration": particle.animationDuration
}}
>
<div class="h-1 w-1 rounded-full bg-amber-400 opacity-30 dark:bg-amber-300" />
</div>
)}
</For>
</div>
{/* Animated grid background */}
<div class="absolute inset-0 opacity-10">
<div
class="h-full w-full"
style={{
"background-image": `
linear-gradient(rgba(251, 191, 36, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(251, 191, 36, 0.3) 1px, transparent 1px)
`,
"background-size": "50px 50px",
animation: "grid-move 20s linear infinite"
}}
/>
</div>
{/* Logo overlay */}
<div class="absolute inset-0 flex items-center justify-center opacity-10">
<picture class="h-80 object-cover sm:h-96">
<source
srcSet="/WhiteLogo.png"
media="(prefers-color-scheme: dark)"
/>
<img
src="/BlackLogo.png"
alt="logo"
class="mx-auto brightness-50"
/>
</picture>
</div>
{/* Main content */}
<div class="relative z-10 flex min-h-screen flex-col items-center justify-center px-4 text-center">
{/* Glitchy 401 */}
<div class="mb-8">
<h1
class="bg-gradient-to-r from-amber-400 via-orange-500 to-amber-600 bg-clip-text text-8xl font-bold text-transparent select-none md:text-9xl"
style={{
"text-shadow": "0 0 30px rgba(251, 191, 36, 0.5)",
filter: "drop-shadow(0 0 10px rgba(251, 191, 36, 0.3))"
}}
>
{glitchText()}
</h1>
<div class="mx-auto mt-2 h-1 w-32 animate-pulse bg-gradient-to-r from-transparent via-amber-500 to-transparent" />
</div>
{/* Error message */}
<div class="max-w-2xl space-y-4">
<h2 class="animate-fade-in text-2xl font-light text-slate-300 md:text-3xl dark:text-slate-400">
Access Denied
</h2>
<p class="animate-fade-in-delay text-lg text-slate-400 dark:text-slate-500">
You lack authentication sufficient for that page.
<br />
Please log in or return to a safe location.
</p>
</div>
{/* Action buttons */}
<div class="mt-12 flex flex-col gap-4 sm:flex-row">
<button
onClick={() => navigate("/login")}
class="group relative overflow-hidden rounded-lg bg-gradient-to-r from-amber-600 to-orange-600 px-8 py-4 text-lg font-medium text-white shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-amber-500/25 active:scale-95"
>
<div class="absolute inset-0 bg-gradient-to-r from-amber-700 to-orange-700 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<span class="relative flex items-center gap-2">🔐 Login</span>
</button>
<button
onClick={() => navigate("/")}
class="group relative overflow-hidden rounded-lg bg-gradient-to-r from-blue-600 to-purple-600 px-8 py-4 text-lg font-medium text-white shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-blue-500/25 active:scale-95"
>
<div class="absolute inset-0 bg-gradient-to-r from-blue-700 to-purple-700 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<span class="relative flex items-center gap-2">
🏠 Return Home
</span>
</button>
<button
onClick={doubleBack}
class="group relative overflow-hidden rounded-lg border-2 border-slate-600 bg-transparent px-8 py-4 text-lg font-medium text-slate-300 transition-all duration-300 hover:border-amber-500 hover:bg-amber-500/10 hover:text-amber-400 active:scale-95"
>
<span class="relative flex items-center gap-2"> Go Back</span>
</button>
</div>
{/* Floating elements */}
<div class="animate-bounce-slow absolute top-20 left-10">
<div class="h-6 w-6 rotate-45 bg-gradient-to-br from-amber-400 to-orange-500 opacity-60" />
</div>
<div class="animate-bounce-slow-delay absolute top-32 right-16">
<div class="h-4 w-4 rounded-full bg-gradient-to-br from-orange-400 to-amber-500 opacity-60" />
</div>
<div class="animate-bounce-slow absolute bottom-20 left-20">
<div
class="h-5 w-5 bg-gradient-to-br from-amber-500 to-orange-400 opacity-60"
style={{ "clip-path": "polygon(50% 0%, 0% 100%, 100% 100%)" }}
/>
</div>
{/* Footer */}
<div class="absolute bottom-8 left-1/2 -translate-x-1/2">
<p class="text-sm text-slate-500 dark:text-slate-600">
Error Code: 401 Unauthorized Access
</p>
</div>
</div>
{/* Custom styles */}
<style>{`
@keyframes grid-move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
.animate-fade-in {
animation: fadeIn 1s ease-out 0.5s both;
}
.animate-fade-in-delay {
animation: fadeIn 1s ease-out 1s both;
}
.animate-bounce-slow {
animation: bounce-slow 3s ease-in-out infinite;
}
.animate-bounce-slow-delay {
animation: bounce-slow 3s ease-in-out infinite 1.5s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce-slow {
0%,
100% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
`}</style>
</div>
</>
);
}

View File

@@ -1,19 +1,204 @@
import { Title } from "@solidjs/meta"; import { Title } from "@solidjs/meta";
import { HttpStatusCode } from "@solidjs/start"; import { HttpStatusCode } from "@solidjs/start";
import { useNavigate } from "@solidjs/router";
import { createEffect, createSignal, For } from "solid-js";
export default function NotFound() { export default function NotFound() {
const navigate = useNavigate();
const [glitchText, setGlitchText] = createSignal("404");
createEffect(() => {
const glitchChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`";
const originalText = "404";
const glitchInterval = setInterval(() => {
if (Math.random() > 0.85) {
let glitched = "";
for (let i = 0; i < originalText.length; i++) {
if (Math.random() > 0.7) {
glitched +=
glitchChars[Math.floor(Math.random() * glitchChars.length)];
} else {
glitched += originalText[i];
}
}
setGlitchText(glitched);
setTimeout(() => setGlitchText(originalText), 100);
}
}, 300);
return () => clearInterval(glitchInterval);
});
const createParticles = () => {
return Array.from({ length: 50 }, (_, i) => ({
id: i,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
animationDuration: `${2 + Math.random() * 3}s`
}));
};
return ( return (
<main> <>
<Title>Not Found</Title> <Title>404 - Not Found</Title>
<HttpStatusCode code={404} /> <HttpStatusCode code={404} />
<h1>Page Not Found</h1> <div class="relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 dark:from-black dark:via-slate-900 dark:to-black">
<p> {/* Animated particle background */}
Visit{" "} <div class="absolute inset-0 overflow-hidden">
<a href="https://start.solidjs.com" target="_blank"> <For each={createParticles()}>
start.solidjs.com {(particle) => (
</a>{" "} <div
to learn how to build SolidStart apps. class="absolute animate-pulse"
</p> style={{
</main> left: particle.left,
top: particle.top,
"animation-delay": particle.animationDelay,
"animation-duration": particle.animationDuration
}}
>
<div class="h-1 w-1 rounded-full bg-blue-400 opacity-30 dark:bg-blue-300" />
</div>
)}
</For>
</div>
{/* Animated grid background */}
<div class="absolute inset-0 opacity-10">
<div
class="h-full w-full"
style={{
"background-image": `
linear-gradient(rgba(59, 130, 246, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(59, 130, 246, 0.3) 1px, transparent 1px)
`,
"background-size": "50px 50px",
animation: "grid-move 20s linear infinite"
}}
/>
</div>
{/* Main content */}
<div class="relative z-10 flex min-h-screen flex-col items-center justify-center px-4 text-center">
{/* Glitchy 404 */}
<div class="mb-8">
<h1
class="bg-gradient-to-r from-blue-400 via-purple-500 to-blue-600 bg-clip-text text-8xl font-bold text-transparent select-none md:text-9xl"
style={{
"text-shadow": "0 0 30px rgba(59, 130, 246, 0.5)",
filter: "drop-shadow(0 0 10px rgba(59, 130, 246, 0.3))"
}}
>
{glitchText()}
</h1>
<div class="mx-auto mt-2 h-1 w-32 animate-pulse bg-gradient-to-r from-transparent via-blue-500 to-transparent" />
</div>
{/* Error message with typewriter effect */}
<div class="max-w-2xl space-y-4">
<h2 class="animate-fade-in text-2xl font-light text-slate-300 md:text-3xl dark:text-slate-400">
You seem to have drifted off into space...
</h2>
<p class="animate-fade-in-delay text-lg text-slate-400 dark:text-slate-500">
...or the page you're looking for has drifted into the void.
<br />
But don't worry, we can navigate you back to safety.
</p>
</div>
{/* Action buttons */}
<div class="mt-12 flex flex-col gap-4 sm:flex-row">
<button
onClick={() => navigate("/")}
class="group relative overflow-hidden rounded-lg bg-gradient-to-r from-blue-600 to-purple-600 px-8 py-4 text-lg font-medium text-white shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-blue-500/25 active:scale-95"
>
<div class="absolute inset-0 bg-gradient-to-r from-blue-700 to-purple-700 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<span class="relative flex items-center gap-2">
🏠 Return Home
</span>
</button>
<button
onClick={() => window.history.back()}
class="group relative overflow-hidden rounded-lg border-2 border-slate-600 bg-transparent px-8 py-4 text-lg font-medium text-slate-300 transition-all duration-300 hover:border-blue-500 hover:bg-blue-500/10 hover:text-blue-400 active:scale-95"
>
<span class="relative flex items-center gap-2"> Go Back</span>
</button>
</div>
{/* Floating elements */}
<div class="animate-bounce-slow absolute top-20 left-10">
<div class="h-6 w-6 rotate-45 bg-gradient-to-br from-blue-400 to-purple-500 opacity-60" />
</div>
<div class="animate-bounce-slow-delay absolute top-32 right-16">
<div class="h-4 w-4 rounded-full bg-gradient-to-br from-purple-400 to-blue-500 opacity-60" />
</div>
<div class="animate-bounce-slow absolute bottom-20 left-20">
<div
class="h-5 w-5 bg-gradient-to-br from-blue-500 to-purple-400 opacity-60"
style={{ "clip-path": "polygon(50% 0%, 0% 100%, 100% 100%)" }}
/>
</div>
{/* Footer */}
<div class="absolute bottom-8 left-1/2 -translate-x-1/2">
<p class="text-sm text-slate-500 dark:text-slate-600">
Error Code: 404 Page Not Found
</p>
</div>
</div>
{/* Custom styles */}
<style>{`
@keyframes grid-move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
.animate-fade-in {
animation: fadeIn 1s ease-out 0.5s both;
}
.animate-fade-in-delay {
animation: fadeIn 1s ease-out 1s both;
}
.animate-bounce-slow {
animation: bounce-slow 3s ease-in-out infinite;
}
.animate-bounce-slow-delay {
animation: bounce-slow 3s ease-in-out infinite 1.5s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce-slow {
0%,
100% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
`}</style>
</div>
</>
); );
} }

44
src/routes/sitemap.xml.ts Normal file
View File

@@ -0,0 +1,44 @@
import { APIEvent } from "@solidjs/start/server";
export async function GET(event: APIEvent) {
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.freno.me</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://www.freno.me/projects</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.freno.me/blog</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://www.freno.me/contact</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://www.freno.me/login</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>
</urlset>`;
return new Response(sitemap, {
headers: {
"Content-Type": "application/xml",
"Cache-Control": "public, max-age=3600"
}
});
}