diff --git a/apps/web/app/(payload)/actions.ts b/apps/web/app/(payload)/actions.ts
new file mode 100644
index 0000000..ad9da86
--- /dev/null
+++ b/apps/web/app/(payload)/actions.ts
@@ -0,0 +1,4 @@
+"use server";
+import { handleServerFunctions as payloadHandleServerFunctions } from "@payloadcms/next/layouts";
+
+export const handleServerFunctions = payloadHandleServerFunctions;
diff --git a/apps/web/app/(payload)/admin/importMap.js b/apps/web/app/(payload)/admin/importMap.js
index fe3840c..b105d7e 100644
--- a/apps/web/app/(payload)/admin/importMap.js
+++ b/apps/web/app/(payload)/admin/importMap.js
@@ -1,75 +1,6 @@
-import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from "@payloadcms/richtext-lexical/rsc";
-import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from "@payloadcms/richtext-lexical/rsc";
-import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from "@payloadcms/richtext-lexical/rsc";
-import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
-import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from "@payloadcms/next/rsc";
export const importMap = {
- "@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell":
- RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
- "@payloadcms/richtext-lexical/rsc#RscEntryLexicalField":
- RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
- "@payloadcms/richtext-lexical/rsc#LexicalDiffComponent":
- LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
- "@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient":
- InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient":
- HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#UploadFeatureClient":
- UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#BlockquoteFeatureClient":
- BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#RelationshipFeatureClient":
- RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#LinkFeatureClient":
- LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#ChecklistFeatureClient":
- ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#OrderedListFeatureClient":
- OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#UnorderedListFeatureClient":
- UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#IndentFeatureClient":
- IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#AlignFeatureClient":
- AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#HeadingFeatureClient":
- HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#ParagraphFeatureClient":
- ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#InlineCodeFeatureClient":
- InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#SuperscriptFeatureClient":
- SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#SubscriptFeatureClient":
- SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#StrikethroughFeatureClient":
- StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#UnderlineFeatureClient":
- UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#BoldFeatureClient":
- BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
- "@payloadcms/richtext-lexical/client#ItalicFeatureClient":
- ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/next/rsc#CollectionCards":
CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1,
};
diff --git a/apps/web/app/(payload)/layout.tsx b/apps/web/app/(payload)/layout.tsx
index 5c37eb4..96b4606 100644
--- a/apps/web/app/(payload)/layout.tsx
+++ b/apps/web/app/(payload)/layout.tsx
@@ -3,8 +3,7 @@ import "@payloadcms/next/css";
import { RootLayout } from "@payloadcms/next/layouts";
import React from "react";
-// @ts-expect-error - Export exists in JS but TS types are missing in this version
-import { handleServerFunctions } from "@payloadcms/next/utilities";
+import { handleServerFunctions } from "./actions";
import { importMap } from "./admin/importMap";
export default function Layout({ children }: { children: React.ReactNode }) {
diff --git a/apps/web/app/about/opengraph-image.tsx b/apps/web/app/(site)/about/opengraph-image.tsx
similarity index 77%
rename from apps/web/app/about/opengraph-image.tsx
rename to apps/web/app/(site)/about/opengraph-image.tsx
index 8fe70ec..daf6e71 100644
--- a/apps/web/app/about/opengraph-image.tsx
+++ b/apps/web/app/(site)/about/opengraph-image.tsx
@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
-import { OGImageTemplate } from "../../src/components/OGImageTemplate";
-import { getOgFonts, OG_IMAGE_SIZE } from "../../src/lib/og-helper";
+import { OGImageTemplate } from "../../../src/components/OGImageTemplate";
+import { getOgFonts, OG_IMAGE_SIZE } from "../../../src/lib/og-helper";
export const size = OG_IMAGE_SIZE;
export const contentType = "image/png";
diff --git a/apps/web/app/about/page.tsx b/apps/web/app/(site)/about/page.tsx
similarity index 95%
rename from apps/web/app/about/page.tsx
rename to apps/web/app/(site)/about/page.tsx
index b483f76..f2abb28 100644
--- a/apps/web/app/about/page.tsx
+++ b/apps/web/app/(site)/about/page.tsx
@@ -1,8 +1,8 @@
"use client";
import Image from "next/image";
-import { Section } from "../../src/components/Section";
-import { Reveal } from "../../src/components/Reveal";
+import { Section } from "@/src/components/Section";
+import { Reveal } from "@/src/components/Reveal";
import {
ExperienceIllustration,
ResponsibilityIllustration,
@@ -11,8 +11,8 @@ import {
HeroLines,
ParticleNetwork,
GridLines,
-} from "../../src/components/Landing";
-import { Signature } from "../../src/components/Signature";
+} from "@/src/components/Landing";
+import { Signature } from "@/src/components/Signature";
import { Check } from "lucide-react";
import {
H1,
@@ -22,17 +22,17 @@ import {
BodyText,
Label,
MonoLabel,
-} from "../../src/components/Typography";
-import { Card, Container } from "../../src/components/Layout";
-import { Button } from "../../src/components/Button";
-import { IconList, IconListItem } from "../../src/components/IconList";
+} from "@/src/components/Typography";
+import { Card, Container } from "@/src/components/Layout";
+import { Button } from "@/src/components/Button";
+import { IconList, IconListItem } from "@/src/components/IconList";
import {
GradientMesh,
CodeSnippet,
AbstractCircuit,
-} from "../../src/components/Effects";
-import { getImgproxyUrl } from "../../src/utils/imgproxy";
-import { Marker } from "../../src/components/Marker";
+} from "@/src/components/Effects";
+import { getImgproxyUrl } from "@/src/utils/imgproxy";
+import { Marker } from "@/src/components/Marker";
export default function AboutPage() {
return (
diff --git a/apps/web/app/blog/[slug]/opengraph-image.tsx b/apps/web/app/(site)/blog/[slug]/opengraph-image.tsx
similarity index 86%
rename from apps/web/app/blog/[slug]/opengraph-image.tsx
rename to apps/web/app/(site)/blog/[slug]/opengraph-image.tsx
index 4b5ac2e..5866d5e 100644
--- a/apps/web/app/blog/[slug]/opengraph-image.tsx
+++ b/apps/web/app/(site)/blog/[slug]/opengraph-image.tsx
@@ -1,8 +1,8 @@
import { ImageResponse } from "next/og";
-import { getAllPosts } from "../../../src/lib/posts";
-import { blogThumbnails } from "../../../src/components/blog/blogThumbnails";
-import { BlogOGImageTemplate } from "../../../src/components/BlogOGImageTemplate";
-import { getOgFonts, OG_IMAGE_SIZE } from "../../../src/lib/og-helper";
+import { getAllPosts } from "../../../../src/lib/posts";
+import { blogThumbnails } from "../../../../src/components/blog/blogThumbnails";
+import { BlogOGImageTemplate } from "../../../../src/components/BlogOGImageTemplate";
+import { getOgFonts, OG_IMAGE_SIZE } from "../../../../src/lib/og-helper";
import * as fs from "node:fs/promises";
import * as path from "node:path";
diff --git a/apps/web/app/blog/[slug]/page.tsx b/apps/web/app/(site)/blog/[slug]/page.tsx
similarity index 83%
rename from apps/web/app/blog/[slug]/page.tsx
rename to apps/web/app/(site)/blog/[slug]/page.tsx
index deeeef7..096099c 100644
--- a/apps/web/app/blog/[slug]/page.tsx
+++ b/apps/web/app/(site)/blog/[slug]/page.tsx
@@ -1,14 +1,14 @@
import * as React from "react";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
-import { getAllPosts } from "../../../src/lib/posts";
-import { BlogPostHeader } from "../../../src/components/blog/BlogPostHeader";
-import { Section } from "../../../src/components/Section";
-import { Reveal } from "../../../src/components/Reveal";
-import { BlogPostClient } from "../../../src/components/BlogPostClient";
-import { TextSelectionShare } from "../../../src/components/TextSelectionShare";
-import { BlogPostStickyBar } from "../../../src/components/blog/BlogPostStickyBar";
-import { MDXContent } from "../../../src/components/MDXContent";
+import { getAllPosts } from "@/src/lib/posts";
+import { BlogPostHeader } from "@/src/components/blog/BlogPostHeader";
+import { Section } from "@/src/components/Section";
+import { Reveal } from "@/src/components/Reveal";
+import { BlogPostClient } from "@/src/components/BlogPostClient";
+import { TextSelectionShare } from "@/src/components/TextSelectionShare";
+import { BlogPostStickyBar } from "@/src/components/blog/BlogPostStickyBar";
+import { MDXContent } from "@/src/components/MDXContent";
export async function generateStaticParams() {
const allPosts = await getAllPosts();
diff --git a/apps/web/app/blog/page.tsx b/apps/web/app/(site)/blog/page.tsx
similarity index 73%
rename from apps/web/app/blog/page.tsx
rename to apps/web/app/(site)/blog/page.tsx
index 854b859..f6c35a5 100644
--- a/apps/web/app/blog/page.tsx
+++ b/apps/web/app/(site)/blog/page.tsx
@@ -1,5 +1,5 @@
-import { getAllPosts } from "../../src/lib/posts";
-import { BlogClient } from "../../src/components/blog/BlogClient";
+import { getAllPosts } from "@/src/lib/posts";
+import { BlogClient } from "@/src/components/blog/BlogClient";
import type { Metadata } from "next";
export const metadata: Metadata = {
diff --git a/apps/web/app/case-studies/klz-cables/page.tsx b/apps/web/app/(site)/case-studies/klz-cables/page.tsx
similarity index 98%
rename from apps/web/app/case-studies/klz-cables/page.tsx
rename to apps/web/app/(site)/case-studies/klz-cables/page.tsx
index e1af421..bc4db55 100644
--- a/apps/web/app/case-studies/klz-cables/page.tsx
+++ b/apps/web/app/(site)/case-studies/klz-cables/page.tsx
@@ -2,8 +2,8 @@
import React from "react";
import { motion, useScroll, useTransform } from "framer-motion";
-import { Section } from "../../../src/components/Section";
-import { Reveal } from "../../../src/components/Reveal";
+import { Section } from "@/src/components/Section";
+import { Reveal } from "@/src/components/Reveal";
import {
H1,
H2,
@@ -12,11 +12,11 @@ import {
Label,
MonoLabel,
BodyText,
-} from "../../../src/components/Typography";
-import { BackgroundGrid, Container } from "../../../src/components/Layout";
+} from "@/src/components/Typography";
+import { BackgroundGrid, Container } from "@/src/components/Layout";
import Link from "next/link";
-import { Button } from "../../../src/components/Button";
-import { IframeSection } from "../../../src/components/IframeSection";
+import { Button } from "@/src/components/Button";
+import { IframeSection } from "@/src/components/IframeSection";
import {
Activity,
ArrowRight,
@@ -27,8 +27,8 @@ import {
Layers,
} from "lucide-react";
-import { Marker } from "../../../src/components/Marker";
-import { GlitchText } from "../../../src/components/GlitchText";
+import { Marker } from "@/src/components/Marker";
+import { GlitchText } from "@/src/components/GlitchText";
export default function KLZCablesCaseStudy() {
const { scrollYProgress } = useScroll();
diff --git a/apps/web/app/case-studies/page.tsx b/apps/web/app/(site)/case-studies/page.tsx
similarity index 95%
rename from apps/web/app/case-studies/page.tsx
rename to apps/web/app/(site)/case-studies/page.tsx
index bb80788..bcb8dad 100644
--- a/apps/web/app/case-studies/page.tsx
+++ b/apps/web/app/(site)/case-studies/page.tsx
@@ -1,12 +1,12 @@
"use client";
import Image from "next/image";
-import { Section } from "../../src/components/Section";
-import { Reveal } from "../../src/components/Reveal";
-import { H3, LeadText, Label, BodyText } from "../../src/components/Typography";
-import { Card } from "../../src/components/Layout";
-import { Button } from "../../src/components/Button";
-import { AbstractCircuit } from "../../src/components/Effects";
+import { Section } from "@/src/components/Section";
+import { Reveal } from "@/src/components/Reveal";
+import { H3, LeadText, Label, BodyText } from "@/src/components/Typography";
+import { Card } from "@/src/components/Layout";
+import { Button } from "@/src/components/Button";
+import { AbstractCircuit } from "@/src/components/Effects";
import { ArrowRight } from "lucide-react";
import { motion } from "framer-motion";
diff --git a/apps/web/app/contact/opengraph-image.tsx b/apps/web/app/(site)/contact/opengraph-image.tsx
similarity index 78%
rename from apps/web/app/contact/opengraph-image.tsx
rename to apps/web/app/(site)/contact/opengraph-image.tsx
index ab9509f..eb9891e 100644
--- a/apps/web/app/contact/opengraph-image.tsx
+++ b/apps/web/app/(site)/contact/opengraph-image.tsx
@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
-import { OGImageTemplate } from "../../src/components/OGImageTemplate";
-import { getOgFonts, OG_IMAGE_SIZE } from "../../src/lib/og-helper";
+import { OGImageTemplate } from "../../../src/components/OGImageTemplate";
+import { getOgFonts, OG_IMAGE_SIZE } from "../../../src/lib/og-helper";
export const size = OG_IMAGE_SIZE;
export const contentType = "image/png";
diff --git a/apps/web/app/contact/page.tsx b/apps/web/app/(site)/contact/page.tsx
similarity index 67%
rename from apps/web/app/contact/page.tsx
rename to apps/web/app/(site)/contact/page.tsx
index 5b1b855..4d2d164 100644
--- a/apps/web/app/contact/page.tsx
+++ b/apps/web/app/(site)/contact/page.tsx
@@ -1,6 +1,6 @@
-import { Section } from "../../src/components/Section";
-import { ContactForm } from "../../src/components/ContactForm";
-import { AbstractCircuit } from "../../src/components/Effects";
+import { Section } from "@/src/components/Section";
+import { ContactForm } from "@/src/components/ContactForm";
+import { AbstractCircuit } from "@/src/components/Effects";
export default function ContactPage() {
return (
diff --git a/apps/web/app/error.tsx b/apps/web/app/(site)/error.tsx
similarity index 97%
rename from apps/web/app/error.tsx
rename to apps/web/app/(site)/error.tsx
index b7fd949..c3df6f6 100644
--- a/apps/web/app/error.tsx
+++ b/apps/web/app/(site)/error.tsx
@@ -2,7 +2,7 @@
import { useEffect } from "react";
import * as Sentry from "@sentry/nextjs";
-import { Button } from "../src/components/Button";
+import { Button } from "@/src/components/Button";
export default function Error({
error,
diff --git a/apps/web/app/errors/api/relay/route.ts b/apps/web/app/(site)/errors/api/relay/route.ts
similarity index 100%
rename from apps/web/app/errors/api/relay/route.ts
rename to apps/web/app/(site)/errors/api/relay/route.ts
diff --git a/apps/web/app/global-error.tsx b/apps/web/app/(site)/global-error.tsx
similarity index 100%
rename from apps/web/app/global-error.tsx
rename to apps/web/app/(site)/global-error.tsx
diff --git a/apps/web/app/globals.css b/apps/web/app/(site)/globals.css
similarity index 93%
rename from apps/web/app/globals.css
rename to apps/web/app/(site)/globals.css
index b09e887..0cc8d11 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/(site)/globals.css
@@ -86,7 +86,6 @@
/* Components - Tailwind utility classes */
@layer components {
-
/* Legacy hooks required by tests */
.file-example {
@apply w-full;
@@ -291,7 +290,6 @@
/* Print styles */
@media print {
-
.floating-back-to-top,
.reading-progress-bar {
display: none !important;
@@ -350,30 +348,38 @@
}
.highlighter-yellow {
- background: linear-gradient(135deg,
- rgba(255, 235, 59, 0.95) 0%,
- rgba(255, 213, 79, 0.95) 100%);
+ background: linear-gradient(
+ 135deg,
+ rgba(255, 235, 59, 0.95) 0%,
+ rgba(255, 213, 79, 0.95) 100%
+ );
color: #3f2f00;
}
.highlighter-pink {
- background: linear-gradient(135deg,
- rgba(255, 167, 209, 0.95) 0%,
- rgba(255, 122, 175, 0.95) 100%);
+ background: linear-gradient(
+ 135deg,
+ rgba(255, 167, 209, 0.95) 0%,
+ rgba(255, 122, 175, 0.95) 100%
+ );
color: #3f0018;
}
.highlighter-green {
- background: linear-gradient(135deg,
- rgba(129, 199, 132, 0.95) 0%,
- rgba(102, 187, 106, 0.95) 100%);
+ background: linear-gradient(
+ 135deg,
+ rgba(129, 199, 132, 0.95) 0%,
+ rgba(102, 187, 106, 0.95) 100%
+ );
color: #002f0a;
}
.highlighter-blue {
- background: linear-gradient(135deg,
- rgba(226, 232, 240, 0.95) 0%,
- rgba(203, 213, 225, 0.95) 100%);
+ background: linear-gradient(
+ 135deg,
+ rgba(226, 232, 240, 0.95) 0%,
+ rgba(203, 213, 225, 0.95) 100%
+ );
color: #0f172a;
}
@@ -409,14 +415,17 @@
border-radius: 0.18em;
z-index: -1;
- background: linear-gradient(180deg,
- rgba(255, 255, 255, 0) 0%,
- rgba(255, 255, 255, 0) 20%,
- rgba(253, 230, 138, 0.7) 20%,
- rgba(253, 230, 138, 0.7) 100%);
+ background: linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 0) 20%,
+ rgba(253, 230, 138, 0.7) 20%,
+ rgba(253, 230, 138, 0.7) 100%
+ );
transform-origin: left center;
- transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg)) skewX(calc((var(--marker-seed, 0) - 3) * -0.25deg));
+ transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg))
+ skewX(calc((var(--marker-seed, 0) - 3) * -0.25deg));
filter: saturate(1.05);
}
@@ -431,24 +440,28 @@
border-radius: 0.18em;
z-index: -1;
- background: linear-gradient(90deg,
- rgba(253, 230, 138, 0) 0%,
- rgba(253, 230, 138, 0.6) 8%,
- rgba(253, 230, 138, 0.55) 60%,
- rgba(253, 230, 138, 0.35) 100%);
+ background: linear-gradient(
+ 90deg,
+ rgba(253, 230, 138, 0) 0%,
+ rgba(253, 230, 138, 0.6) 8%,
+ rgba(253, 230, 138, 0.55) 60%,
+ rgba(253, 230, 138, 0.35) 100%
+ );
opacity: 0.75;
mix-blend-mode: multiply;
- transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg)) translateY(0.02em);
+ transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg))
+ translateY(0.02em);
- mask-image: linear-gradient(180deg,
- rgba(0, 0, 0, 0) 0%,
- rgba(0, 0, 0, 1) 20%,
- rgba(0, 0, 0, 1) 100%);
+ mask-image: linear-gradient(
+ 180deg,
+ rgba(0, 0, 0, 0) 0%,
+ rgba(0, 0, 0, 1) 20%,
+ rgba(0, 0, 0, 1) 100%
+ );
}
@media (prefers-reduced-motion: no-preference) {
-
.post-link:hover .marker-title::before,
.post-link:hover .marker-title::after {
filter: saturate(1.08) contrast(1.02);
@@ -711,7 +724,6 @@ pre:has(code[class*="language-"]) {
/* Gradient Mesh Blob Animations */
@keyframes gradient-blob-1 {
-
0%,
100% {
transform: translate(0, 0) scale(1);
@@ -731,7 +743,6 @@ pre:has(code[class*="language-"]) {
}
@keyframes gradient-blob-2 {
-
0%,
100% {
transform: translate(0, 0) scale(1);
@@ -747,7 +758,6 @@ pre:has(code[class*="language-"]) {
}
@keyframes gradient-blob-3 {
-
0%,
100% {
transform: translate(0, 0) scale(1);
@@ -783,7 +793,6 @@ pre:has(code[class*="language-"]) {
/* Circuit Pulse (used for node glow effects) */
@keyframes circuit-pulse {
-
0%,
100% {
opacity: 0.2;
@@ -887,12 +896,14 @@ pre:has(code[class*="language-"]) {
inset: 0;
border-radius: inherit;
padding: 1px;
- background: linear-gradient(90deg,
- transparent 0%,
- rgba(148, 163, 184, 0.3) 25%,
- rgba(191, 206, 228, 0.2) 50%,
- rgba(148, 163, 184, 0.3) 75%,
- transparent 100%);
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ rgba(148, 163, 184, 0.3) 25%,
+ rgba(191, 206, 228, 0.2) 50%,
+ rgba(148, 163, 184, 0.3) 75%,
+ transparent 100%
+ );
background-size: 200% 100%;
animation: border-trace 4s linear infinite;
-webkit-mask:
@@ -962,7 +973,6 @@ pre:has(code[class*="language-"]) {
}
@keyframes junctionGlow {
-
0%,
100% {
opacity: 0.1;
@@ -971,4 +981,4 @@ pre:has(code[class*="language-"]) {
50% {
opacity: 0.4;
}
-}
\ No newline at end of file
+}
diff --git a/apps/web/app/(site)/layout.tsx b/apps/web/app/(site)/layout.tsx
new file mode 100644
index 0000000..9a5b707
--- /dev/null
+++ b/apps/web/app/(site)/layout.tsx
@@ -0,0 +1,46 @@
+import type { Metadata } from "next";
+import { Inter, Newsreader } from "next/font/google";
+import { Analytics } from "@/src/components/Analytics";
+import { Footer } from "@/src/components/Footer";
+import { Header } from "@/src/components/Header";
+import { InteractiveElements } from "@/src/components/InteractiveElements";
+import "./globals.css";
+
+const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
+const newsreader = Newsreader({
+ subsets: ["latin"],
+ variable: "--font-newsreader",
+ style: "italic",
+ display: "swap",
+});
+
+export const metadata: Metadata = {
+ title: {
+ default: "Marc Mintel",
+ template: "%s | Marc Mintel",
+ },
+ description:
+ "Technical problem solver's blog - practical insights and learning notes",
+ metadataBase: new URL("https://mintel.me"),
+ icons: {
+ icon: "/favicon.svg",
+ },
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/apps/web/app/not-found.tsx b/apps/web/app/(site)/not-found.tsx
similarity index 97%
rename from apps/web/app/not-found.tsx
rename to apps/web/app/(site)/not-found.tsx
index ce9d53a..34fa5f6 100644
--- a/apps/web/app/not-found.tsx
+++ b/apps/web/app/(site)/not-found.tsx
@@ -4,7 +4,7 @@ export const metadata = {
};
import Link from "next/link";
-import { Button } from "../src/components/Button";
+import { Button } from "@/src/components/Button";
export default function NotFound() {
return (
diff --git a/apps/web/app/opengraph-image.tsx b/apps/web/app/(site)/opengraph-image.tsx
similarity index 78%
rename from apps/web/app/opengraph-image.tsx
rename to apps/web/app/(site)/opengraph-image.tsx
index 8e247e8..db33a01 100644
--- a/apps/web/app/opengraph-image.tsx
+++ b/apps/web/app/(site)/opengraph-image.tsx
@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
-import { OGImageTemplate } from "../src/components/OGImageTemplate";
-import { getOgFonts, OG_IMAGE_SIZE } from "../src/lib/og-helper";
+import { OGImageTemplate } from "../../src/components/OGImageTemplate";
+import { getOgFonts, OG_IMAGE_SIZE } from "../../src/lib/og-helper";
export const size = OG_IMAGE_SIZE;
export const contentType = "image/png";
diff --git a/apps/web/app/page.tsx b/apps/web/app/(site)/page.tsx
similarity index 95%
rename from apps/web/app/page.tsx
rename to apps/web/app/(site)/page.tsx
index 8f5977f..288db31 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/(site)/page.tsx
@@ -7,9 +7,9 @@ import {
ConceptPrice,
ConceptPrototyping,
ConceptWebsite,
-} from "../src/components/Landing";
-import { Reveal } from "../src/components/Reveal";
-import { Section } from "../src/components/Section";
+} from "@/src/components/Landing";
+import { Reveal } from "@/src/components/Reveal";
+import { Section } from "@/src/components/Section";
import {
H1,
H3,
@@ -17,15 +17,15 @@ import {
BodyText,
MonoLabel,
Label,
-} from "../src/components/Typography";
-import { Card, Container } from "../src/components/Layout";
-import { Button } from "../src/components/Button";
-import { GradientMesh, CodeSnippet } from "../src/components/Effects";
-import { IconList, IconListItem } from "../src/components/IconList";
-import { HeroSection } from "../src/components/HeroSection";
-import { GlitchText } from "../src/components/GlitchText";
-import { Marker } from "../src/components/Marker";
-import { PenCircle } from "../src/components/PenCircle";
+} from "@/src/components/Typography";
+import { Card, Container } from "@/src/components/Layout";
+import { Button } from "@/src/components/Button";
+import { GradientMesh, CodeSnippet } from "@/src/components/Effects";
+import { IconList, IconListItem } from "@/src/components/IconList";
+import { HeroSection } from "@/src/components/HeroSection";
+import { GlitchText } from "@/src/components/GlitchText";
+import { Marker } from "@/src/components/Marker";
+import { PenCircle } from "@/src/components/PenCircle";
export default function LandingPage() {
return (
diff --git a/apps/web/app/sitemap.ts b/apps/web/app/(site)/sitemap.ts
similarity index 96%
rename from apps/web/app/sitemap.ts
rename to apps/web/app/(site)/sitemap.ts
index e2f0be3..3fdcb8f 100644
--- a/apps/web/app/sitemap.ts
+++ b/apps/web/app/(site)/sitemap.ts
@@ -1,5 +1,5 @@
import { MetadataRoute } from "next";
-import { getAllPosts } from "../src/lib/posts";
+import { getAllPosts } from "@/src/lib/posts";
import { technologies } from "./technologies/[slug]/data";
/**
diff --git a/apps/web/app/stats/api/send/route.ts b/apps/web/app/(site)/stats/api/send/route.ts
similarity index 100%
rename from apps/web/app/stats/api/send/route.ts
rename to apps/web/app/(site)/stats/api/send/route.ts
diff --git a/apps/web/app/tags/[tag]/page.tsx b/apps/web/app/(site)/tags/[tag]/page.tsx
similarity index 89%
rename from apps/web/app/tags/[tag]/page.tsx
rename to apps/web/app/(site)/tags/[tag]/page.tsx
index 934f50b..5f22469 100644
--- a/apps/web/app/tags/[tag]/page.tsx
+++ b/apps/web/app/(site)/tags/[tag]/page.tsx
@@ -1,7 +1,7 @@
import Link from "next/link";
-import { getAllPosts } from "../../../src/lib/posts";
-import { MediumCard } from "../../../src/components/MediumCard";
-import { Reveal } from "../../../src/components/Reveal";
+import { getAllPosts } from "@/src/lib/posts";
+import { MediumCard } from "@/src/components/MediumCard";
+import { Reveal } from "@/src/components/Reveal";
export async function generateStaticParams() {
const allPosts = await getAllPosts();
diff --git a/apps/web/app/technologies/[slug]/content.tsx b/apps/web/app/(site)/technologies/[slug]/content.tsx
similarity index 96%
rename from apps/web/app/technologies/[slug]/content.tsx
rename to apps/web/app/(site)/technologies/[slug]/content.tsx
index 7970633..b2c654f 100644
--- a/apps/web/app/technologies/[slug]/content.tsx
+++ b/apps/web/app/(site)/technologies/[slug]/content.tsx
@@ -2,11 +2,11 @@
import React from "react";
import Link from "next/link";
-import { Container } from "../../../src/components/Layout";
-import { Label } from "../../../src/components/Typography";
+import { Container } from "@/src/components/Layout";
+import { Label } from "@/src/components/Typography";
import { Check, ArrowLeft, Zap, ExternalLink } from "lucide-react";
import { technologies } from "./data";
-import { Reveal } from "../../../src/components/Reveal";
+import { Reveal } from "@/src/components/Reveal";
export default function TechnologyContent({ slug }: { slug: string }) {
const tech = technologies[slug];
diff --git a/apps/web/app/(site)/technologies/[slug]/data.tsx b/apps/web/app/(site)/technologies/[slug]/data.tsx
new file mode 100644
index 0000000..cb7544d
--- /dev/null
+++ b/apps/web/app/(site)/technologies/[slug]/data.tsx
@@ -0,0 +1,115 @@
+import { Layers, Code, Database, Palette, Terminal } from "lucide-react";
+
+export interface TechInfo {
+ title: string;
+ subtitle: string;
+ description: string;
+ icon: any; // React.ElementType
+ benefits: string[];
+ customerValue: string;
+ color: string;
+ related: { name: string; slug: string }[];
+}
+
+export const technologies: Record = {
+ "next-js-14": {
+ title: "Next.js 14",
+ subtitle: "The React Framework for the Web",
+ description:
+ "Next.js 14 is the latest version of the industry-leading framework for building high-performance web applications. It allows me to create fast, scalable, and search-engine-friendly websites by rendering content on the server before sending it to your users.",
+ icon: Layers,
+ benefits: [
+ "Lightning-fast page loads with Server Components",
+ "Automatic image optimization",
+ "Instant navigation between pages",
+ "Top-tier SEO (Search Engine Optimization) out of the box",
+ ],
+ customerValue:
+ "For my clients, Next.js means a website that ranks higher on Google, keeps visitors engaged with instant interactions, and scales effortlessly as your traffic grows.",
+ color: "bg-black text-white",
+ related: [
+ { name: "TypeScript", slug: "typescript" },
+ { name: "React", slug: "react" },
+ ],
+ },
+ typescript: {
+ title: "TypeScript",
+ subtitle: "JavaScript with Syntax for Types",
+ description:
+ "TypeScript adds a powerful type system to JavaScript, catching errors before they ever reach production. It acts as a safety net for your code, ensuring that data flows exactly as expected through your entire application.",
+ icon: Code,
+ benefits: [
+ "Eliminates whole categories of common bugs",
+ "Makes large codebases easier to maintain",
+ "Improves developer productivity and code confidence",
+ "Ensures critical data integrity",
+ ],
+ customerValue:
+ 'Using TypeScript means your application is robust and reliable from day one. It dramatically reduces the risk of "runtime errors" that could crash your site, saving time and money on bug fixes down the line.',
+ color: "bg-blue-600 text-white",
+ related: [
+ { name: "Directus CMS", slug: "directus-cms" },
+ { name: "Next.js 14", slug: "next-js-14" },
+ ],
+ },
+ "directus-cms": {
+ title: "Directus CMS",
+ subtitle: "The Open Data Platform",
+ description:
+ "Directus is a modern, headless Content Management System (CMS) that instantly turns any database into a beautiful, easy-to-use application for managing your content. Unlike traditional CMSs, it doesn't dictate how your website looks.",
+ icon: Database,
+ benefits: [
+ "Intuitive interface for non-technical editors",
+ "Complete freedom regarding front-end design",
+ "Real-time updates and live previews",
+ "Highly secure and role-based access control",
+ ],
+ customerValue:
+ "Directus gives you full control over your content without needing a developer for every text change. It separates your data from the design, ensuring your website can evolve visually without rebuilding your entire content library.",
+ color: "bg-purple-600 text-white",
+ related: [
+ { name: "Next.js 14", slug: "next-js-14" },
+ { name: "Tailwind CSS", slug: "tailwind-css" },
+ ],
+ },
+ "tailwind-css": {
+ title: "Tailwind CSS",
+ subtitle: "Utility-First CSS Framework",
+ description:
+ "Tailwind CSS is a utility-first framework that allows me to build custom designs directly in markup. It eliminates the need for bulky, overriding stylesheets and ensures a consistent design system across every page.",
+ icon: Palette,
+ benefits: [
+ "Rapid UI development and prototyping",
+ "Consistent spacing, colors, and typography",
+ "Highly optimized final bundle size (only includes used styles)",
+ "Responsive design made simple",
+ ],
+ customerValue:
+ "Tailwind ensures your brand looks pixel-perfect on every device. It also results in incredibly small CSS files, meaning your site loads faster for users on mobile networks.",
+ color: "bg-cyan-500 text-white",
+ related: [
+ { name: "React", slug: "react" },
+ { name: "Next.js 14", slug: "next-js-14" },
+ ],
+ },
+ react: {
+ title: "React",
+ subtitle: "The Library for Web and Native User Interfaces",
+ description:
+ "React is the core library powering Next.js. It lets me build encapsulated components that manage their own state, then compose them to make complex UIs.",
+ icon: Terminal,
+ benefits: [
+ "Component-based architecture for reuse",
+ "Efficient updates and rendering",
+ "Rich ecosystem of libraries and tools",
+ "Strong community support",
+ ],
+ customerValue:
+ "React enables rich, app-like interactions on your website. Whether it's a complex dashboard or a smooth animation, React makes it possible to build dynamic experiences that feel instantaneous.",
+ color: "bg-blue-400 text-white",
+ related: [
+ { name: "Next.js 14", slug: "next-js-14" },
+ { name: "Tailwind CSS", slug: "tailwind-css" },
+ ],
+ },
+};
diff --git a/apps/web/app/(site)/technologies/[slug]/page.tsx b/apps/web/app/(site)/technologies/[slug]/page.tsx
new file mode 100644
index 0000000..6603986
--- /dev/null
+++ b/apps/web/app/(site)/technologies/[slug]/page.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import { technologies } from "./data";
+import TechnologyContent from "./content";
+
+export default async function TechnologyPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+ return ;
+}
+
+// Generate static params for these dynamic routes
+export async function generateStaticParams() {
+ return Object.keys(technologies).map((slug) => ({
+ slug,
+ }));
+}
diff --git a/apps/web/app/websites/page.tsx b/apps/web/app/(site)/websites/page.tsx
similarity index 96%
rename from apps/web/app/websites/page.tsx
rename to apps/web/app/(site)/websites/page.tsx
index 2d9cbe0..70cb397 100644
--- a/apps/web/app/websites/page.tsx
+++ b/apps/web/app/(site)/websites/page.tsx
@@ -1,23 +1,23 @@
"use client";
-import { Reveal } from "../../src/components/Reveal";
-import { Section } from "../../src/components/Section";
+import { Reveal } from "@/src/components/Reveal";
+import { Section } from "@/src/components/Section";
import {
SpeedPerformance,
SolidFoundation,
LayerSeparation,
TaskDone,
-} from "../../src/components/Landing";
+} from "@/src/components/Landing";
import {
H3,
LeadText,
BodyText,
Label,
MonoLabel,
-} from "../../src/components/Typography";
-import { Card } from "../../src/components/Layout";
-import { Button } from "../../src/components/Button";
-import { IconList, IconListItem } from "../../src/components/IconList";
+} from "@/src/components/Typography";
+import { Card } from "@/src/components/Layout";
+import { Button } from "@/src/components/Button";
+import { IconList, IconListItem } from "@/src/components/IconList";
import {
GradientMesh,
CodeSnippet,
@@ -25,8 +25,8 @@ import {
CMSVisualizer,
ArchitectureVisualizer,
ResultVisualizer,
-} from "../../src/components/Effects";
-import { Marker } from "../../src/components/Marker";
+} from "@/src/components/Effects";
+import { Marker } from "@/src/components/Marker";
export default function WebsitesPage() {
return (
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
deleted file mode 100644
index 6c85822..0000000
--- a/apps/web/app/layout.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import type { Metadata } from 'next';
-import { Inter, Newsreader } from 'next/font/google';
-import { Analytics } from '../src/components/Analytics';
-import { Footer } from '../src/components/Footer';
-import { Header } from '../src/components/Header';
-import { InteractiveElements } from '../src/components/InteractiveElements';
-import './globals.css';
-
-const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });
-const newsreader = Newsreader({
- subsets: ['latin'],
- variable: '--font-newsreader',
- style: 'italic',
- display: 'swap',
-});
-
-export const metadata: Metadata = {
- title: {
- default: 'Marc Mintel',
- template: '%s | Marc Mintel',
- },
- description: "Technical problem solver's blog - practical insights and learning notes",
- metadataBase: new URL('https://mintel.me'),
- icons: {
- icon: '/favicon.svg',
- },
-};
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return (
-
-
-
-
- {children}
-
-
-
-
-
-
- );
-}
diff --git a/apps/web/app/technologies/[slug]/data.tsx b/apps/web/app/technologies/[slug]/data.tsx
deleted file mode 100644
index 1b0c7d4..0000000
--- a/apps/web/app/technologies/[slug]/data.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { Layers, Code, Database, Palette, Terminal } from 'lucide-react';
-
-export interface TechInfo {
- title: string;
- subtitle: string;
- description: string;
- icon: any; // React.ElementType
- benefits: string[];
- customerValue: string;
- color: string;
- related: { name: string; slug: string }[];
-}
-
-export const technologies: Record = {
- 'next-js-14': {
- title: 'Next.js 14',
- subtitle: 'The React Framework for the Web',
- description: 'Next.js 14 is the latest version of the industry-leading framework for building high-performance web applications. It allows me to create fast, scalable, and search-engine-friendly websites by rendering content on the server before sending it to your users.',
- icon: Layers,
- benefits: [
- 'Lightning-fast page loads with Server Components',
- 'Automatic image optimization',
- 'Instant navigation between pages',
- 'Top-tier SEO (Search Engine Optimization) out of the box'
- ],
- customerValue: 'For my clients, Next.js means a website that ranks higher on Google, keeps visitors engaged with instant interactions, and scales effortlessly as your traffic grows.',
- color: 'bg-black text-white',
- related: [
- { name: 'TypeScript', slug: 'typescript' },
- { name: 'React', slug: 'react' }
- ]
- },
- 'typescript': {
- title: 'TypeScript',
- subtitle: 'JavaScript with Syntax for Types',
- description: 'TypeScript adds a powerful type system to JavaScript, catching errors before they ever reach production. It acts as a safety net for your code, ensuring that data flows exactly as expected through your entire application.',
- icon: Code,
- benefits: [
- 'Eliminates whole categories of common bugs',
- 'Makes large codebases easier to maintain',
- 'Improves developer productivity and code confidence',
- 'Ensures critical data integrity'
- ],
- customerValue: 'Using TypeScript means your application is robust and reliable from day one. It dramatically reduces the risk of "runtime errors" that could crash your site, saving time and money on bug fixes down the line.',
- color: 'bg-blue-600 text-white',
- related: [
- { name: 'Directus CMS', slug: 'directus-cms' },
- { name: 'Next.js 14', slug: 'next-js-14' }
- ]
- },
- 'directus-cms': {
- title: 'Directus CMS',
- subtitle: 'The Open Data Platform',
- description: 'Directus is a modern, headless Content Management System (CMS) that instantly turns any database into a beautiful, easy-to-use application for managing your content. Unlike traditional CMSs, it doesn\'t dictate how your website looks.',
- icon: Database,
- benefits: [
- 'Intuitive interface for non-technical editors',
- 'Complete freedom regarding front-end design',
- 'Real-time updates and live previews',
- 'Highly secure and role-based access control'
- ],
- customerValue: 'Directus gives you full control over your content without needing a developer for every text change. It separates your data from the design, ensuring your website can evolve visually without rebuilding your entire content library.',
- color: 'bg-purple-600 text-white',
- related: [
- { name: 'Next.js 14', slug: 'next-js-14' },
- { name: 'Tailwind CSS', slug: 'tailwind-css' }
- ]
- },
- 'tailwind-css': {
- title: 'Tailwind CSS',
- subtitle: 'Utility-First CSS Framework',
- description: 'Tailwind CSS is a utility-first framework that allows me to build custom designs directly in markup. It eliminates the need for bulky, overriding stylesheets and ensures a consistent design system across every page.',
- icon: Palette,
- benefits: [
- 'Rapid UI development and prototyping',
- 'Consistent spacing, colors, and typography',
- 'Highly optimized final bundle size (only includes used styles)',
- 'Responsive design made simple'
- ],
- customerValue: 'Tailwind ensures your brand looks pixel-perfect on every device. It also results in incredibly small CSS files, meaning your site loads faster for users on mobile networks.',
- color: 'bg-cyan-500 text-white',
- related: [
- { name: 'React', slug: 'react' },
- { name: 'Next.js 14', slug: 'next-js-14' }
- ]
- },
- 'react': {
- title: 'React',
- subtitle: 'The Library for Web and Native User Interfaces',
- description: 'React is the core library powering Next.js. It lets me build encapsulated components that manage their own state, then compose them to make complex UIs.',
- icon: Terminal,
- benefits: [
- 'Component-based architecture for reuse',
- 'Efficient updates and rendering',
- 'Rich ecosystem of libraries and tools',
- 'Strong community support'
- ],
- customerValue: 'React enables rich, app-like interactions on your website. Whether it\'s a complex dashboard or a smooth animation, React makes it possible to build dynamic experiences that feel instantaneous.',
- color: 'bg-blue-400 text-white',
- related: [
- { name: 'Next.js 14', slug: 'next-js-14' },
- { name: 'Tailwind CSS', slug: 'tailwind-css' }
- ]
- }
-};
diff --git a/apps/web/app/technologies/[slug]/page.tsx b/apps/web/app/technologies/[slug]/page.tsx
deleted file mode 100644
index a6fb657..0000000
--- a/apps/web/app/technologies/[slug]/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { technologies } from './data';
-import TechnologyContent from './content';
-
-export default async function TechnologyPage({ params }: { params: Promise<{ slug: string }> }) {
- const { slug } = await params;
- return ;
-}
-
-// Generate static params for these dynamic routes
-export async function generateStaticParams() {
- return Object.keys(technologies).map((slug) => ({
- slug,
- }));
-}
diff --git a/apps/web/package.json b/apps/web/package.json
index 011317b..527875b 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -4,7 +4,7 @@
"version": "0.1.0",
"description": "Technical problem solver's blog - practical insights and learning notes",
"scripts": {
- "dev": "rm -rf .next .contentlayer/.cache && concurrently -k -p \"[{name}]\" -n \"content,next\" -c \"magenta,cyan\" \"contentlayer2 dev\" \"next dev\"",
+ "dev": "rm -rf .next && next dev",
"build": "next build --webpack",
"start": "next start",
"lint": "eslint app src scripts video",
@@ -37,6 +37,7 @@
"@opentelemetry/core": "^2.1.0",
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@payloadcms/db-postgres": "^3.77.0",
+ "@payloadcms/email-nodemailer": "^3.77.0",
"@payloadcms/next": "^3.77.0",
"@payloadcms/richtext-lexical": "^3.77.0",
"@react-pdf/renderer": "^4.3.2",
diff --git a/apps/web/payload.config.ts b/apps/web/payload.config.ts
index ea1e01b..f7e0cb3 100644
--- a/apps/web/payload.config.ts
+++ b/apps/web/payload.config.ts
@@ -1,6 +1,7 @@
import { buildConfig } from "payload";
import { postgresAdapter } from "@payloadcms/db-postgres";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
+import { nodemailerAdapter } from "@payloadcms/email-nodemailer";
import path from "path";
import { fileURLToPath } from "url";
import sharp from "sharp";
@@ -20,6 +21,22 @@ export default buildConfig({
},
},
collections: [Users, Media, Posts],
+ ...(process.env.MAIL_HOST
+ ? {
+ email: nodemailerAdapter({
+ defaultFromAddress: process.env.MAIL_FROM || "info@mintel.me",
+ defaultFromName: "Mintel.me",
+ transportOptions: {
+ host: process.env.MAIL_HOST,
+ port: parseInt(process.env.MAIL_PORT || "587"),
+ auth: {
+ user: process.env.MAIL_USERNAME,
+ pass: process.env.MAIL_PASSWORD,
+ },
+ },
+ }),
+ }
+ : {}),
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || "fallback-secret-for-dev",
typescript: {
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 1154c6a..d4ffaa3 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -51,7 +51,7 @@ services:
- directus-db-data:/var/lib/postgresql/data
imgproxy:
- image: darthsim/imgproxy:latest
+ image: registry.infra.mintel.me/mintel/image-processor:latest
restart: always
networks:
- default
@@ -59,13 +59,12 @@ services:
extra_hosts:
- "mintel.localhost:host-gateway"
- "host.docker.internal:host-gateway"
+ env_file:
+ - .env
environment:
+ OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
IMGPROXY_URL_MAPPING: "http://mintel.localhost/:http://app:3000/"
- IMGPROXY_USE_ETAG: "true"
- IMGPROXY_MAX_SRC_RESOLUTION: 20
- IMGPROXY_ALLOWED_NETWORKS: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
- IMGPROXY_IGNORE_SSL_ERRORS: "true"
- IMGPROXY_DEBUG: "true"
+ IMGPROXY_LOG_LEVEL: debug
labels:
- "traefik.http.services.${PROJECT_NAME:-mintel-me}-imgproxy.loadbalancer.server.port=8080"
- "traefik.docker.network=infra"
diff --git a/docker-compose.yml b/docker-compose.yml
index 2209003..962786d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -79,6 +79,27 @@ services:
- "caddy=gatekeeper.${TRAEFIK_HOST:-mintel.localhost}"
- "caddy.reverse_proxy={{upstreams 3000}}"
+ mintel-me-imgproxy:
+ image: registry.infra.mintel.me/mintel/image-processor:latest
+ restart: always
+ networks:
+ infra:
+ aliases:
+ - mintel-me-imgproxy
+ env_file:
+ - ${ENV_FILE:-.env}
+ environment:
+ OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.mintel-me-imgproxy.rule=Host(`img.${TRAEFIK_HOST:-mintel.localhost}`)"
+ - "traefik.http.routers.mintel-me-imgproxy.entrypoints=${TRAEFIK_ENTRYPOINT:-web}"
+ - "traefik.http.routers.mintel-me-imgproxy.service=mintel-me-imgproxy-svc"
+ - "traefik.http.services.mintel-me-imgproxy-svc.loadbalancer.server.port=8080"
+ - "traefik.docker.network=infra"
+ - "caddy=img.${TRAEFIK_HOST:-mintel.localhost}"
+ - "caddy.reverse_proxy={{upstreams 8080}}"
+
postgres-db:
image: postgres:15-alpine
restart: always
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5c10205..a989624 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -136,6 +136,9 @@ importers:
"@payloadcms/db-postgres":
specifier: ^3.77.0
version: 3.77.0(@opentelemetry/api@1.9.0)(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))
+ "@payloadcms/email-nodemailer":
+ specifier: ^3.77.0
+ version: 3.77.0(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))
"@payloadcms/next":
specifier: ^3.77.0
version: 3.77.0(@types/react@19.2.13)(graphql@16.12.0)(monaco-editor@0.55.1)(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
@@ -3580,6 +3583,15 @@ packages:
peerDependencies:
payload: 3.77.0
+ "@payloadcms/email-nodemailer@3.77.0":
+ resolution:
+ {
+ integrity: sha512-1i4JSYAzQ+t2htdZAbfek7cslWXYV0YeLN9MoDI6LSbR8Q6Run6idPBLdEkBNLPc0NJFVWepj2IgXt72ifdbtw==,
+ }
+ engines: { node: ^18.20.2 || >=20.9.0 }
+ peerDependencies:
+ payload: 3.77.0
+
"@payloadcms/graphql@3.77.0":
resolution:
{
@@ -11205,6 +11217,13 @@ packages:
integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==,
}
+ nodemailer@7.0.12:
+ resolution:
+ {
+ integrity: sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==,
+ }
+ engines: { node: ">=6.0.0" }
+
nodemailer@8.0.1:
resolution:
{
@@ -17406,6 +17425,11 @@ snapshots:
- sql.js
- sqlite3
+ "@payloadcms/email-nodemailer@3.77.0(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))":
+ dependencies:
+ nodemailer: 7.0.12
+ payload: 3.77.0(graphql@16.12.0)(typescript@5.9.3)
+
"@payloadcms/graphql@3.77.0(graphql@16.12.0)(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))(typescript@5.9.3)":
dependencies:
graphql: 16.12.0
@@ -22830,6 +22854,8 @@ snapshots:
node-releases@2.0.27: {}
+ nodemailer@7.0.12: {}
+
nodemailer@8.0.1: {}
normalize-path@3.0.0: {}